交易打包上链的概述
btcd首先从内存池取得交易,用交易填充区块,并往区块上填入必要的信息。随后区块进行POW计算。当计算符合难度值的区块哈希后,btcd对新区块进行最后一步的验证,将新区块连接到本地的主链上,并广播这个新区块给对等方。
BitCoin RPCs
为了了解交易是怎么被打包到区块,最后发布的,首先从查看相关的RPC调用。
getblocktemplate
getblocktemplate ( "template_request" )
其描述如下:
If the request parameters include a ‘mode’ key, that is used to explicitly select between the default ‘template’ request or a ‘proposal’.
It returns data needed to construct a block to work on.
getblocktemplate RPC
将返回构造一个区块所需要的数据。
generateblock
generateblock "output" ["rawtx/txid",...]
其描述如下:
Mine a block with a set of ordered transactions immediately to a specified address or descriptor (before the RPC call returns)
generateblock RPC
用给定的排好序的交易挖矿,需要指定受益人的地址。
submitblock
submitblock "hexdata" ( "dummy" )
其描述如下:
Attempts to submit new block to network.
尝试提交一个新的区块到网络中。
setgenerate
Set the server to generate coins (mine) or not.
NOTE: Since btcd does not have the wallet integrated to provide payment addresses, btcd must be configured via the --miningaddr option to provide which payment addresses to pay created blocks to for this RPC to function.
setgenerate设置server是否开启mining。如果开启,服务器将动用CPU mining。需要用–miningaddr指定受益人地址。
mining总体流程分析
有了这三个RPC,我们可以大概确定从打包交易到发布区块的流程。
首先通过getblocktemplate RPC
得到组装一个区块需要的信息,然后通过generateblock RPC
产生一个区块,最后通过submitblock RPC
将区块发布到网络上。
也可以通过setgenerate RPC
设置服务器开启mining,在服务器上完成区块的生成、挖矿、提交。
以下选取getblocktemplate RPC
和setgenerate RPC
进行分析。
getblocktemplate
从Client
发出RPC开始。该RPC位于rpcclient/mining.go
,其代码如下:
// GetBlockTemplate returns a new block template for mining.
func (c *Client) GetBlockTemplate(req *btcjson.TemplateRequest) (*btcjson.GetBlockTemplateResult, error) {
return c.GetBlockTemplateAsync(req).Receive()
}
它实际调用了异步版本GetBlockTemplateAsync
,并调用其Receive
方法阻塞等待结果返回:
// GetBlockTemplateAsync returns an instance of a type that can be used to get the
// result of the RPC at some future time by invoking the Receive function on the
// returned instance.
//
// See GetBlockTemplate for the blocking version and more details.
func (c *Client) GetBlockTemplateAsync(req *btcjson.TemplateRequest) FutureGetBlockTemplateResponse {
cmd := btcjson.NewGetBlockTemplateCmd(req)
return c.SendCmd(cmd)
}
调用了Client
的SendCmd
方法:
// SendCmd sends the passed command to the associated server and returns a
// response channel on which the reply will be delivered at some point in the
// future. It handles both websocket and HTTP POST mode depending on the
// configuration of the client.
func (c *Client) SendCmd(cmd interface{
}) chan *Response {
...
responseChan := make(chan *Response, 1)
jReq := &jsonRequest{
id: id,
method: method,
cmd: cmd,
marshalledJSON: marshalledJSON,
responseChan: responseChan,
}
c.sendRequest(jReq)
return responseChan
}
该方法声明了一个容量为1的回复用的通道,说明调用者GetBlockTemplateAsync
不会阻塞在等待该方法返回结果。
SendCmd
进一步调用了Client
的sendRequest
方法,之后就是用POST方法将请求发到服务器上。
在服务器上,RPC的处理方法位于rpcserver.go
,处理方法为handleGetBlockTemplate
,其代码如下:
// handleGetBlockTemplate implements the getblocktemplate command.
func handleGetBlockTemplate(s *rpcServer, cmd interface{
}, closeChan <-chan struct{
}) (interface{
}, error) {
c := cmd.(*btcjson.GetBlockTemplateCmd)
request := c.Request
// Set the default mode and override it if supplied.
mode := "template"
if request != nil && request.Mode != "" {
mode = request.Mode
}
switch mode {
case "template":
return handleGetBlockTemplateRequest(s, request, closeChan)
case "proposal":
return handleGetBlockTemplateProposal(s, request)
}
return nil, &btcjson.RPCError{
Code: btcjson.ErrRPCInvalidParameter,
Message: "Invalid mode",
}
}
该方法先判断请求的模式mode
是什么,默认为template
。以template
为例,它调用了handleGetBlockTemplateRequest
方法,其代码如下:
func handleGetBlockTemplateRequest(s *rpcServer, request *btcjson.TemplateRequest, closeChan <-chan struct{
}) (interface{
}, error) {
...
// Get and return a block template. A new block template will be
// generated when the current best block has changed or the transactions
// in the memory pool have been updated and it has been at least five
// seconds since the last template was generated. Otherwise, the
// timestamp for the existing block template is updated (and possibly
// the difficulty on testnet per the consesus rules).
if err := state.updateBlockTemplate(s, useCoinbaseValue); err != nil {
return nil, err
}
return state.blockTemplateResult(useCoinbaseValue, nil)
}
该方法调用了blockTemplateResult
方法。查看该方法可知blockTemplateResult
返回了目前和state
关联的区块模板。
首先看state
是什么,其结构体定义如下:
// gbtWorkState houses state that is used in between multiple RPC invocations to
// getblocktemplate.
type gbtWorkState struct {
sync.Mutex
lastTxUpdate time.Time
lastGenerated time.Time
prevHash *chainhash.Hash
minTimestamp time.Time
template *mining.BlockTemplate
notifyMap map[chainhash.Hash]map[int64]chan struct{
}
timeSource blockchain.MedianTimeSource
}
gbtWorkState
保存了多个getblocktemplate
RPC调用请求间的状态。其中BlockTemplate
域保存了我们需要的区块模板。那么这个template *mining.BlockTemplate
是什么时候设置的呢?其实在handleGetBlockTemplateRequest
中,调用的updateBlockTemplate
方法设置了这个template
:
func (state *gbtWorkState) updateBlockTemplate(s *rpcServer, useCoinbaseValue bool) error {
blkTemplate, err := generator.NewBlockTemplate(payAddr)
...
template = blkTemplate
...
state.template = template
...
}
NewBlockTemplate
方法则真正创建了一个新的区块模板。该区块模板使用来自内存池的交易创建区块。其中,传入的payToAddress
用于创建coinbase交易。
NewBlockTemplate选择交易的策略
选择和包含的交易根据几个因素进行优先级排序:
- 每个事务都有一个基于其值、输入时间和大小计算的优先级。由较大金额、较旧输入和较小规模组成的事务具有最高优先级;
- 计算每笔交易的每千字节费用。首选每千字节费用较高的交易。
- 将所有与块生成相关的策略设置都考虑在内。