当前位置: 首页 > 工具软件 > btcd > 使用案例 >

btcd源码解析——交易创建(2)—— input的创建

井洲
2023-12-01

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行),该函数是一个较为复杂的函数,针对不同的地址格式进行不同的签名,留作下篇博客进行介绍。

 类似资料: