前一篇博客btcd源码解析——交易创建中的4.2节介绍了创建output
的细节,本篇博客主要介绍input
的创建细节。
由于已经拥有了output
,input
的创建也就是整个transaction
的创建了。因此在本节中,我们会不加区分地使用“创建input
”和“创建交易”这两个词。换句话说,在本节中,创建交易的过程主要就是创建input
的过程。
首先,我们先介绍一下用于“创建交易”的请求。
为什么这里会生成一个请求,用来“创建交易”呢?
这是为了保证交易创建的正确性。因为在创建交易时,需要选择合适的
UTXO
来作为输入。而UTXO
只能被使用一次,这就要求创建交易必须是个串行的过程。也就是说:即使需要创建多笔交易,也必须一个一个地进行。
为此,btcwallet
启动了一个txCreator
协程专门用来创建交易,每次只接收一个“创建交易”的请求,完成当前请求后再处理下一个。
相关代码如下所示。在wallet
的start
函数中启动了txCreator
协程,该协程从createTxRequests
通道中获得请求,然后交由txToOutputs
函数处理。txToOutputs
函数是个比较复杂的函数,这里我们先不介绍,留到4.3.3.节介绍。
// Start [wallet.go]
func (w *Wallet) Start() {
...
go w.txCreator()
...
}
// Start [wallet.go] -> txCreator [wallet.go]
func (w *Wallet) txCreator() {
...
for {
select {
case txr := <-w.createTxRequests: // L1165
...
tx, err := w.txToOutputs(txr.outputs, txr.account, // L1171
txr.minconf, txr.feeSatPerKB, txr.dryRun)
...
txr.resp <- createTxResponse{tx, err} // L1174
...
}
}
}
回到sendPairs
函数,再次将函数主体摘录如下:
// 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
...
}
其中,L1381行调用SendOutputs
函数来创建完整的交易。SendOutputs
函数主体如下所示:
// sendToAddress [methods.go] -> sendPairs [methods.go] -> SendOutputs [wallet.go]
func (w *Wallet) SendOutputs(outputs []*wire.TxOut, account uint32, minconf int32,
satPerKb btcutil.Amount) (*wire.MsgTx, error) {
...
createdTx, err := w.CreateSimpleTx( // L3144
account, outputs, minconf, satPerKb, false,
)
...
}
L3144行调用CreateSimpleTx
函数生成“创建交易”的请求,并返回创建后的交易。CreateSimpleTx
函数的主题代码如下所示:
// sendToAddress [methods.go] -> sendPairs [methods.go] -> SendOutputs [wallet.go]
func (w *Wallet) CreateSimpleTx(account uint32, outputs []*wire.TxOut,
minconf int32, satPerKb btcutil.Amount, dryRun bool) (*txauthor.AuthoredTx, error) {
req := createTxRequest{ // L1195
account: account,
outputs: outputs,
minconf: minconf,
feeSatPerKB: satPerKb,
dryRun: dryRun,
resp: make(chan createTxResponse),
}
w.createTxRequests <- req // L1203
resp := <-req.resp // L1204
return resp.tx, resp.err
}
L1195行生成了“创建交易”的请求createTxRequest
,该请求通过wallet
的channel
变量(createTxRequests
)发送出去。回顾4.3.1节中txCreator
函数的L1165行代码,该行代码尝试从createTxRequests
变量中读取数据。
此外,createTxRequest
请求中包含了一个channel
变量(resp
),L1204行代码通过从该channel
中读取生成的“交易”返回值。回顾4.3.1节中txCreator
函数的L1174行代码,该行代码会将生成的“交易”返回值写入该channel
中。
我们在4.3.1.节的txCreator
函数中提及到了txToOutputs
函数 (即L1171行),该函数是真正“干实事”的,也即:真正生成input
并组装成完整的transaction
。本节介绍该函数的实现细节。
txToOutputs
函数的主体代码如下:
// Start [wallet.go] -> txCreator [wallet.go] -> txToOutputs [createtx.go]
func (w *Wallet) txToOutputs(outputs []*wire.TxOut, account uint32,
minconf int32, feeSatPerKb btcutil.Amount, dryRun bool) (tx *txauthor.AuthoredTx, err error) {
...
eligible, err := w.findEligibleOutputs(dbtx, account, minconf, bs) // L131
...
inputSource := makeInputSource(eligible) // L136
changeSource := func() ([]byte, error) { // L137
var changeAddr btcutil.Address
var err error
if account == waddrmgr.ImportedAddrAccount {
changeAddr, err = w.newChangeAddress(addrmgrNs, 0)
} else {
changeAddr, err = w.newChangeAddress(addrmgrNs, account)
}
return txscript.PayToAddrScript(changeAddr)
}
tx, err = txauthor.NewUnsignedTransaction(outputs, feeSatPerKb, // L153
inputSource, changeSource)
...
err = tx.AddAllInputScripts(secretSource{w.Manager, addrmgrNs}) // L174
...
return tx, nil
}
L131行代码调用findEligibleOutputs
函数从数据库中获取所有符合条件的output
集合(eligible
)。该函数的实现逻辑比较简单,略过。
L136行代码调用makeInputSource
函数,生成另一个函数inputSource
,该函数从eligible
集合中挑选出“数额总和满足指定大小”的outputs
,后面介绍。
L137行定义一个changeSource
函数,该函数用于生成“找零”的output
(准确来说是该output
的lock script
)。该函数的实现逻辑也比较简单,略过。
L153行基于已有的outputs
,inputSource
函数,changeSource
函数,生成“未签名的”交易。
L174对以上生成的交易进行签名。
以下对这几个重要的函数进行介绍。
makeInputSource
函数主体如下所示。
// Start [wallet.go] -> txCreator [wallet.go] -> txToOutputs [createtx.go] ->
// makeInputSource [createtx.go]
func makeInputSource(eligible []wtxmgr.Credit) txauthor.InputSource {
...
sort.Sort(sort.Reverse(byAmount(eligible))) // L33
...
return func(target btcutil.Amount) (btcutil.Amount, []*wire.TxIn,
[]btcutil.Amount, [][]byte, error) {
...
}
}
可见,其生成了一个InputSource
类型的函数。InputSource
类型也即func(target btcutil.Amount) (btcutil.Amount, []*wire.TxIn, []btcutil.Amount, [][]byte, error)
函数类型,该函数类型以一个目标数额(target
)为输入参数,输出为“数额总和大于target
”的output
集合(及统计值)。
此外,从L33行代码可知,在选择output
时,按照由大到小的顺序进行挨个选择。当数额总和大于等于target
时,即停止。
NewUnsignedTransaction
函数的主体如下所示:
// Start [wallet.go] -> txCreator [wallet.go] -> txToOutputs [createtx.go] ->
// NewUnsignedTransaction [author.go]
func NewUnsignedTransaction(outputs []*wire.TxOut, relayFeePerKb btcutil.Amount,
fetchInputs InputSource, fetchChange ChangeSource) (*AuthoredTx, error) {
targetAmount := SumOutputValues(outputs) // L89
estimatedSize := txsizes.EstimateVirtualSize(0, 1, 0, outputs, true) // L90
targetFee := txrules.FeeForSerializeSize(relayFeePerKb, estimatedSize) // L91
for { // L93
inputAmount, inputs, inputValues, scripts, err := fetchInputs(targetAmount + targetFee) // L94
...
maxSignedSize := txsizes.EstimateVirtualSize(p2pkh, p2wpkh, // L118
nested, outputs, true)
maxRequiredFee := txrules.FeeForSerializeSize(relayFeePerKb, maxSignedSize) // L120
remainingAmount := inputAmount - targetAmount // L121
if remainingAmount < maxRequiredFee { // L122
targetFee = maxRequiredFee // L123
continue // L124
}
unsignedTransaction := &wire.MsgTx{ // L127
Version: wire.TxVersion,
TxIn: inputs,
TxOut: outputs,
LockTime: 0,
}
...
changeScript, err := fetchChange() // L137
...
change := wire.NewTxOut(int64(changeAmount), changeScript) // L145
...
unsignedTransaction.TxOut = append(outputs[:l:l], change) // L147
...
return &AuthoredTx{ // L151
Tx: unsignedTransaction,
...
}, nil
}
该函数首先计算出目标数额(L89),然后估算出一个初步的交易大小(L90)和所需交易费(L91)。
在L93行使用一个for
循环,每一轮循环都尝试构造一次交易。之所以使用循环,是因为交易费是估算出来的,可能实际交易费会比估算值大,从而需要对交易进行构造。
L94行基于目标数额和目标交易费之和进行交易input
的选取。
L118至L120行基于选取出的input重新估算所需的交易费。需要注意的是,这里在估算时采用了“max”的原则(即估算最大所需交易费maxRequiredFee
),这是为了保证交易在发布广播之后不会因为交易费过小而无法被打包入块。
L121行至L124行判断当前选取的input
是否足够支付更新后的交易费。若不够,则将目标交易费更新为maxRequiredFee
(L123行),并进入下一轮for
循环(L124行)。
L127行构建出unsignedTransaction
.
L137行构造找零output
的lock script
,并在L145行构造完成的找零output
. L147行将该找零output
加入到unsignedTransaction
中。
最后在L151行将unsignedTransaction
函数包装一下返回。
AddAllInputScripts
函数是对AddAllInputScripts
函数的封装,AddAllInputScripts
的函数主体如下所示:
// Start [wallet.go] -> txCreator [wallet.go] -> txToOutputs [createtx.go] ->
// AddAllInputScripts [author.go] -> AddAllInputScripts
func AddAllInputScripts(tx *wire.MsgTx, prevPkScripts [][]byte, inputValues []btcutil.Amount,
secrets SecretsSource) error {
...
inputs := tx.TxIn
...
for i := range inputs {
pkScript := prevPkScripts[i]
switch {
...
case txscript.IsPayToScriptHash(pkScript):
...
case txscript.IsPayToWitnessPubKeyHash(pkScript):
...
default:
sigScript := inputs[i].SignatureScript
script, err := txscript.SignTxOutput(chainParams, tx, i, // L234
pkScript, txscript.SigHashAll, secrets, secrets,
sigScript)
...
inputs[i].SignatureScript = script
}
}
}
AddAllInputScripts
函数的逻辑比较清晰,就是针对每一个input
进行解锁脚本(SignatureScript
)的构建。
input
对应的锁定脚本可能出现三种情形: IsPayToScriptHash
, IsPayToWitnessPubKeyHash
和其他情况(default
)。前两种情形涉及到隔离见证的相关知识,较为复杂。这里我们以“其他情况”为例进行讲解。
其他情况下主要是调用了SignTxOutput
函数(L234行),该函数是一个较为复杂的函数,针对不同的地址格式进行不同的签名,留作下篇博客进行介绍。