比特币交易源码研读笔记(一)

比特币每一笔交易可以包含一个或多个输入和一个或多个输出。先看看一笔交易是如何创建的。创建交易的行为在钱包中发生。可以在比特源码中的 wallet.cpp 的 CreateTransaction 函数中看到具体实现。本文只记录了交易输出的创建过程。比特币交易的类型有几种。为了方便起见,这里假定交易的类型是 P2PKH。

P2PKH

支付到比特币地址的交易包含支付公钥哈希脚本(P2PKH)。由P2PKH脚本锁定的交易输出可以通过给出由相应
私钥创建的公钥和数字签名来解锁(消费)。

其交易模型是这样的

比特币交易源码研读笔记(一)_第1张图片
image
bool CWallet::CreateTransaction(const std::vector& vecSend, CWalletTx& wtxNew, CReserveKey& reservekey, CAmount& nFeeRet,
int& nChangePosInOut, std::string& strFailReason, const CCoinControl& coin_control, bool sign) 

...

    for (const auto& recipient : vecSend)
    {
        if (nValue < 0 || recipient.nAmount < 0)
        {
            strFailReason = _("Transaction amounts must not be negative");
            return false;
        }
        nValue += recipient.nAmount;

        if (recipient.fSubtractFeeFromAmount)
            nSubtractFeeFromAmount++;
    }
    if (vecSend.empty())
    {
        strFailReason = _("Transaction must have at least one recipient");
        return false;
    }
...
    
   /*
    recipient 就是比特的接收方
        struct CRecipient
        {
            CScript scriptPubKey;  // 锁定脚本
            CAmount nAmount;       // 比特币数量    
            bool fSubtractFeeFromAmount; //字面上是 “从金额中扣除费用”。
        };
        
        要求满足以下条件才能交易的创建工作才能继续往前
        1.接收方的比特币数目不能为负数
        2.接收方的人数要大于1
        
   */
   
...

    CMutableTransaction txNew;
    txNew.nLockTime = chainActive.Height();
    
    /*
      针对费用狙击的时间锁定。《精通比特币》中有详细的介绍。
    */
...


    FeeCalculation feeCalc;
    CAmount nFeeNeeded;   //手续费
    unsigned int nBytes;
    
...

    std::vector vAvailableCoins;
    AvailableCoins(vAvailableCoins, true, &coin_control);//从钱包中找到可花费的比特币
    
...
    
    if (!boost::get(&coin_control.destChange)) {//???
            scriptChange = GetScriptForDestination(coin_control.destChange);
        } else { 
        // Reserve a new key pair from key pool
        CPubKey vchPubKey; //包装的公钥
        bool ret;
        ret = reservekey.GetReservedKey(vchPubKey, true);
        if (!ret)
            {
                strFailReason = _("Keypool ran out, please call keypoolrefill first");
                return false;
            }   
                scriptChange = GetScriptForDestination(vchPubKey.GetID()); //用于找零
    }
    
    /*
      跟进 GetScriptForDestination,如果是P2PKH 类型的交易, 会发现创建了一个锁定脚本
      
     bool operator()(const CKeyID &keyID) const {
        script->clear();
        *script << OP_DUP << OP_HASH160 << ToByteVector(keyID) << OP_EQUALVERIFY << OP_CHECKSIG;
        return true;
    }
    */
    
...

 // Start with no fee and loop until there is enough fee
 // 计算交易费
    while (true)
        {
        ...
                // vouts to the payees
                for (const auto& recipient : vecSend)
                {
                    CTxOut txout(recipient.nAmount, recipient.scriptPubKey);//构建输出

                    if (recipient.fSubtractFeeFromAmount)
                    {
                        ...
                        
                        txout.nValue -= nFeeRet / nSubtractFeeFromAmount; // Subtract fee equally from each selected recipient
                        // 从接收方的接收比特币数量减去手续费?有点像快递到付
                        ...
                    }

                    if (IsDust(txout, ::dustRelayFee))     //比特币数量太小,不能创建交易输出。
                    {
                        if (recipient.fSubtractFeeFromAmount && nFeeRet > 0)
                        {
                            if (txout.nValue < 0)
                                strFailReason = _("The transaction amount is too small to pay the fee");
                            else
                                strFailReason = _("The transaction amount is too small to send after the fee has been deducted");
                        }
                        else
                            strFailReason = _("Transaction amount too small");
                        return false;
                    }
                    txNew.vout.push_back(txout);//把创建的交易输出放进交交易中
                }   

                // Choose coins to use
                if (pick_new_inputs) {  // true
                    ...
                    //找出做够的比特币用来支付,比特币保存在 setcoins 中
                    if (!SelectCoins(vAvailableCoins, nValueToSelect, setCoins, nValueIn, &coin_control))
                    {
                        strFailReason = _("Insufficient funds");
                        return false;
                    }
                }
                ...

                const CAmount nChange = nValueIn - nValueToSelect;//支付给接收方后多出来的比特币
                
                ...

                if (nChange > 0)
                {
                    // Fill a vout to ourself
                    CTxOut newTxOut(nChange, scriptChange);//--找零  scriptChange 是发送方的

                    // Never create dust outputs; if we would, just 
                    // add the dust to the fee.
                    //nChange 如果很小直接用作手续费
                    if (IsDust(newTxOut, discard_rate))
                    {
                        nChangePosInOut = -1;
                        nFeeRet += nChange;
                    }
                    else
                    {
                        if (nChangePosInOut == -1)
                        {
                            // Insert change txn at random position:
                            nChangePosInOut = GetRandInt(txNew.vout.size()+1);
                        }
                        else if ((unsigned int)nChangePosInOut > txNew.vout.size())
                        {
                            strFailReason = _("Change index out of range");
                            return false;
                        }

                        std::vector::iterator position = txNew.vout.begin()+nChangePosInOut;
                        txNew.vout.insert(position, newTxOut);  // 把找零输出加入交易
                    }
                } else {
                    nChangePosInOut = -1; 
                }
                //构建交易的输出完毕。
    
    
...    
    

总结

  1. 判断接收方的合法性,合法继续,不合法则终止创建交易。
  2. 从钱包中找到可用的比特币(UTXO),从可用的比特币中选出足够的支付的比特币,如果不足以支付,终止创建交易。
  3. 计算手续费,找零。

备注

本文只记录了比特币交易中交易输出的过程,省略了很多代码(主要是看不太懂:)),肯定有理解错误的地方,欢迎指正。

作者:区块链研习社比特币源码研读班 wwgz

你可能感兴趣的:(比特币交易源码研读笔记(一))