btcd源碼解析——交易創建(2)—— input的創建

4.3. 創建input

前一篇博客btcd源碼解析——交易創建中的4.2節介紹了創建output的細節,本篇博客主要介紹input的創建細節。
由於已經擁有了outputinput的創建也就是整個transaction的創建了。因此在本節中,我們會不加區分地使用“創建input”和“創建交易”這兩個詞。換句話說,在本節中,創建交易的過程主要就是創建input的過程。

4.3.1. “創建交易”的請求

首先,我們先介紹一下用於“創建交易”的請求。
爲什麼這裏會生成一個請求,用來“創建交易”呢?

這是爲了保證交易創建的正確性。因爲在創建交易時,需要選擇合適的UTXO來作爲輸入。而UTXO只能被使用一次,這就要求創建交易必須是個串行的過程。也就是說:即使需要創建多筆交易,也必須一個一個地進行。
爲此,btcwallet啓動了一個txCreator協程專門用來創建交易,每次只接收一個“創建交易”的請求,完成當前請求後再處理下一個。

相關代碼如下所示。在walletstart函數中啓動了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,該請求通過walletchannel變量(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 (準確來說是該outputlock script)。該函數的實現邏輯也比較簡單,略過。
L153行基於已有的outputsinputSource函數,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行構造找零outputlock 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行),該函數是一個較爲複雜的函數,針對不同地址格式進行不同的簽名,留作下篇博客進行介紹。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章