0%

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

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

I. 相关命令

1
2
3
4
5
6
7
8
# 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

II. btcctl中的相关源码

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

A. btcctl的main函数

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

1
2
3
4
5
6
7
8
9
10
// 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钱包进程。

B. NewCmd的函数

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

1
2
3
4
5
6
7
// 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字典中,从中查询到相应的变量。

C. method的注册

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

1
2
3
4
5
6
// main[btcctl.go] -> NewCmd[cmdparser.go] -> init[btcdextcmds.go]
func init() {
...
MustRegisterCmd("generate", (*GenerateCmd)(nil), flags)
...
}

1
2
3
4
5
// 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 {
...
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// 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文件的初始化函数中被注册的。

D. sendPostRequest函数的实现

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

1
2
3
4
5
6
7
8
9
10
11
12
// 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函数中完成了初始化,如下所示:

1
2
3
4
5
6
7
8
9
10
11
// 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进行分析。

III. btcwallet中的相关代码

btcwallet具有两个职能:

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

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

1
2
3
4
5
6
7
8
// walletMain[btcwallet.go]
func walletMain() error { // L43
...
rpcs, legacyRPCServer, err := startRPCServers(loader) // L77
...
go rpcClientConnectLoop(legacyRPCServer, loader) // L86
...
}

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

A. 作为服务端的btcwallet

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

1
2
3
4
5
6
// 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...",但服务端的启动也在该函数中完成了。代码如下所示:

1
2
3
4
5
6
7
8
9
10
11
// 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,关键代码如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 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函数的实现细节:

1
2
3
4
5
// 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的实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// 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函数的实现:

1
2
3
4
// 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函数。

1. RawRequestAync函数相关

进一步查看RawRequestAsync函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// 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类型的变量,其会在III.A.2节中重点讲解。 sendRequest函数的实现细节如下所示:

1
2
3
4
5
// 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函数:

1
2
3
4
5
6
7
8
// 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管道,用于激活III.B节中wsOutHandler函数中阻塞的L449行代码。需要注意的是,RawRequestAync函数并没有直接将数据发送到btcd中,而是将数据发往sendChan管道。III.B.1.2节中的wsOutHandler函数才会真正将数据发往btcd

2. Receive函数相关

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

1
2
// 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类型的重命名。

1
2
3
4
// 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函数的实现如下所示:

1
2
3
4
5
// 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的阻塞,该阻塞将在III.B节的wsInHandler函数中被激活。

B. 作为客户端的btcwallet

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