比特幣交易源代碼分析

關于交易部分可以先閱讀《精通比特幣》第五章
本文內容參考自https://blog.csdn.net/g2com/article/details/64386251
對于初次分析比特幣源代碼,建議先閱讀最原始版本的比特幣源代碼original-bitcoin。此版本源代碼比較簡單,可以幫助快速理解比特幣各個階段的工作流程及原理。

1.SendMoney()

當比特幣客戶端向某個地址發送比特幣時,便會調用該函數。函數位于'src/main.cpp'第2625行。

bool SendMoney(CScript scriptPubKey, int64 nValue, CWalletTx& wtxNew)
{
    CRITICAL_BLOCK(cs_main)
    {
        int64 nFeeRequired;
        if (!CreateTransaction(scriptPubKey, nValue, wtxNew, nFeeRequired))
        {
            strig strError;
            if (nValue + nFeeRequired > GetBalance())
                strError = strprintf("Error: This is an oversized transaction that requires a transaction fee of %s ", FormatMoney(nFeeRequired).c_str());
            else
                strError = "Error: Transaction creation failed ";
            wxMessageBox(strError, "Sending...");
            return error("SendMoney() : %s\n", strError.c_str());
        }
        if (!CommitTransactionSpent(wtxNew))
        {
            wxMessageBox("Error finalizing transaction", "Sending...");
            return error("SendMoney() : Error finalizing transaction");
        }

        printf("SendMoney: %s\n", wtxNew.GetHash().ToString().substr(0,6).c_str());

        // Broadcast
        if (!wtxNew.AcceptTransaction())
        {
            // This must not fail. The transaction has already been signed and recorded.
            throw runtime_error("SendMoney() : wtxNew.AcceptTransaction() failed\n");
            wxMessageBox("Error: Transaction not valid", "Sending...");
            return error("SendMoney() : Error: Transaction not valid");
        }
        wtxNew.RelayWalletTransaction();
    }
    MainFrameRepaint();
    return true;
}

該方法包含三個參數:

  • scriptPubKey為收款人公鑰鎖定腳本,關于鎖定腳本和解鎖腳本將會在下章做分析。
  • nValue表示將要轉賬的金額。該金額并未包含交易費nTrasactionFee。
  • wtxNew是一個CWalletTx類的本地變量。該變量目前的值為空,之后會包含若干CMerkleTX類對象。該類由CTransaction衍生而來,并且添加了若干方法。我們暫時先不管具體細節,僅將其看作CTransaction類。

SendMoney()首先調用了CreateTransaction()函數,這個函數作用便是構造一筆新的交易,也是本文重點分析的函數。該函數源代碼如下:

bool CreateTransaction(CScript scriptPubKey, int64 nValue, CWalletTx& wtxNew, int64& nFeeRequiredRet)
{
    nFeeRequiredRet = 0;
    CRITICAL_BLOCK(cs_main)
    {
        // txdb must be opened before the mapWallet lock
        CTxDB txdb("r");
        CRITICAL_BLOCK(cs_mapWallet)
        {
            int64 nFee = nTransactionFee;
            loop
            {
                wtxNew.vin.clear();
                wtxNew.vout.clear();
                if (nValue < 0)
                    return false;
                int64 nValueOut = nValue;
                nValue += nFee;

                // Choose coins to use
                set<CWalletTx*> setCoins;
                if (!SelectCoins(nValue, setCoins))
                    return false;
                int64 nValueIn = 0;
                foreach(CWalletTx* pcoin, setCoins)
                    nValueIn += pcoin->GetCredit();

                // Fill vout[0] to the payee
                wtxNew.vout.push_back(CTxOut(nValueOut, scriptPubKey));

                // Fill vout[1] back to self with any change
                if (nValueIn > nValue)
                {
                    // Use the same key as one of the coins
                    vector<unsigned char> vchPubKey;
                    CTransaction& txFirst = *(*setCoins.begin());
                    foreach(const CTxOut& txout, txFirst.vout)
                        if (txout.IsMine())
                            if (ExtractPubKey(txout.scriptPubKey, true, vchPubKey))
                                break;
                    if (vchPubKey.empty())
                        return false;

                    // Fill vout[1] to ourself
                    CScript scriptPubKey;
                    scriptPubKey << vchPubKey << OP_CHECKSIG;
                    wtxNew.vout.push_back(CTxOut(nValueIn - nValue, scriptPubKey));
                }

                // Fill vin
                foreach(CWalletTx* pcoin, setCoins)
                    for (int nOut = 0; nOut < pcoin->vout.size(); nOut++)
                        if (pcoin->vout[nOut].IsMine())
                            wtxNew.vin.push_back(CTxIn(pcoin->GetHash(), nOut));

                // Sign
                int nIn = 0;
                foreach(CWalletTx* pcoin, setCoins)
                    for (int nOut = 0; nOut < pcoin->vout.size(); nOut++)
                        if (pcoin->vout[nOut].IsMine())
                            SignSignature(*pcoin, wtxNew, nIn++);

                // Check that enough fee is included
                if (nFee < wtxNew.GetMinFee(true))
                {
                    nFee = nFeeRequiredRet = wtxNew.GetMinFee(true);
                    continue;
                }

                // Fill vtxPrev by copying from previous transactions vtxPrev
                wtxNew.AddSupportingTransactions(txdb);
                wtxNew.fTimeReceivedIsTxTime = true;

                break;
            }
        }
    }
    return true;
}

調用該方法時,它所需要的四個參數如下:

  • scriptPubKey即腳本代碼
  • nValue是將要轉賬的數額,交易費nTransactionFee并未包括在內。
  • wtxNew是一個新的Tx實例。
  • nFeeRequiredRet是一筆用來支付交易費的輸出交易,在該方法執行完成之后獲得。

函數首先對實例wtxNew初始化,隨后計算總共費用nValue=轉賬金額+交易費,調用 SelectCoin()尋找合適的交易輸入。

實際上,并不存在儲存比特幣地址或賬戶余額的地點,只有被所有者鎖住的、分散的UTXO。“一個用戶的比特幣余額”,這個概念是一個通過比特幣錢包應用創建的派生之物。比特幣錢包通過掃描區塊鏈并聚合所有屬于該用戶的UTXO來計算該用戶的余額。

bool SelectCoins(int64 nTargetValue, set<CWalletTx*>& setCoinsRet)
{
    setCoinsRet.clear();

    // List of values less than target
    int64 nLowestLarger = _I64_MAX;
    CWalletTx* pcoinLowestLarger = NULL;
    vector<pair<int64, CWalletTx*> > vValue;
    int64 nTotalLower = 0;
    ...
}

我們知道比特幣是基于UTXO模型的,所以SelectCoin便負責從所有屬于該用戶的UTXO中找到一組符合轉賬金額的輸入。具體的尋找算法此處便不具體分析。
在得到一組輸入之后會計算所有輸入的總金額nValueIn,一般輸入總金額是大于轉賬金額的,所以后面會構造一筆轉給自己地址的輸出,用于找零。
隨后調用wtxNew.vout.push_back(CTxOut(nValueOut, scriptPubKey))構造第一筆輸出,指向該筆交易的轉賬地址。關于CTxIn和CTxOut的數據結構可以參考https://blog.csdn.net/pure_lady/article/details/77771392

如果需要找零(nValueIn > nValue),添加另一筆輸出交易至wtxNew并將零錢發回本人。該過程包含以下步驟:
從setCoin當中獲取第一筆交易txFirst,依次檢查txFirst.vout中的輸出是否屬于本人。如果是則從該筆輸出交易當中提取出公鑰ExtractPubKey,并放入本地變量vchPubKey
將vchPubKey放入腳本vchPubKey OP_CHECKSIG,并使用這段腳本代碼為wtxNew添加一個支付給本人的輸出交易。
因為setCoins包含支付給本人的交易,所以每筆交易一定包括至少一筆支付給本人的交易。從第一筆交易txFirst中即可找到。

至此,wtxNew的輸出交易容器vout已準備就緒。現在,該設置輸入交易容器vin。記住每一個輸入交易列表vin均引用一筆來源交易,而且wtxNew的每筆來源交易均可在setCoins中被找到。對于每一筆setCoins中的交易pcoin,逐個遍歷其輸出交易pcoin->vout[nOut]。如果第nOut筆輸出支付給本人(意味著wtxNew從該筆輸出交易中獲得幣),則向wtxNew添加一筆新的輸入交易(wtxNew.vin(wtxNew.vin.push_back(CTxIn(pcoin->GetHash(), nOut)),第51行)。該輸入交易指向pcoin中的第nOut筆輸出交易,由此將wtxNew.vin與pcoin的第nOut筆輸出相連接。
對于setCoins當中的每筆交易pcoin,逐個遍歷其所有輸出交易pcoin->vout[nOut]。如果該筆交易屬于本人,調用SignSignature(*pcoin,wtxNew, nIn++)為第nIn筆輸入交易添加簽名。注意nIn為wtxNew的輸入交易位置。

對于交易簽名函數SignSignature,以下為源代碼:

bool SignSignature(const CTransaction& txFrom, CTransaction& txTo, unsigned int nIn, int nHashType, CScript scriptPrereq)
{
    assert(nIn < txTo.vin.size());
    CTxIn& txin = txTo.vin[nIn];
    assert(txin.prevout.n < txFrom.vout.size());
    const CTxOut& txout = txFrom.vout[txin.prevout.n];

    // Leave out the signature from the hash, since a signature can't sign itself.
    // The checksig op will also drop the signatures from its hash.
    uint256 hash = SignatureHash(scriptPrereq + txout.scriptPubKey, txTo, nIn, nHashType);

    if (!Solver(txout.scriptPubKey, hash, nHashType, txin.scriptSig))
        return false;

    txin.scriptSig = scriptPrereq + txin.scriptSig;

    // Test solution
    if (scriptPrereq.empty())
        if (!EvalScript(txin.scriptSig + CScript(OP_CODESEPARATOR) + txout.scriptPubKey, txTo, nIn))
            return false;

    return true;
}

首先需要注意的是,該函數有5個參數,而CreateTransaction()只有3個。這是因為在script.h文件里,后兩個參數已默認給出。

SignSignature(*pcoin, wtxNew, nIn++)

  • txFrom是一個*pcoin對象。即我們前面找到的setCoins中的每一個。
  • txTo是CreateTransaction()里的wtxNew對象。它是將要花費來源交易txFrom的新交易。新交易需要被簽署方可生效。
  • nIn是指向txTo中輸入交易列表的索引位置。該輸入交易列表包含一個對txFrom的輸出交易列表的引用。更準確地講,txin=txTo.vin[nIn](第4行)是txTo中的輸入交易;txout=txFrom.vout[txin.prev.out.n](第6行)是txin所指向的txFrom中的輸出交易。

以下是SignSignature()所做的工作:

  • 根據索引位置找到對應的輸入輸出交易。
  • 調用SignatureHash()方法生成txTo的哈希值。
  • 調用Solver()函數簽署剛才生成的哈希。
  • 調用EvalScript()來運行一小段腳本并檢查簽名是否合法。

下面分別介紹這幾個函數:

SignatureHash()

uint256 SignatureHash(CScript scriptCode, const CTransaction& txTo, unsigned int nIn, int nHashType)
{
    if (nIn >= txTo.vin.size())
    {
        printf("ERROR: SignatureHash() : nIn=%d out of range\n", nIn);
        return 1;
    }
    CTransaction txTmp(txTo);

    // In case concatenating two scripts ends up with two codeseparators,
    // or an extra one at the end, this prevents all those possible incompatibilities.
    scriptCode.FindAndDelete(CScript(OP_CODESEPARATOR));

    // Blank out other inputs' signatures
    for (int i = 0; i < txTmp.vin.size(); i++)
        txTmp.vin[i].scriptSig = CScript();
    txTmp.vin[nIn].scriptSig = scriptCode;

    // Blank out some of the outputs
    if ((nHashType & 0x1f) == SIGHASH_NONE)
    {
        // Wildcard payee
        txTmp.vout.clear();

        // Let the others update at will
        for (int i = 0; i < txTmp.vin.size(); i++)
            if (i != nIn)
                txTmp.vin[i].nSequence = 0;
    }
    else if ((nHashType & 0x1f) == SIGHASH_SINGLE)
    {
        // Only lockin the txout payee at same index as txin
        unsigned int nOut = nIn;
        if (nOut >= txTmp.vout.size())
        {
            printf("ERROR: SignatureHash() : nOut=%d out of range\n", nOut);
            return 1;
        }
        txTmp.vout.resize(nOut+1);
        for (int i = 0; i < nOut; i++)
            txTmp.vout[i].SetNull();

        // Let the others update at will
        for (int i = 0; i < txTmp.vin.size(); i++)
            if (i != nIn)
                txTmp.vin[i].nSequence = 0;
    }

    // Blank out other inputs completely, not recommended for open transactions
    if (nHashType & SIGHASH_ANYONECANPAY)
    {
        txTmp.vin[0] = txTmp.vin[nIn];
        txTmp.vin.resize(1);
    }

    // Serialize and hash
    CDataStream ss(SER_GETHASH);
    ss.reserve(10000);
    ss << txTmp << nHashType;
    return Hash(ss.begin(), ss.end());
}

SignatureHash(scriptPrereq + txout.scriptPubKey, txTo, nIn, nHashType);

以下是該函數所需要的參數:

  • txTo是將要被簽署的交易。它同時也是CreateTransaction()中的wtxNew對象。它的輸入交易列表中的第nIn項,txTo.vin[nIn],是該函數將要起作用的目標。
  • scriptCode是scriptPrereq + txout.scriptPubKey,其中txout是SignSignature()中定義的來源交易txFrom()的輸出交易。由于此時scriptPrereq為空,scriptCode事實上是來源交易txFrom中的輸出交易列表當中被txTo作為輸入交易引用的那筆的腳本代碼。txout.scriptPubKey有可能包含兩類腳本:
    腳本A:OP_DUP OP_HASH160 <你地址的160位哈希> OP_EQUALVERIFY OP_CECKSIG。該腳本將來源交易txFrom中的幣發送給你,其中<你地址的160位哈希>是你的比特幣地址。
    腳本B:<你的公鑰> OP_CHECKSIG。該腳本將剩余的幣退還至來源交易txFrom的發起人。由于你創建的新交易txTo/wtxNew將會花費來自txFrom的幣,你必須同時也是txFrom的創建者。換句話講,當你在創建txFrom的時候,你其實是在花費之前別人發送給你的幣。因此,<你的公鑰>即是txFrom創建者的公鑰,也是你自己的公鑰。

在了解了輸入交易之后,我們來一起了解SignatureHash()是怎樣工作的。

SignatureHash()首先將txTO拷貝至txTmp,接著清空txTmp.vin中每一筆輸入交易的scriptSig,除了txTmp.vin[nIn]之外,該輸入交易的scriptSig被設為scriptCode(第14、15行)。

接著,該函數檢驗nHashType的值。根據不同的nHAshType選擇不同的置空操作。

  • SIGHASH_ALL是默認選項,具體流程是把所有的TxOut都納入臨時Tx中用來生成被簽署的交易,相當于針對這個TxIn,這個交易中的所有的TxOut都已經被這個TxIn承認,不可改
  • SIGHASH_NONE,具體流程是把所有的TxOut都置空,相當于針對這個TxIn,不關心這個交易的TxOut是什么情況,即使被替換了也是可以的
  • SIGHASH_SINGLE,具體流程是只保留和自已同樣index的out,其他的out都置空,表示只關心和自己同樣index的out,其他的out不關心。比如當前的txin是這個交易的第3個in(index=2),那么這個交易的第3個out保留,其他的out都置空。
  • SIGHASH_ANYONECANPAY比較特殊,他是獨立的??梢院土?個標志取并集。它表示簽署這個TxIn的時候我連其他的TxIn都不關心,可以和前面3個并存。

在最后4行代碼中,txTmp和nHashType變成序列化后的類型CDataStream對象。該類型包括一個裝有數據的字符容器類型。所返回的哈希值是Hash()方法在計算序列化后的數據所得到的。

到這里我們便生成了txOut的哈希值hash,接下來會調用Solver()函數簽署剛才生成hash。

Solver()

Solver(txout.scriptPubKey, hash, nHashType, txin.scriptSig)
其源代碼如下:

bool Solver(const CScript& scriptPubKey, uint256 hash, int nHashType, CScript& scriptSigRet)
{
    scriptSigRet.clear();

    vector<pair<opcodetype, valtype> > vSolution;
    if (!Solver(scriptPubKey, vSolution))
        return false;

    // Compile solution
    CRITICAL_BLOCK(cs_mapKeys)
    {
        foreach(PAIRTYPE(opcodetype, valtype)& item, vSolution)
        {
            if (item.first == OP_PUBKEY)
            {
                // Sign
                const valtype& vchPubKey = item.second;
                if (!mapKeys.count(vchPubKey))
                    return false;
                if (hash != 0)
                {
                    vector<unsigned char> vchSig;
                    if (!CKey::Sign(mapKeys[vchPubKey], hash, vchSig))
                        return false;
                    vchSig.push_back((unsigned char)nHashType);
                    scriptSigRet << vchSig;
                }
            }
            else if (item.first == OP_PUBKEYHASH)
            {
                // Sign and give pubkey
                map<uint160, valtype>::iterator mi = mapPubKeys.find(uint160(item.second));
                if (mi == mapPubKeys.end())
                    return false;
                const vector<unsigned char>& vchPubKey = (*mi).second;
                if (!mapKeys.count(vchPubKey))
                    return false;
                if (hash != 0)
                {
                    vector<unsigned char> vchSig;
                    if (!CKey::Sign(mapKeys[vchPubKey], hash, vchSig))
                        return false;
                    vchSig.push_back((unsigned char)nHashType);
                    scriptSigRet << vchSig << vchPubKey;
                }
            }
        }
    }

    return true;
}

以下是該方法所需要的4個參數:

  • 位于第10行的調用函數SignSignature()將txOut.scriptPubKey,來源交易txFrom的輸出腳本,作為輸入值傳入第一個參數scriptPubKey。記住它可能包含腳本A或者腳本B。
  • 第二個參數hash是由SignatureHash()生成的哈希值。
  • 第三個參數nHashType的值默為SIGHASH_ALL。其余三種值見上一個函數的解釋。
  • 第四個參數是該函數的返回值,即調用函數SignSIgnature()中位于第12行的txin.scriptSig。記住txin是新生成的交易wtxNew(在調用函數SignSignature()中作為txTo引用)位于第nIn的輸入交易。因此,wtxNew第nIn筆輸入交易的scriptSig將存放該函數返回的簽名。

該函數首先將scriptSigRet清空,隨后調用Solver(scriptPubKey, vSolution),此Solver函數有兩個輸入。其源代碼為:

bool Solver(const CScript& scriptPubKey, vector<pair<opcodetype, valtype> >& vSolutionRet)
{
    // Templates
    static vector<CScript> vTemplates;
    if (vTemplates.empty())
    {
        // Standard tx, sender provides pubkey, receiver adds signature
        vTemplates.push_back(CScript() << OP_PUBKEY << OP_CHECKSIG);

        // Short account number tx, sender provides hash of pubkey, receiver provides signature and pubkey
        vTemplates.push_back(CScript() << OP_DUP << OP_HASH160 << OP_PUBKEYHASH << OP_EQUALVERIFY << OP_CHECKSIG);
    }

    // Scan templates
    const CScript& script1 = scriptPubKey;
    foreach(const CScript& script2, vTemplates)
    {
        vSolutionRet.clear();
        opcodetype opcode1, opcode2;
        vector<unsigned char> vch1, vch2;

        // Compare
        CScript::const_iterator pc1 = script1.begin();
        CScript::const_iterator pc2 = script2.begin();
        loop
        {
            bool f1 = script1.GetOp(pc1, opcode1, vch1);
            bool f2 = script2.GetOp(pc2, opcode2, vch2);
            if (!f1 && !f2)
            {
                // Success
                reverse(vSolutionRet.begin(), vSolutionRet.end());
                return true;
            }
            else if (f1 != f2)
            {
                break;
            }
            else if (opcode2 == OP_PUBKEY)
            {
                if (vch1.size() <= sizeof(uint256))
                    break;
                vSolutionRet.push_back(make_pair(opcode2, vch1));
            }
            else if (opcode2 == OP_PUBKEYHASH)
            {
                if (vch1.size() != sizeof(uint160))
                    break;
                vSolutionRet.push_back(make_pair(opcode2, vch1));
            }
            else if (opcode1 != opcode2)
            {
                break;
            }
        }
    }

    vSolutionRet.clear();
    return false;
}

該函數的作用是將scriptPubKey與兩個模板相比較:

如果輸入腳本為腳本A,則將模板A中的OP_PUBKEYHASH與腳本A中的<你的地址160位哈希>配對,并將該對放入vSolutionRet。
如果輸入腳本為腳本B,則從模板B中提取運算符OP_PUBKEY,和從腳本B中提取運算元<你的公鑰>,將二者配對并放入vSolutionRet。
如果輸入腳本與兩個模板均不匹配,則返回false。

回到有4個參數的Solver()并繼續對該函數的分析。現在我們清楚了該函數的工作原理。它會在兩個分支中選擇一個執行,取決于從vSolutionRet得到的對來自腳本A還是腳本B。如果來自腳本A,item.first == OP_PUBKEYHASH;如果來自腳本B,item.first == OP_PUBKEY。

  • item.first == OP_PUBKEY(腳本B)。在該情形下,item.second包含<你的公鑰>。全局變量mapKeys將你的全部公鑰映射至與之對應的私鑰。如果mapKeys當中沒有該公鑰,則報錯(第16行)。否則,用從mapKeys中提取出的私鑰簽署新生成的交易wtxNew的哈希值,其中哈希值作為第2個被傳入的參數(CKey::Sign(mapKeys[vchPubKey], hash, vchSig),第23行),再將結果放入vchSig,接著將其序列化成scriptSigRet(scriptSigRet << vchSig,第24行)并返回。
  • item.first == OP_PUBKEYHASH(腳本A)。在該情形下,item.second包含<你的地址160位哈希>。該比特幣地址將被用于從位于第23行的全局映射mapPubKeys中找到其所對應的公鑰。全局映射mapPubKeys將你的地址與生成它們的公鑰建立一一對應關系(查看函數AddKey())。接著,通過該公鑰從mapKeys中找到所對應的私鑰,并用該私鑰簽署第二個參數hash。簽名和公鑰將一同被序列化至scriptSigRet并返回(scriptSig << vchSig << vchPubkey,第24行)

EvalScript()

最后將調用EvalScript()來運行一小段腳本并檢查簽名是否合法。
EvalScript(txin.scriptSig + CScript(OP_CODESEPARATOR) + txout.scriptPubKey, txTo, nIn)
其源代碼如下:

bool EvalScript(const CScript& script, const CTransaction& txTo, unsigned int nIn, int nHashType,
                vector<vector<unsigned char> >* pvStackRet)
{
    CAutoBN_CTX pctx;
    CScript::const_iterator pc = script.begin();
    CScript::const_iterator pend = script.end();
    CScript::const_iterator pbegincodehash = script.begin();
    vector<bool> vfExec;
    vector<valtype> stack;
    vector<valtype> altstack;
    if (pvStackRet)
        pvStackRet->clear();


    while (pc < pend)
    {
        bool fExec = !count(vfExec.begin(), vfExec.end(), false);
    ...
    }
    if (pvStackRet)
        *pvStackRet = stack;
    return (stack.empty() ? false : CastToBool(stack.back()));
}

EvalScript()帶有3個參數,分別為:

  • 第一個參數為txin.scriptSig + CScript(OP_CODESEPARATOR) + txout.scriptPubKey。它有可能是:
    驗證情形A:<你的簽名_vchSig> <你的公鑰_vchPubKey> OP_CODESEPARATOR OP_DUP OP_HASH160 <你的地址160位哈希> OP_EQUALVERIFY OP_CHECKSIG,即簽名A + OP_CODESEPARATOR + 腳本A。
    驗證情形B:<你的簽名_vchSig> OP_CODESEPARATOR <你的公鑰_vchPubKey> OP_CHECKSIG,即簽名B + OP_CODESEPARATOR + 腳本B。
  • 第二個參數為新創建的交易txTo,即CreateTransaction()中的wtxNew。
  • 第三個參數為nIn,即將被驗證的交易在txTo輸入交易列表中的位置。

該函數將根據你輸入的腳本,依次取出腳本中的操作代碼進行相應操作,并對最后的模擬執行結果做判斷,返回執行結果。如果結果為true,則完成了SignSignature(),此時便生成了一筆新的交易。

回到SendMoney()

生成了一筆新的交易后,利用函數CommitTransactionSpent(wtxNet)嘗試將這筆交易提交至數據庫,之后判斷交易是否提交成功,如果該筆交易提交成功wtxNew.AcceptTransaction()=true,將這筆交易廣播至其他peer節點wtxNew.RelayWalletTransaction()。

當礦工收到這筆交易的廣播之后會對交易進行相應操作,之后的章節我們將對新區塊的處理部分做詳細分析。

總結

比特幣生成一筆新的交易大致分為如下幾個階段:

  • 根據轉賬金額以及交易費用從UTXO中尋找一組滿足條件的輸入。
  • 對于這組輸入以及轉賬地址構造一個新的交易:輸入分別對應UTXO中的不同輸出,第一個輸出指向轉賬地址,如果有找零,則計算找零金額,將其放入第二個輸出,同時指向自身的錢包地址。
  • 對構造好的輸入輸出分別進行簽名。
  • 利用Solver函數對簽名完的交易做模擬運行,如果運行通過則生成完畢。
  • 將交易提交至數據庫并廣播到其他節點。
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容