btcwallet 对外服务
btcwallet 除了像 btcd 对外提供 rpc 服务以外, 还提供了 grpc 服务, 同时 grpc 采用的是 protobuf 来实现.
这方便与不同语言进行交互, 降低客户端代码编写量.
阅读这个模块, 顺便了解一下 proto 的使用, 更详细的细节问题.
Service 分类
总共有三种 Service, 分别是 VersionService,WalletService 和 WalletLoaderService,
从中可以看出
VersionService
只是提供版本查询服务, 为什么会做成一个独立的服务, 设计者是出于什么考虑的呢?
这里重点考察 grpc 服务的启动过程
walletMain 函数中传递 wallet.Loader 调用 startRPCServers
配置 grpc 所需参数, 包括证书
创建 grpcServer
通过 rpcserver.StartVersionService 注册 VersionService
通过 pb.RegisterVersionServiceServer 注册 versionServer
这里的 RegisterVersionServiceServer 是自动生成,
versionServer 实现了 Version 接口, 对外提供服务
下面是 proto 自动生成的 Service Description , 其中 HandlerType 为空, 需要我们自己实现.
go var _VersionService_serviceDesc = grpc.ServiceDesc{ ServiceName: "walletrpc.VersionService", HandlerType: (*VersionServiceServer)(nil), Methods: []grpc.MethodDesc{ { MethodName: "Version", Handler: _VersionService_Version_Handler, }, }, Streams: []grpc.StreamDesc{}, Metadata: "api.proto", }
VersionService client 的实现
同时 proto 自动生成了客户端访问代码,
通过 NewVersionServiceClient 创建 VersionServiceClient
通过 VersionServiceClient 的 Version 来访问
相关参数
grpc 调用的所有参数都是通过 Message 来定义,
可以看出, 虽然 VersionRequest 什么都没偶, 还是要定义
- message VersionRequest {}
- message VersionResponse {
- string version_string = 1;
- uint32 major = 2;
- uint32 minor = 3;
- uint32 patch = 4;
- string prerelease = 5;
- string build_metadata = 6;
- }
客户端和服务端的实现
客户端, 由 proto 自动生成, 完全不用管理
- type VersionServiceClient interface {
- Version(ctx context.Context, in *VersionRequest, opts ...grpc.CallOption) (*VersionResponse, error)
- }
- func (c *versionServiceClient) Version(ctx context.Context, in *VersionRequest, opts ...grpc.CallOption) (*VersionResponse, error) {
- out := new(VersionResponse)
- err := grpc.Invoke(ctx, "/walletrpc.VersionService/Version", in, out, c.cc, opts...)
- if err != nil {
- return nil, err
- }
- return out, nil
- }
服务端
- type VersionServiceServer interface {
- Version(context.Context, *VersionRequest) (*VersionResponse, error)
- }
- func (*versionServer) Version(ctx context.Context, req *pb.VersionRequest) (*pb.VersionResponse, error) {
- return &pb.VersionResponse{
- VersionString: semverString,
- Major: semverMajor,
- Minor: semverMinor,
- Patch: semverPatch,
- }, nil
- }
这里给的例子比较特殊, 就是输入参数根本没用, 不过看得出如何使用 proto 以及 grpc 了.
WalletLoaderService
此服务主要用于打开关闭钱包,
StartConsensusRpc 是在 btcwallet 启动的时候没有指定 btcd 的情形下, 可以连接指定的 btcd.
- service WalletLoaderService {
- rpc WalletExists (WalletExistsRequest) returns (WalletExistsResponse);
- rpc CreateWallet (CreateWalletRequest) returns (CreateWalletResponse);
- rpc OpenWallet (OpenWalletRequest) returns (OpenWalletResponse);
- rpc CloseWallet (CloseWalletRequest) returns (CloseWalletResponse);
- rpc StartConsensusRpc (StartConsensusRpcRequest) returns (StartConsensusRpcResponse);
- }
WalletLoaderService 启动方式和 VersionService 完全一致.
我的问题:
钱包不存在的时候只能通过 --create 创建完成以后再启动, 是否这个服务目前根本没用?
核心服务 WalletService
接口
- service WalletService {
- // Queries
- rpc Ping (PingRequest) returns (PingResponse);
- rpc Network (NetworkRequest) returns (NetworkResponse);
- rpc AccountNumber (AccountNumberRequest) returns (AccountNumberResponse);
- rpc Accounts (AccountsRequest) returns (AccountsResponse);
- rpc Balance (BalanceRequest) returns (BalanceResponse);
- rpc GetTransactions (GetTransactionsRequest) returns (GetTransactionsResponse);
- // Notifications
- rpc TransactionNotifications (TransactionNotificationsRequest) returns (stream TransactionNotificationsResponse);
- rpc SpentnessNotifications (SpentnessNotificationsRequest) returns (stream SpentnessNotificationsResponse);
- rpc AccountNotifications (AccountNotificationsRequest) returns (stream AccountNotificationsResponse);
- // Control
- rpc ChangePassphrase (ChangePassphraseRequest) returns (ChangePassphraseResponse);
- rpc RenameAccount (RenameAccountRequest) returns (RenameAccountResponse);
- rpc NextAccount (NextAccountRequest) returns (NextAccountResponse);
- rpc NextAddress (NextAddressRequest) returns (NextAddressResponse);
- rpc ImportPrivateKey (ImportPrivateKeyRequest) returns (ImportPrivateKeyResponse);
- rpc FundTransaction (FundTransactionRequest) returns (FundTransactionResponse);
- rpc SignTransaction (SignTransactionRequest) returns (SignTransactionResponse);
- rpc PublishTransaction (PublishTransactionRequest) returns (PublishTransactionResponse);
- }
启动过程
walletMain 中等待钱包打开以后获取到钱包句柄, 然后调用 startWalletRPCServices
注意 startWalletRPCServices 传递进去三个参数, 一个是钱包句柄, 一个是 grpc server, 另一个是普通的 http rpc server
rpcserver.StartWalletService 启动 grpc WalletService
legacyServer.RegisterWallet 注册 http rpc 服务
pb.RegisterWalletServiceServer 注册 rpc.walletServer
rpc.walletServer 实现了接口
- type WalletServiceServer interface {
- // Queries
- Ping(context.Context, *PingRequest) (*PingResponse, error)
- Network(context.Context, *NetworkRequest) (*NetworkResponse, error)
- AccountNumber(context.Context, *AccountNumberRequest) (*AccountNumberResponse, error)
- Accounts(context.Context, *AccountsRequest) (*AccountsResponse, error)
- Balance(context.Context, *BalanceRequest) (*BalanceResponse, error)
- GetTransactions(context.Context, *GetTransactionsRequest) (*GetTransactionsResponse, error)
- // Notifications
- TransactionNotifications(*TransactionNotificationsRequest, WalletService_TransactionNotificationsServer) error
- SpentnessNotifications(*SpentnessNotificationsRequest, WalletService_SpentnessNotificationsServer) error
- AccountNotifications(*AccountNotificationsRequest, WalletService_AccountNotificationsServer) error
- // Control
- ChangePassphrase(context.Context, *ChangePassphraseRequest) (*ChangePassphraseResponse, error)
- RenameAccount(context.Context, *RenameAccountRequest) (*RenameAccountResponse, error)
- NextAccount(context.Context, *NextAccountRequest) (*NextAccountResponse, error)
- NextAddress(context.Context, *NextAddressRequest) (*NextAddressResponse, error)
- ImportPrivateKey(context.Context, *ImportPrivateKeyRequest) (*ImportPrivateKeyResponse, error)
- FundTransaction(context.Context, *FundTransactionRequest) (*FundTransactionResponse, error)
- SignTransaction(context.Context, *SignTransactionRequest) (*SignTransactionResponse, error)
- PublishTransaction(context.Context, *PublishTransactionRequest) (*PublishTransactionResponse, error)
- }
stream 返回的实现
stream 就是持续不断的有返回的意思吧.
rpc TransactionNotifications (TransactionNotificationsRequest) returns (stream TransactionNotificationsResponse); proto 中的接口被转换成了 TransactionNotifications(*TransactionNotificationsRequest, WalletService_TransactionNotificationsServer) error
其中 TransactionNotificationsResponse 被转换成了
- type WalletService_TransactionNotificationsServer interface {
- Send(*TransactionNotificationsResponse) error
- grpc.ServerStream
- }
服务端 TransactionNotifications 实现
- func (s *walletServer) TransactionNotifications(req *pb.TransactionNotificationsRequest,
- svr pb.WalletService_TransactionNotificationsServer) error {
- n := s.wallet.NtfnServer.TransactionNotifications()
- defer n.Done()
- ctxDone := svr.Context().Done()
- for {
- select {
- case v := <-n.C:
- resp := pb.TransactionNotificationsResponse{
- AttachedBlocks: marshalBlocks(v.AttachedBlocks),
- DetachedBlocks: marshalHashes(v.DetachedBlocks),
- UnminedTransactions: marshalTransactionDetails(v.UnminedTransactions),
- UnminedTransactionHashes: marshalHashes(v.UnminedTransactionHashes),
- }
- err := svr.Send(&resp)
- if err != nil {
- return translateError(err)
- }
- case <-ctxDone:
- return nil
- }
- }
- }
其他: 与 http rpc 服务的简单比较
通过代码实现对比就可以发现 http rpc 服务实现起来比较繁琐, 各种客户端编解码需要自己处理,
不过从代码完善度来说, http 接口明显更胜一筹, 无论是注释还是测试 case, 包括 API 文档.
如果生产中使用, 还是使用 http rpc 更好, 如果熟悉代码的话, 使用 grpc 更清晰.
来源: http://www.bubuko.com/infodetail-3064070.html