btcWallet系列之一-grpc模块

  • Post author:
  • Post category:其他


btcwallet对外服务

btcwallet除了像btcd对外提供rpc服务以外,还提供了grpc服务,同时grpc采用的是protobuf来实现.

这方便与不同语言进行交互,降低客户端代码编写量.

阅读这个模块,顺便了解一下proto的使用,更详细的细节问题.

Service分类

总共有三种Service,分别是VersionService,WalletService和WalletLoaderService,

从中可以看出

VersionService

只是提供版本查询服务,为什么会做成一个独立的服务,设计者是出于什么考虑的呢?

这里重点考察grpc服务的启动过程

  1. walletMain函数中传递wallet.Loader调用startRPCServers
  2. 配置grpc所需参数,包括证书
  3. 创建grpcServer
  4. 通过rpcserver.StartVersionService注册VersionService
  5. 通过pb.RegisterVersionServiceServer 注册versionServer

    这里的RegisterVersionServiceServer是自动生成,
  6. 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自动生成了客户端访问代码,

  1. 通过NewVersionServiceClient创建VersionServiceClient
  2. 通过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);
}

启动过程

  1. walletMain中等待钱包打开以后获取到钱包句柄,然后调用startWalletRPCServices
  2. 注意startWalletRPCServices传递进去三个参数,一个是钱包句柄,一个是grpc server,另一个是普通的http rpc server
  3. rpcserver.StartWalletService启动grpc WalletService
  4. legacyServer.RegisterWallet 注册http rpc服务
  5. pb.RegisterWalletServiceServer注册rpc.walletServer
  6. 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更清晰.

转载于:https://www.cnblogs.com/baizx/p/10888209.html