btcd源码解析——交易创建

1. 写在前面

从本节开始,我们从源码层面关注比特币交易的构建过程。
其中,我们尤其会关注比特币解锁脚本(为了使用UTXO)和新的锁定脚本(为了生成新的UTXO)的创建细节。我们相信通过跟踪两种脚本的创建过程,我们将对于比特币的交易细节理解得更为深入。

新交易的创建会涉及到两个代码仓库(btcdbtcwallet)编译生成的三个可执行文件(btcdbtcctl,和btcwallet)。
btcdbtcwallet代码版本号如下所示:

  • btcd版本:[git commit log]: ed77733ec07dfc8a513741138419b8d9d3de9d2d
  • btcwallet版本:[git commit log]: ae9416ad7623598121a7c8ad67a202c1be767155

读者如果没有阅读过之前的这两篇博客btcd源码解析btcd源码解析——从“新区块的生成”开始强烈建议先阅读完再来阅读本篇博客。

2. 相关命令

本篇博客从“发送一笔交易”的命令开始叙述,相应的命令如下:

./btcctl -u seafooler -P 123456 --wallet --simnet sendtoaddress SMZtiZkgwnsjy1WQNwoCrRwEbdsT8tktWU 10

其中SMZtiZkgwnsjy1WQNwoCrRwEbdsT8tktWU是接收方的比特币地址,10是发送的数额。

3. 从btcctl到btcwallet

btcd源码解析——从“新区块的生成”开始中所说,btcctl中利用MustRegisterCmd注册了一个sendtoaddress方法,如下所示:

// init [walletsvrcmds.go]
func init() {
    ...
    MustRegisterCmd("sendtoaddress", (*SendToAddressCmd)(nil), flags)  // L691
    ...
}

该方法和相应的参数会被序列化成json对象,然后通过sendPostRequest函数发送到btcwallet端处理,如下代码所示:

// 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
    ...

btcwallet代码中的rpcHandlers字典中注册了处理sendtoaddresshandler,代码如下所示:

// rpcHandlers [methods.go]
var rpcHandlers = map[string]struct {      
    handler          requestHandler      
    handlerWithChain requestHandlerChainRequired
    ...
    noHelp bool
} {
    ...
    "sendtoaddress":          {handler: sendToAddress},                 // L104
    ...
}

4. btcwallet中的实现——创建新交易

sendToAddress函数主体如下所示:

// sendToAddress [methods.go]
func sendToAddress(icmd interface{}, w *wallet.Wallet) (interface{}, error) {
    cmd := icmd.(*btcjson.SendToAddressCmd)                                 // L1501
    ...
    amt, err := btcutil.NewAmount(cmd.Amount)                               // L1512
    ...
    pairs := map[string]btcutil.Amount{                                     // L1523  
        cmd.Address: amt,
    }
    ...
    return sendPairs(w, pairs, waddrmgr.DefaultAccountNum, 1,           // L1528
        txrules.DefaultRelayFeePerKb)
}

其中,L1501首先将btcctl发送来的json数据转换为SendToAddressCmd类型的变量,然后依次构建出转账数额amt(L1512)和转账map (L1523), 最后调用sendPairs函数。

4.1. wallet变量的传入

需要注意的是,sendPairs中传入了wallet变量w,这是由lazyApplyHandler函数调用sendToAddress这个handler时传入的。而lazyApplyHandler函数中的w又是由handlerClosure函数中调用该函数时传入的,代码主体如下所示:

// handlerClosure [server.go]
func (s *Server) handlerClosure(request *btcjson.Request) lazyHandler {
    ...
    wallet := s.wallet
    ...
    return lazyApplyHandler(request, wallet, chainClient)
}
// handlerClosure [server.go] -> lazyApplyHandler [methods.go]
func lazyApplyHandler(request *btcjson.Request, w *wallet.Wallet, chainClient 
	chain.Interface) lazyHandler {
    handlerData, ok := rpcHandlers[request.Method]                                // L169
    if ok && handlerData.handlerWithChain != nil && w != nil && chainClient != nil {// L170
        return func() (interface{}, *btcjson.RPCError) {
            ...
        }
    }
    if ok && handlerData.handler != nil && w != nil {                             // L192
        return func() (interface{}, *btcjson.RPCError) 
            ...
            resp, err := handlerData.handler(cmd, w)                              // L198
            ...
        }
    }
    ...
    return func() (interface{}, *btcjson.RPCError) {                               // L207
        ...
    }
}

lazyApplyHandler中有三个return语句,分别对应于三种不同的request情况,如下所示。我们的sendtoaddress方法对应第2种情况。

  1. btcctl中传送来的方法没有在rpcHandlers中进行过注册,则对应于L207行的返回。如博客btcd源码解析——从“新区块的生成”开始中所介绍的,`generate方法即对应这种情况
  2. btcctl中传送来的方法已经在rpcHandlers中进行过注册,且处理该方法时无需与区块链链上数据进行交互,则对应于L192行的返回。我们这里的sendtoaddress方法即对应这种情况。需要解释的是,这里说的“无需与区块链链上数据进行交互”是指处理前期,最终将交易发送到链上肯定还是要交互的,但在前期生成交易的过程是不需要交互的。
  3. btcctl中传送来的方法已经在rpcHandlers中进行过注册,且处理该方法时需要与区块链链上数据进行交互,则对应于L170行的返回。

4.2. 创建output

我们知道一笔交易主要可以分为两大部分:inputoutput。其中output是相对比较简单的,所以我们在这篇博客中先介绍output, input的构建细节将交由下一篇博客进行介绍。
回到前面的sendPairs函数,主体代码如下所示。其中L1377行即调用makeOutputs函数生成output

// sendToAddress [methods.go] -> sendPairs [methods.go]
func sendPairs(w *wallet.Wallet, amounts map[string]btcutil.Amount, account uint32, 
	minconf int32, feeSatPerKb btcutil.Amount) (string, error) {
    outputs, err := makeOutputs(amounts, w.ChainParams())                       // L1377
    ...
    tx, err := w.SendOutputs(outputs, account, minconf, feeSatPerKb)            // L1381
    ...
}

makeOutputs函数代码如下所示:

// sendToAddress [methods.go] -> sendPairs [methods.go] -> makeOutputs [methods.go]
func makeOutputs(pairs map[string]btcutil.Amount, chainParams *chaincfg.Params) 
	([]*wire.TxOut, error) {
    outputs := make([]*wire.TxOut, 0, len(pairs))
    for addrStr, amt := range pairs {
        addr, err := btcutil.DecodeAddress(addrStr, chainParams)             // L1356
        ...
        pkScript, err := txscript.PayToAddrScript(addr)                      // L1361
        ...
        outputs = append(outputs, wire.NewTxOut(int64(amt), pkScript))       // L1366
    }
    return outputs, nil
}
4.2.1. 将字符串解码为地址

makeOutputs函数中的L1356行利用DecodeAddress函数将addrStr解码为Address类型。DecodeAddress函数主体代码如下所示:

// sendToAddress [methods.go] -> sendPairs [methods.go] -> makeOutputs [methods.go] 
// -> DecodeAddress [address.go]
func DecodeAddress(addr string, defaultNet *chaincfg.Params) (Address, error) {
    ...
    oneIndex := strings.LastIndexByte(addr, '1')                          // L142
    if oneIndex > 1 {
        ...
    }                                                                     // L169
    ...
    if len(addr) == 130 || len(addr) == 66 {                              // L173
        serializedPubKey, err := hex.DecodeString(addr)                   // L174
        ...
        return NewAddressPubKey(serializedPubKey, defaultNet)             // L178
    }                                                                     // L179
    ...
    decoded, netID, err := base58.CheckDecode(addr)                       // L182
    ...
    switch len(decoded) {                                                 // L189
    case ripemd160.Size:                                                  // L190
        isP2PKH := netID == defaultNet.PubKeyHashAddrID
        isP2SH := netID == defaultNet.ScriptHashAddrID
        switch hash160 := decoded; {
        ...
        case isP2PKH:      
            return newAddressPubKeyHash(hash160, netID)                    // L197
        case isP2SH:      
            return newAddressScriptHashFromHash(hash160, netID)            // L199
        ...
        }
    ...
}

DecodeAddress函数在解码地址的过程中,分为几种不同的情况。由于L142到L169主要是对Bech32格式的地址进行解码,笔者暂时对Bech32格式理解得还不到位,这里先略过不讲。先介绍其他几种情况。
总的来说,在sendtoaddress命令中可以接受作为地址的字符串包括以下两种形式:1)ECDSA public key;2)Common Bitcoin addr。
关于这两种形式,笔者也是强烈建议读者先阅读博客关于比特币地址的一些问题和解答,其中详细介绍了比特币地址九种形式的相互转化关系。
这里为了大家更加直观地理解,将该博客中的插图复制如下:
比特币地址的相关变量

4.2.1.1 ECDSA public key 格式

若字符串的长度是130或者66,表明这是个ECDSA public key格式的地址。
在L178行通过调用NewAddressPubKey函数生成PubKey格式的地址。

4.2.1.2 Common Bitcoin addr 格式

这个格式是我们最常见的地址,在比特币主网上以1或3开头,长度约为34位。
该格式的地址首先通过base58.CheckDecode函数转变为RIPEMD-160 hash value格式的地址 (decoded),并返回相应的地址类型 (netID),如L182所示。这里的地址类型是指PubKeyHash(P2PKH) 和ScriptHash(P2SH)两种类型。
对应于两种类型,分别生成两种地址,如L197和L199所示。

4.2.2. 构建锁定脚本

回到makeOutputs函数。基于解码后生成的地址,调用PayToAddrScript函数即可构建锁定脚本了,如L1361所示。
PayToAddrScript函数的主题代码如下所示。

// sendToAddress [methods.go] -> sendPairs [methods.go] -> makeOutputs [methods.go] 
// -> PayToAddrScript [standard.go]
func PayToAddrScript(addr btcutil.Address) ([]byte, error) {
    ...
    switch addr := addr.(type) {
    case *btcutil.AddressPubKeyHash:                       			          // L426
        ...
        return payToPubKeyHashScript(addr.ScriptAddress())              	  // L431
    case *btcutil.AddressScriptHash:
        ...
        return payToScriptHashScript(addr.ScriptAddress())
    case *btcutil.AddressPubKey:
        ...
        return payToPubKeyScript(addr.ScriptAddress())
    ...
}

PayToAddrScript函数的逻辑还是比较简单的。对应于不同格式的地址,采用不同的函数生成锁定脚本。我们以AddressPubKeyHash (也即P2PKH)为例,进行简单介绍。
L431行的ScriptAddress函数只是简单地返回addr中的字节切片,并作为参数传入payToPubKeyHashScript函数中。
payToPubKeyHashScript函数的主体代码如下所示:

// sendToAddress [methods.go] -> sendPairs [methods.go] -> makeOutputs [methods.go] 
// -> PayToAddrScript [standard.go] -> payToPubKeyHashScript
func payToPubKeyHashScript(pubKeyHash []byte) ([]byte, error) {
    return NewScriptBuilder().AddOp(OP_DUP).AddOp(OP_HASH160).      
        AddData(pubKeyHash).AddOp(OP_EQUALVERIFY).AddOp(OP_CHECKSIG).      
        Script()
}

这正是对应着我们已经很熟悉的P2PKH script的格式:

OP_DUP OP_HASH160 OP_EQUALVERIFY OP_CHECKSIG

4.2.3. 构建output

再次回到makeOutputs函数。
简单理解: output = amount + script.
有了4.2.2节构建的script,就很容易构建出output了,代码如L1366所示。
L1366主要通过调用wire.NewTxOut函数生成outputNewTxOut函数主体如下所示:

// sendToAddress [methods.go] -> sendPairs [methods.go] -> makeOutputs [methods.go] 
// -> NewTxOut [msgtx.go]
func NewTxOut(value int64, pkScript []byte) *TxOut {      
    return &TxOut{            
        Value:    value,            
        PkScript: pkScript,      
    }
}

NewTxOut函数还是比较简单的,这里略去不讲。

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
2008年爆发全球金融危机,同年11月1日,一个自称中本聪(Satoshi Nakamoto)的人在P2P foundation网站上发布了比特币白皮书《比特币:一种点对点的电子现金系统》 [6]  ,陈述了他对电子货币的新设想——比特币就此面世。2009年1月3日,比特币创世区块诞生。 和法定货币相比,比特币没有一个集中的发行方,而是由网络节点的计算生成,谁都有可能参与制造比特币,而且可以全世界流通,可以在任意一台接入互联网的电脑上买卖,不管身处何方,任何人都可以挖掘、购买、出售或收取比特币,并且在交易过程中外人无法辨认用户身份信息。2009年1月5日,不受央行和任何金融机构控制的比特币诞生。比特币是一种数字货币,由计算机生成的一串串复杂代码组成,新比特币通过预设的程序制造。 每当比特币进入主流媒体的视野时,主流媒体总会请一些主流经济学家分析一下比特币。早先,这些分析总是集中在比特币是不是骗局。而现如今的分析总是集中在比特币能否成为未来的主流货币。而这其中争论的焦点又往往集中在比特币的通缩特性上。 [7]  不少比特币玩家是被比特币的不能随意增发所吸引的。和比特币玩家的态度截然相反,经济学家们对比特币2100万固定总量的态度两极分化。 凯恩斯学派的经济学家们认为政府应该积极调控货币总量,用货币政策的松紧来为经济适时的加油或者刹车。因此,他们认为比特币固定总量货币牺牲了可调控性,而且更糟糕的是将不可避免地导致通货紧缩,进而伤害整体经济。奥地利学派经济学家们的观点却截然相反,他们认为政府对货币的干预越少越好,货币总量的固定导致的通缩并没什么大不了的,甚至是社会进步的标志。 比特币网络通过“挖矿”来生成新的比特币。所谓“挖矿”实质上是用计算机解决一项复杂的数学问题,来保证比特币网络分布式记账系统的一致性。比特币网络会自动调整数学问题的难度,让整个网络约每10分钟得到一个合格答案。随后比特币网络会新生成一定量的比特币作为区块奖励,奖励获得答案的人。 [6]  2009年,比特币诞生的时候,区块奖励是50个比特币。诞生10分钟后,第一批50个比特币生成了,而此时的货币总量就是50。随后比特币就以约每10分钟50个的速度增长。当总量达到1050万时(2100万的50%),区块奖励减半为25个。当总量达到1575万(新产出525万,即1050的50%)时,区块奖励再减半为12.5个。该货币系统曾在4年内只有不超过1050万个,之后的总数量将被永久限制在约2100万个。 [3]  [8]  比特币是一种虚拟货币,数量有限,但是可以用来套现:可以兑换成大多数国家的货币。你可以使用比特币购买一些虚拟的物品,比如网络游戏当中的衣服、帽子、装备等,只要有人接受,你也可以使用比特币购买现实生活当中的物品。 2014年2月25日,“比特币中国”的比特币开盘价格为3562.41元,截至下午4点40分,价格已下跌至3185元,跌幅逾10%。根据该平台的历史行情数据显示,在2014年1月27日,1比特币还能兑换5032元人民币。这意味着,该平台上不到一个月,比特币价格已下跌了36.7%。 同年9月9日,美国电商巨头eBay宣布,该公司旗下支付处理子公司Braintree将开始接受比特币支付。该公司已与比特币交易平台Coinbase达成合作,开始接受这种相对较新的支付手段。 虽然eBay市场交易平台和PayPal业务还不接受比特币支付,但旅行房屋租赁社区Airbnb和租车服务Uber等Braintree客户将可开始接受这种虚拟货币。Braintree的主要业务是面向企业提供支付处理软件,该公司在2013年被eBay以大约8亿美元的价格收购。 2017年1月22日晚间,火币网、比特币中国与OKCoin币行相继在各自官网发布公告称,为进一步抑制投机,防止价格剧烈波动,各平台将于1月24日中午12:00起开始收取交易服务费,服务费按成交金额的0.2%固定费率收取,且主动成交和被动成交费率一致。 [9]  5月5日,OKCoin币行网的新数据显示,比特币的价格刚刚再度刷新历史,截止发稿前高触及9222元人民币高位。1月24日中午12:00起,中国三大比特币平台正式开始收取交易费。9月4日,央行等七部委发公告称中国禁止虚拟货币交易。同年12月17日,比特币达到历史高价19850美元。 2018年11月25日,比特币跌破4000美元大关,后稳定在3000多美元。 [10]  11月19日,加密货币恢复跌势,比特币自2017年10月以来首次下探5000美元大关,原因是之前BCH出现硬分叉,且监管部门对首次代币发行(ICO)加强了审查。 [10]  11月21日凌晨4点半,coinbase平台比特币报价跌破4100美元,创下了13个月以来的新低。 2019年4月,比特币再次突破5000美元大关,创年内新高。 [11]  5月12日,比特币近八个月来首次突破7000美元。 [12]  5月14日,据coinmarketcap报价显示,比特币站上8000美元,24小时内上涨14.68%。 [13]  6月22日 ,比特币价格突破10000美元大关。比特币价格在10200左右震荡,24小时涨幅近7%。 [14]  6月26日,比特币价格一举突破12000美元,创下自去年1月来近17个月高点。 [15]  6月27日早间,比特币价格一度接近14000美元,再创年内新高。 [16]  2020年2月10日,比特币突破了一万美元。据交易数据,比特币的价格涨幅突破3% [17]  。3月12日,据加密货币交易平台Bitstamp数据显示,19点44分,比特币低价格已跌至5731美元 [18]  。5月8日,比特币突破10000美元关口,创下2月份以来的新高 [19]  。5月10日早上8点开始,比特币单价在半小时内从9500美元价位瞬间下跌了上千美元,低价格跌破8200美元,高价差超1400美元 [20]  。7月26日下午6点,比特币短时极速拉升,高触及10150.15USDT,日内大涨幅超过4%,这是2020年6月2日以来首次突破1万美元关口 [21]  。11月4日,比特币价格正式突破14000美元 [22]  。11月12日晚,比特币价格突破16000美元,刷新2018年1月以来新高,一周涨超8.6%。比特币总市值突破2915亿美元 [23]  。11月18日,比特币价格突破17000美元 [24]  。12月1日,比特币价格报19455.31美元,24小时涨幅为5.05%。 [25]  12月17日,比特币价格突破23000美元整数关口,刷新历史新高,日内涨幅超7.5%。 [26]  截至12月27日19时20分,比特币报价28273.06美元。 [27]  2021年1月8日,比特币涨至4万美元关口上方,高至40402美元

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值