我们从‘新区块的生成“开始我们的btcd
源码之旅。
# 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
btcctl
源码位于btcd
整个源码树中,但其是个相对独立的模块,主要位于btcd/cmd/btcctl
路径下。
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
钱包进程。
我们先来观察一下这个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
字典中,从中查询到相应的变量。
我们再来看一下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
文件的初始化函数中被注册的。
回到btcctl
的main
函数中来,我们来看一下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
进行分析。
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
发送请求的客户端功能。
我们首先来看一下作为服务端的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
函数。
进一步查看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
。
回到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节的内容将放在下一篇博客中。