zoukankan      html  css  js  c++  java
  • btcWallet系列之一-grpc模块

    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为空,需要我们自己实现.

    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更清晰.

  • 相关阅读:
    高斯滤波
    最短路径与贪婪
    应该要知道的几个统计学定义.
    Linux network setting.
    Windows与自定义USB HID设备通信说明.
    Win32 WriteFile and ReadFile
    C语言原码反码补码与位运算.
    Linux下查看显示器输出状态以及修改显示器工作模式(复制 or 扩展)
    Web通信协议:OSI、TCP、UDP、Socket、HTTP、HTTPS、TLS、SSL、WebSocket、Stomp
    如何用搜狗拼音输入法输入希腊字母及各种上下标
  • 原文地址:https://www.cnblogs.com/baizx/p/10888209.html
Copyright © 2011-2022 走看看