当前位置: 首页 > 工具软件 > btcd > 使用案例 >

btcd源码解析——从“新区块的生成”开始

丁嘉
2023-12-01

1. 写在前面

我们从‘新区块的生成“开始我们的btcd源码之旅。

2. 相关命令

# start btcd
./btcd -u seafooler -P 123456 --simnet --miningaddr=SRgBmewVAfaVzqKMPHeFYwkiAGD8jKBWFz

# start btcwallet
./btcwallet -u seafooler -P 123456 --simnet

# generate a new block
./btcctl -u seafooler -P 123456 --wallet --simnet generate 1

3. btcctl中的相关源码

btcctl源码位于btcd整个源码树中,但其是个相对独立的模块,主要位于btcd/cmd/btcctl路径下。

3.1 btcctl的main函数

btcctl作为一个可执行程序,其必然存在main函数,在btcctl.go文件的L49.
main函数的前半部分主要是读取一些配置信息,我们为保证源码分析思路的清晰,先不去讲解这些配置信息,只在后面用到这些配置信息的时候再进行讲解。我们在进入到一个函数时,只会讲解跟当前分析最相关的那些代码。也就是说,我们可能会在不同的分析场景下多次讲解同一个函数。

// main[btcctl.go]
func main() {           // L49
    ...
    cmd, err := btcjson.NewCmd(method, params...)           // L107
    ...
    marshalledJSON, err := btcjson.MarshalCmd(1, cmd)           // L130
    ...
    result, err := sendPostRequest(marshalledJSON, cfg)           // L138
    ...
}

如上代码片段所示,method是从命令行参数中独取的, 利用NewCmd函数将其包装成一个cmd, 然后再利用MarshalCmd函数将其编码成Json格式,最后通过sendPostRequest函数将该命令发往btcwallet钱包进程。

3.2 NewCmd的函数

我们先来观察一下这个NewCmd函数。该函数定义在btcd/btcjson/cmdparser.go文件中。也就是说,btcctl是和btcd中的其他模块共享的该文件。

// main[btcctl.go] -> NewCmd[cmdparser.go]
func NewCmd(method string, args ...interface{}) (interface{}, error) {           // L511
    ...
    rtp, ok := methodToConcreteType[method]           // L515
    info := methodToInfo[method]            // L516
    ...
}

这一段代码涉及到反射的许多知识,相对来说是比较难理解的。其中涉及到的反射细节,我会单独再用一个章节来讲解。这里只需要知道method是存储在一个map字典中,从中查询到相应的变量。

3.3 method的注册

我们再来看一下generate这个method是何时以及如何注册到上述的map字典中的。

// main[btcctl.go] -> NewCmd[cmdparser.go] -> init[btcdextcmds.go]
func init() {
    ...
    MustRegisterCmd("generate", (*GenerateCmd)(nil), flags)
    ...
}
// main[btcctl.go] -> NewCmd[cmdparser.go] -> init[btcdextcmds.go] -> MustRegisterCmd[register.go]
func MustRegisterCmd(method string, cmd interface{}, flags UsageFlag) {
    if err := RegisterCmd(method, cmd, flags); err != nil {
    ...
}
// main[btcctl.go] -> NewCmd[cmdparser.go] -> init[btcdextcmds.go] -> MustRegisterCmd[register.go] -> RegisterCmd[register.go] 
methodToConcreteType = make(map[string]reflect.Type)
methodToInfo         = make(map[string]methodInfo)

func RegisterCmd(method string, cmd interface{}, flags UsageFlag) error {
    ...
    rtp := reflect.TypeOf(cmd)
    ...
    methodToConcreteType[method] = rtp
    methodToInfo[method] = methodInfo{       
        maxParams:    numFields,       
        numReqParams: numFields - numOptFields,       
        numOptParams: numOptFields,       
        defaults:     defaults,       
        flags:        flags,
    }
    ...
}

从上面三个代码片段可以看出,generate这个method是在btcdextcmds.go文件的初始化函数中被注册的。

3.4 sendPostRequest函数的实现

回到btcctlmain函数中来,我们来看一下sendPostRequest函数的实现细节。

// main[btcctl.go] ->  sendPostRequest[httpclient.go]
func sendPostRequest(marshalledJSON []byte, cfg *config) ([]byte, error) {
    ...
    url := protocol + "://" + cfg.RPCServer
    bodyReader := bytes.NewReader(marshalledJSON)
    httpRequest, err := http.NewRequest("POST", url, bodyReader)
    ...
    httpClient, err := newHTTPClient(cfg)
    ...
    httpResponse, err := httpClient.Do(httpRequest)
    ...    
}

这段代码是我们比较熟悉的,就是以POST方式调用url,并将Json数据作为附加数据发送出去。该url的地址和端口号在cfg.RPCServer中定义,其在main函数的loadConfig函数中的normalizeAddress函数中完成了初始化,如下所示:

// main[btcctl.go] -> loadConfig[config.go] -> normalizeAddress[config.go]
func normalizeAddress(addr string, useTestNet3, useSimNet, useWallet bool) string {
    ...
    switch {
    case useSimNet:
        ...
        defaultPort = "18554"
        ...
    }
    ...
}

18554端口是btcwallet进程监听的端口号,接下来我们进入btcwallet进行分析。

4. btcwallet中的相关代码

btcwallet具有两个职能:

  • 作为服务端,接收btcctl发过来的请求
  • 作为客户端,向btcd发送请求

同样地,btcwallet作为一个可以独立启动的进程,也必然是存在main函数的。而其main函数只是一个引子,其主要的功能代码都在walletMain函数中进行了实现。我们先来关注一下walletMain函数。

// walletMain[btcwallet.go]
func walletMain() error {               // L43
    ...
    rpcs, legacyRPCServer, err := startRPCServers(loader)                // L77
    ...
    go rpcClientConnectLoop(legacyRPCServer, loader)                    // L86
    ...
}

其中L77行的代码,主要实现了接收btcctl请求的服务端功能;而L86行的代码,主要实现了向btcd发送请求的客户端功能。

4.1 作为服务端的btcwallet

我们首先来看一下作为服务端的btcwallet是如何接收btcctl的请求的。

// walletMain[btcwallet.go] -> startRPCServers[rpcserver.go]
func startRPCServers(walletLoader *wallet.Loader) (*grpc.Server, *legacyrpc.Server, error) {             // L105
    ...
    legacyServer = legacyrpc.NewServer(&opts, walletLoader, listeners)               // L168
    ...
}

功能主要是在L168行的NewServer函数中实现的。需要注意的是,尽管该函数名为"New…",但服务端的启动也在该函数中完成了。代码如下所示:

// walletMain[btcwallet.go] -> startRPCServers[rpcserver.go] -> NewServe[server.go]
func NewServer(opts *Options, walletLoader *wallet.Loader, listeners []net.Listener) *Server {      // L90
    ...
    serveMux.Handle("/", throttledFn(opts.MaxPOSTClients,       // L117
        func(w http.ResponseWriter, r *http.Request) {
        ...
        server.postClientRPC(w, r)                                  // L129
        ...
        }))
    ...
}

NewServer函数中L129行的postClientRPC函数用于接收btcctl的数据,并将数据进行处理后发往btcd,关键代码如下所示:

// walletMain[btcwallet.go] -> startRPCServers[rpcserver.go] -> NewServe[server.go] -> postClientRPC[server.go]
func (s *Server) postClientRPC(w http.ResponseWriter, r *http.Request) {     // L566
    body := http.MaxBytesReader(w, r.Body, maxRequestSize)          // L567
    rpcRequest, err := ioutil.ReadAll(body)                             // L568
    ...
    err = json.Unmarshal(rpcRequest, &req)                         // L581
    ...
    switch req.Method {
        ...
    default:       
        res, jsonErr = s.handlerClosure(&req)()}                    // L611
    }
    ...
}

postClientRPC函数中的前半部分用于从网络中读取并解析出"req"请求,并通过L611行进行处理。下面我们来看一下handlerClosure函数的实现细节:

// walletMain[btcwallet.go] -> startRPCServers[rpcserver.go] -> NewServe[server.go] -> postClientRPC[server.go] -> handlerClosure[server.go]
func (s *Server) handlerClosure(request *btcjson.Request) lazyHandler{     // L274
    ...
    return lazyApplyHandler(request, wallet, chainClient)                   // L285
}

再来看lazyApplyHandler的实现:

// walletMain[btcwallet.go] -> startRPCServers[rpcserver.go] -> NewServe[server.go] -> postClientRPC[server.go] -> handlerClosure[server.go] -> lazyApplyHandler[methods.go]
func lazyApplyHandler(request *btcjson.Request, w *wallet.Wallet, chainClient chain.Interface) lazyHandler {     // L168
    handlerData, ok := rpcHandlers[request.Method]                          // L169
    if ok && handlerData.handlerWithChain != nil && w != nil && chainClient != nil {            // L170
        ...
    }
    if ok && handlerData.handler != nil && w != nil {                   // L192
        ...
    }
    return func() (interface{}, *btcjson.RPCError) {                        // L207
        ...
        switch client := chainClient.(type) {
        case *chain.RPCClient:
            resp, err := client.RawRequest(request.Method, 
                request.Params)
            ...
        ...
        }
    }
}

L169首先从rpcHandlers字典中查找是否有注册的handler,若有就直接调用该handler,但generate命令并没有注册相应的handler (后面我们会举sendtoaddress的例子,其在rpcHandlers中进行了注册)。因此,lazyApplyHandler函数中的代码将运行至L207中。我们来看看RawRequest函数的实现:

// walletMain[btcwallet.go] -> startRPCServers[rpcserver.go] -> NewServe[server.go] -> postClientRPC[server.go] -> handlerClosure[server.go] -> lazyApplyHandler[methods.go] -> RawRequest[rawrequest.go]
func (c *Client) RawRequest(method string, params []json.RawMessage) (json.RawMessage, error) {
    return c.RawRequestAsync(method, params).Receive()              // L77
}

RawRequest函数的实现很简单,只是做了一层函数的封装。但需要注意的是L77行末尾的Receive()调用。以下我们再分两个小节,分别讲解RawRequestAsync函数和Receive函数。

4.1.1 RawRequestAync函数相关

进一步查看RawRequestAsync函数:

// walletMain[btcwallet.go] -> startRPCServers[rpcserver.go] -> NewServe[server.go] -> postClientRPC[server.go] -> handlerClosure[server.go] -> lazyApplyHandler[methods.go] -> RawRequest[rawrequest.go] -> RawRequestAsync[rawrequest.go]
func (c *Client) RawRequestAsync(method string, params []json.RawMessage) FutureRawResult {
    ...
    rawRequest := &btcjson.Request{       
        Jsonrpc: "1.0",       
        ID:      id,       
        Method:  method,       
        Params:  params,
    }
    marshalledJSON, err := json.Marshal(rawRequest)
    ...
    responseChan := make(chan *response, 1)
    jReq := &jsonRequest{       
        id:             id,       
        method:         method,       
        cmd:            nil,       
        marshalledJSON: marshalledJSON,       
        responseChan:   responseChan,
    }
    c.sendRequest(jReq)             // L66
    
    return responseChan
}

RawRequestAsync函数首先将数据封装成jsonRequest请求,然后通过L66行的sendRequest函数将该请求发送出去。注意到RawRequestAsync函数返回的是FutureRawResult类型的变量,其会在4.1.2节中重点讲解。
sendRequest函数的实现细节如下所示:

// walletMain[btcwallet.go] -> startRPCServers[rpcserver.go] -> NewServe[server.go] -> postClientRPC[server.go] -> handlerClosure[server.go] -> lazyApplyHandler[methods.go] -> RawRequest[rawrequest.go] -> RawRequestAsync[rawrequest.go] -> sendRequest[infrastructure.go]
func (c *Client) sendRequest(jReq *jsonRequest) {
    ...
    c.sendMessage(jReq.marshalledJSON)
}

我们再来看sendMessage函数:

// walletMain[btcwallet.go] -> startRPCServers[rpcserver.go] -> NewServe[server.go] -> postClientRPC[server.go] -> handlerClosure[server.go] -> lazyApplyHandler[methods.go] -> RawRequest[rawrequest.go] -> RawRequestAsync[rawrequest.go] -> sendRequest[infrastructure.go] -> sendMessage[infrastructure.go]
func (c *Client) sendMessage(marshalledJSON []byte) {
    select {
    case c.sendChan <- marshalledJSON:          // L481
    case <-c.disconnectChan():
        return
    }
}

sendMessage函数中将数据发送往sendChan管道,用于激活4.2节中wsOutHandler函数中阻塞的L449行代码。需要注意的是,RawRequestAync函数并没有直接将数据发送到btcd中,而是将数据发往sendChan管道。4.2.1.2节中的wsOutHandler函数才会真正将数据发往btcd

4.1.2 Receive函数相关

回到RawRequest函数中L77行末尾的Receive()调用。
准确来说,Receive函数已经不完全属于"服务端"的职能了,因为该函数用于接收从btcd返回的数据(如新生成的区块的hash值)。但其并没有直接与btcd打交道,而是通过FutureRawResult管道接收返回值。和btcd打交道的工作是由4.2.1.1小节的wsInHandler函数完成,该函数进一步激活了FutureRawResult管道。
前面已经提及RawRequestAsync函数返回的是FutureRawResult类型的变量。
下面我们关注一下FutureRawResult类型以及其Receive方法。

// walletMain[btcwallet.go] -> startRPCServers[rpcserver.go] -> NewServe[server.go] -> postClientRPC[server.go] -> handlerClosure[server.go] -> lazyApplyHandler[methods.go] -> RawRequest[rawrequest.go] -> rawrequest.go
type FutureRawResult chan *response

注意到,FutureRawResult只是一个chan类型的重命名。

// walletMain[btcwallet.go] -> startRPCServers[rpcserver.go] -> NewServe[server.go] -> postClientRPC[server.go] -> handlerClosure[server.go] -> lazyApplyHandler[methods.go] -> RawRequest[rawrequest.go] -> Receive[rawrequest.go]
func (r FutureRawResult) Receive() (json.RawMessage, error) {
    return receiveFuture(r)           // L21
}

该函数也只是对receiveFuture函数进行了一层封装,receiveFuture函数的实现如下所示:

// walletMain[btcwallet.go] -> startRPCServers[rpcserver.go] -> NewServe[server.go] -> postClientRPC[server.go] -> handlerClosure[server.go] -> lazyApplyHandler[methods.go] -> RawRequest[rawrequest.go] -> Receive[rawrequest.go] -> receiveFuture[infrastructure.go]
func receiveFuture(f chan *response) ([]byte, error) {
    r := <-f                // L797
    ...
}

L797行形成了chan的阻塞,该阻塞将在4.2节的wsInHandler函数中被激活。

4.2 作为客户端的btcwallet

为避免这一篇博客过长,4.2节的内容将放在下一篇博客中。

 类似资料: