文章目錄
4.3. 創建input
前一篇博客btcd源碼解析——交易創建中的4.2節介紹了創建output
的細節,本篇博客主要介紹input
的創建細節。
由於已經擁有了output
,input
的創建也就是整個transaction
的創建了。因此在本節中,我們會不加區分地使用“創建input
”和“創建交易”這兩個詞。換句話說,在本節中,創建交易的過程主要就是創建input
的過程。
4.3.1. “創建交易”的請求
首先,我們先介紹一下用於“創建交易”的請求。
爲什麼這裏會生成一個請求,用來“創建交易”呢?
這是爲了保證交易創建的正確性。因爲在創建交易時,需要選擇合適的
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
...
}
}
}
4.3.2. 生成“創建交易”的請求
回到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.3. txToOutputs函數
我們在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對以上生成的交易進行簽名。
以下對這幾個重要的函數進行介紹。
4.3.3.1. makeInputSource函數
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
時,即停止。
4.3.3.2. NewUnsignedTransaction函數
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
函數包裝一下返回。
4.3.3.3. AddAllInputScripts函數
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行),該函數是一個較爲複雜的函數,針對不同地址格式進行不同的簽名,留作下篇博客進行介紹。