bitcoin源码分析 – 交易验证

Transaction — Verify

交易验证是bitcoin的核心逻辑之一。交易验证包含两个问题: (1)验证的时机 (2)如何验证。

how

when and where

交易

当交易即将放如交易内存池中时, 将进行交易的验证。

调用时机:

  • init(即程序启动时): LoadMempool

  • 交易消息: NetMsgType::TX

  • Wallet: (1)CommitTransaction (2)ReacceptWalletTransactions (3)ResendWalletTransactionsBefore

  • UpdateMempoolForReorg

AcceptToMemoryPool函数调用栈如下:

AcceptToMemoryPool:

    AcceptToMemoryPoolWithTime
        AcceptToMemoryPoolWorker
            CheckInputs
                VerifyScript

其中AcceptToMemoryPoolWorker交易验证最核心的函数。

区块

(1)当ConnectBlock(新块产生或收到新块)时, 会对区块包含的所有交易进行验证。

  • init: CVerifyDB::VerifyDB(需checklevel4,默认为3,故默认不执行)

  • TestBlockValidity(miner.cpp里BlockAssembler::CreateNewBlock调用)

  • CChainState::ConnectTip

部分代码如下所示:

CChainState::ConnectBlock:

for (unsigned int i = 0; i < block.vtx.size(); i++)
{
    const CTransaction &tx = *(block.vtx[i]);
    ...
    std::vector<CScriptCheck> vChecks;
    if (!CheckInputs(... &vChecks...))
            return error...
    control.Add(vChecks);

其中, CScriptCheck最终会调用VerifyScript函数。

(2)ActivateBestChain(LoadBlock, GetBlocks, 回滚区块)。

调用栈如下所示:

ActivateBestChain:

    ActivateBestChainStep
        UpdateMempoolForReorg
            AcceptToMemoryPool

how

核心函数:(1)AcceptToMemoryPoolWorker (2)VerifyScript

AcceptToMemoryPoolWorker

AcceptToMemoryPoolWorker对构成交易的每个元素进行完整的验证, 检验金额,费用,UTXO, 祖先及子孙等是否合理,调用VerifyScript验证脚本及签名。

bool AcceptToMemoryPoolWorker(const CChainParams& chainparams, CTxMemPool& pool, CValidationState& state, const CTransactionRef& ptx,
                              bool* pfMissingInputs, int64_t nAcceptTime, std::list<CTransactionRef>* plTxnReplaced,
                              bool bypass_limits, const CAmount& nAbsurdFee, std::vector<COutPoint>& coins_to_uncache, bool test_accept)

chainparams: 表示主网, 测试网络, 还是私有网络
pool: 交易内存池
state: 返回验证状态
ptx: 交易
pfMissingInputs: 返回是否Missing Inputs
nAcceptTime: accept time
plTxnReplaced:
bypass_limits:
nAbsurdFee:
coins_to_uncache:
test_accept:

流程:

Step1. CheckTransaction:

is-txns-vin-empty
is-txns-vout-emp
is-txns-oversize
is-txns-vout-negative
is-txns-vout-toolarge
is-txns-txouttotal-toolarge
is-txns-inputs-duplicate
cb-length
is-txns-prevout-null

Step2. IsStandardTx:

非主网跳过此步骤;
检查交易版本号;
检查Weight是否合理;
检查每个txin.scriptSig.size() <= 1650;
检查每个txin.scriptSig.IsPushOnly();
检查每个txout是否IsStandard;
检查每个txout不存在bare-multisig;
检查每个txout不为dust;
检查所有txout里至多存在一个OP_RETURN

Step3. 检查tx-size >= MIN_STANDARD_TX_NONWITNESS_SIZE

Step4. CheckFinalTx:

检查tx.nLockTime是否为0,或者是否为合理时间或合理高度;
检查每个txin.nSequence == CTxIn::SEQUENCE_FINAL

Step5. 检查mempool不存在此交易

Step6. 检查mempool与其它交易不冲突

Step7. 检查交易中的所有inputs是否存在

Step8. 检查SequenceLocks(BIP68)

Step9. CheckTxInputs:

检查inputs是否可用;
检查所有coinbase的OutPoint为coinbase是否成熟;
检查所有Output及总和是否为合理范围;
检查in总和 >= out总和;
检查交易费用是否合理

Step10. AreInputsStandard:

非主网跳过此步骤;
coinbase交易跳过此步骤;
检查每个txin的prev txout是否合理

Step11. IsWitnessStandard:

非主网跳过此步骤;
非witness交易此步骤;
coinbase交易跳过此步骤;
检查每个txin的prev txout的witness是否合理

Step12. 检查交易的SigOpsCost ⇐ MAX_STANDARD_TX_SIGOPS_COST

Step13. 检查费用(根据mempool及优先级等)

Step14. 检查祖先及子孙的个数,size是否为合理范围

Step15. 检查祖先是否冲突

Step16. 检查replacement相关

Step17. CheckInputs

coinbase交易跳过此步骤;
scriptExecutionCache如果已经包含,返回true;
对每个txin Verify signature: 调用CScriptCheck::operator()即VerifyScript;
插入到scriptExecutionCache

Step18. CheckInputsFromMempoolAndCache:

只检查非coinbase交易步骤;
检查每个tx.vin的UTXO是否合理;
CheckInputs

Step19. Remove conflicting transactions from the mempool

Step20. Store transaction in memory

VerifyScript

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.