资讯动态

比特币交易源码深度解析:从raw transaction到内存池的七步验证

发布时间:2026/10/20 5:58:27 来源:尧图企业网站定制
1. 项目概述从一笔交易出发真正看懂比特币的“心脏跳动”你打开钱包点下“发送”输入地址、金额、手续费确认——几秒后对方收到。这看似轻巧的一次操作背后是全球数万台节点共同验证、记账、广播、共识的精密协作。而驱动这一切的不是黑箱API不是中心化服务器而是写在GitHub上、任何人都可下载、编译、调试的C代码。BTC源码分析 交易一说的正是这件事不靠二手解读、不依赖抽象图示、不满足于“它大概这样运行”而是亲手翻开src/transaction.h、src/main.cpp、src/validation.cpp逐行跟踪一笔原始交易raw transaction从内存构造、序列化编码、签名验证、Mempool暂存到最终被打包进区块的完整生命周期。我带过不少刚接触区块链的开发者他们常卡在同一个地方能背出UTXO模型、能画出Merkle树结构、甚至能手推ECDSA签名流程但一旦面对CTransaction类里几十个字段、CheckInputs()函数中嵌套四层的校验逻辑、或是AcceptToMemoryPool()返回REJECT_INVALID却不知错在哪一行就立刻失去方向。问题不在概念而在代码与概念之间的那层薄纸没被捅破。这篇分析就是帮你把这张纸彻底撕开——我们不讲“比特币是什么”只讲“当bitcoind进程收到一个tx消息时它内部到底做了什么”。你会看到nLockTime如何被解析为绝对时间或区块高度、scriptSig和scriptPubKey如何在EvalScript()中完成脚本执行、为什么一笔交易即使签名正确也可能因nSequence值过小而被拒绝进入内存池。所有内容基于Bitcoin Core v24.1当前稳定版源码所有路径、函数名、关键变量均真实可查你可以随时打开本地源码对照阅读。适合有C基础、了解基本密码学概念哈希、椭圆曲线、并已成功编译过Bitcoin Core的开发者如果你刚接触建议先用./configure --without-gui make -j$(nproc)编译一次再回来读这一篇——因为接下来的每一行分析都建立在你亲眼见过那些代码的基础上。2. 核心设计思路拆解为什么交易处理要分七步走比特币交易处理绝非“收到→验证→上链”三步那么简单。翻开源码你会发现整个流程被严格拆解为至少七个逻辑阶段每个阶段承担明确职责、拥有独立校验目标并通过清晰的错误码如TX_NOT_STANDARD、TX_MISSING_INPUTS反馈失败原因。这种设计不是为了炫技而是源于比特币网络最根本的约束去中心化环境下的确定性、安全性与资源可控性。下面我带你一层层剥开这个设计背后的硬逻辑。2.1 阶段划分与核心目标比特币Core将一笔交易的诞生到落块划分为以下不可跳过的七个环节反序列化Deserialize将网络传输的二进制字节流ser_tx还原为内存中的CTransaction对象。这是所有后续操作的前提也是第一道防线——格式错误如长度溢出、字段越界在此刻被拦截。基础结构校验Basic Checks检查交易版本号是否合法v1/v2/v3、输入输出数量是否超限MAX_BLOCK_WEIGHT / WITNESS_SCALE_FACTOR、交易大小是否在合理范围MAX_STANDARD_TX_WEIGHT。这一步不涉及密码学纯属防滥发。标准性检查IsStandard()判断交易是否符合“标准模板”Standardness Rules。例如scriptPubKey不能包含OP_RETURN以外的OP_PUSHDATA指令、P2SH脚本必须小于等于520字节、多签脚本最多15个公钥。这不是共识规则而是节点自愿执行的“礼貌协议”防止垃圾交易挤占内存池。上下文无关签名验证Context-Free Signature Validation对每个输入的scriptSig与对应UTXO的scriptPubKey进行脚本执行EvalScript()验证签名有效性。注意此时不查询UTXO状态仅做数学运算——ECDSA签名是否匹配公钥、脚本是否按预期执行如OP_CHECKSIG返回true。上下文相关输入检查Context-Dependent Input Checks这才是真正的“查账”。调用CoinsViewCache::AccessCoin()从UTXO集合中精确获取该输入引用的前序交易输出prevout验证其存在性、未花费状态、金额是否足够支付。同时检查nLockTime和nSequence是否满足相对/绝对锁定条件。内存池接纳Mempool Acceptance若以上全过交易被放入本地内存池m_mempool等待被打包。此阶段会检查手续费率是否达到最低阈值minRelayTxFee、交易是否与内存池中已有交易冲突双花、是否造成内存池过大maxmempool限制。区块打包与共识验证Block Inclusion Consensus Validation矿工将内存池中高费交易打包进候选区块全网节点在接收新区块时会再次执行全部七步尤其是第4、5步确保区块内所有交易在当前链尖状态下依然有效。这是最终的、不可绕过的共识裁决。提示很多初学者混淆“内存池验证”和“区块验证”。关键区别在于内存池验证是单节点的“预筛选”允许一定宽松如不强制检查nLockTime是否已到而区块验证是全网共识的“终审”必须100%严格。这也是为什么一笔交易能进内存池却无法被打包——它可能在区块生成时刻nLockTime尚未满足。2.2 为何必须如此复杂三个硬约束的倒逼这种看似繁琐的设计实则是三个无法妥协的硬约束共同作用的结果约束一网络带宽与存储成本比特币P2P网络没有中心服务器分发压力每个节点都是平等的广播者。如果允许任意大小、任意复杂度的交易比如一个含1000个输入、每个输入脚本都含循环的交易恶意节点可以轻易用极小带宽发起“脚本炸弹”让全网节点CPU瞬间拉满。因此IsStandard()和MAX_SCRIPT_ELEMENT_SIZE等限制本质是用计算成本换网络稳定性——简单交易快速通过复杂交易直接拒收保护绝大多数诚实节点。约束二UTXO查询的I/O瓶颈UTXO集合是比特币最核心的状态数据通常存储在LevelDB中。频繁随机读取尤其对老区块的UTXO会产生大量磁盘I/O。如果在反序列化后立即查所有输入的UTXO一笔100输入的交易就会触发100次磁盘寻道严重拖慢交易处理速度。所以设计上上下文无关验证第4步先做纯内存计算把明显无效的交易签名错快速筛掉只有通过这关才进行昂贵的UTXO查询第5步。这是一种典型的“乐观执行”策略。约束三时间锁机制的动态性nLockTime和nSequence让交易具备“定时”能力但“当前时间”对每个节点并非绝对一致。区块时间戳允许±2小时偏差节点本地时钟也可能不准。因此共识规则规定nLockTime为区块高度时以链尖高度为准为时间戳时以节点本地时间2小时为上限。这意味着同一笔交易在不同节点、不同时间点其“是否可广播”的结论可能不同。将时间锁检查放在第5步上下文相关而非第2步基础检查正是为了将动态状态判断推迟到拥有最新链状态的时刻确保结果确定性。这七步设计不是教科书里的理想模型而是十年来在真实网络攻击、硬件瓶颈、共识分歧中反复打磨出的生存法则。理解它你就理解了比特币为何能十年不倒——它的健壮藏在每一行校验代码的取舍里。3. 核心细节解析从CTransaction到EvalScript的逐帧拆解现在我们把镜头拉近聚焦到交易处理最核心的两个数据结构与一个函数CTransaction交易本体、CTxIn/CTxOut输入输出、以及决定交易生死的EvalScript()。它们不是孤立存在而是环环相扣的齿轮。下面我以一笔真实的测试网交易txid: 5a8...c3f为例带你手把手走过内存中的每一步。3.1 CTransaction交易的“基因图谱”当你调用decoderawtransaction解码一笔十六进制交易得到的JSON结构正是CTransaction类在内存中的映射。打开src/primitives/transaction.h你会看到这个类的定义。它没有虚函数、没有复杂继承就是一个纯粹的数据容器设计哲学是零开销抽象Zero-cost abstraction。class CTransaction { public: const std::vectorCTxIn vin; // 输入列表vector of CTxIn const std::vectorCTxOut vout; // 输出列表vector of CTxOut const int32_t nVersion; // 版本号int32_t非uint32_t const uint32_t nLockTime; // 锁定时间uint32_t // ... 其他字段witness、hash等 };关键细节与陷阱vin与vout是const vector这意味着一旦交易对象构建完成其输入输出数量、顺序、内容就完全不可变。这是保证交易哈希GetHash()确定性的基石。任何试图修改vin[0].scriptSig的操作都必须创建新交易对象而非原地修改。nVersion是int32_tnLockTime是uint32_t这个类型差异有深意。nVersion可为负虽然当前协议只用1/2/3为未来软分叉预留空间而nLockTime作为时间/高度标识必须无符号避免负数时间引发歧义。在序列化时nVersion按小端序写入4字节nLockTime同理但解析时若误用int32_t读取nLockTime会导致高位为1时解析成巨大负数直接崩溃。vin中的prevout是COutPointCOutPoint包含uint256 hash前序交易ID和uint32_t n输出索引。这里有个易错点hash是32字节但在P2P网络中它被序列化为反向字节序即hash.GetReverseHex()。如果你用Python的hashlib.sha256()直接计算交易ID得到的是正向字节序必须手动反转才能匹配COutPoint.hash。这是无数调试者深夜抓狂的根源。实操心得我在某次调试中发现一笔交易总被MISSING_INPUTS拒绝但getrawtransaction显示前序交易存在。最后定位到测试脚本里用hashlib算出的prevout.hash是正向的而bitcoind内存里存的是反向的。解决方案在构造COutPoint时显式调用hash.SetReverseHex(...)。这个细节官方文档从不提但源码里Serialize()函数的注释写着“Hashes are serialized in reverse byte order”。3.2 脚本执行引擎EvalScript的“虚拟机”真相如果说CTransaction是交易的骨架那么scriptSig和scriptPubKey的组合就是它的神经与肌肉。而EvalScript()就是那个执行神经信号、驱动肌肉收缩的“大脑”。它位于src/script/interpreter.cpp是一个纯函数式、无状态的脚本解释器。一笔典型的P2PKH交易其脚本执行流程如下scriptSig: sig pubkey scriptPubKey: OP_DUP OP_HASH160 pubkey_hash OP_EQUALVERIFY OP_CHECKSIGEvalScript()会将两者拼接形成执行栈Stack: [sig, pubkey, pubkey, OP_HASH160, pubkey_hash, OP_EQUALVERIFY, OP_CHECKSIG]然后逐条执行OPCODEOP_DUP复制栈顶pubkey→[sig, pubkey, pubkey, pubkey]OP_HASH160对栈顶pubkey做RIPEMD160(SHA256())→[sig, pubkey, hash160(pubkey)]OP_EQUALVERIFY比较栈顶两元素hash160(pubkey)vspubkey_hash相等则继续否则失败 →[sig, pubkey]OP_CHECKSIG用pubkey验证sig对交易hash的签名 → 返回true/false关键原理与陷阱交易哈希sighash不是对原始交易哈希OP_CHECKSIG验证的是交易的特定哈希由sighash标志位决定如SIGHASH_ALL、SIGHASH_SINGLE。这个哈希的计算过程极其复杂需临时清空所有vin[i].scriptSig对指定输入设置scriptSig为sig pubkey再对整个交易序列化后哈希。EvalScript()本身不计算sighash它只调用SignatureHash()函数。而SignatureHash()的实现是比特币最易出错的代码之一——它必须精确模拟“如果这笔交易被打包矿工会看到什么”包括对nSequence、nLockTime的特殊处理。脚本执行是“沙盒”环境EvalScript()有自己的stack、altstack、vfExec条件执行栈所有操作都在此隔离环境中进行。它不访问任何外部状态如UTXO、区块链高度也不产生副作用。这意味着即使脚本里有OP_CAT连接字符串它也只能操作栈内数据无法读写磁盘或网络。这种设计是脚本安全性的根本保障。错误码即真相EvalScript()返回true或false但更重要的是它抛出的ScriptError枚举。例如SCRIPT_ERR_EVAL_FALSE表示脚本执行完栈顶非trueSCRIPT_ERR_VERIFY表示OP_VERIFY失败SCRIPT_ERR_BAD_OPCODE表示遇到未知OPCODE。这些错误码是定位脚本问题的唯一依据。我在分析一笔闪电网络通道关闭交易时EvalScript()返回SCRIPT_ERR_BAD_OPCODE追踪发现是测试网启用了OP_CHECKMULTISIG的替代指令而我的节点未同步该软分叉。永远相信错误码而不是日志里的模糊提示。3.3 输入验证的“临门一脚”CheckInputs()的深度剖析当EvalScript()对所有输入返回true交易就进入了最危险也最关键的环节CheckInputs()。这个函数位于src/validation.cpp是连接“密码学正确”与“经济上可行”的桥梁。它不做新事只做一件事查账。其核心逻辑伪代码如下for (const auto txin : tx.vin) { // 1. 从UTXO缓存中获取前序输出 Coin coin; if (!view.AccessCoin(txin.prevout, coin)) { return state.Invalid(TX_INVALID, bad-txns-inputs-missingorspent); } // 2. 检查金额是否足够含手续费 if (coin.out.nValue tx.GetValueOut()) { // 注意此处是累计所有输出 return state.Invalid(TX_INVALID, bad-txns-in-belowout); } // 3. 检查nLockTime和nSequence if (!CheckFinalTx(*pblockindex, tx)) { // pblockindex是当前链尖 return state.Invalid(TX_INVALID, bad-txns-nonfinal); } }三个致命细节GetValueOut()的陷阱这个函数返回的是当前交易所有vout的金额总和而非单个输入对应的金额。而coin.out.nValue是该输入所引用的那个CTxOut的金额。因此coin.out.nValue tx.GetValueOut()这个判断其实是检查“单个输入的金额是否足以支付整笔交易的所有输出”。这显然不合理正确逻辑应该是所有输入的nValue之和必须大于等于所有输出之和。源码中这个求和是在循环外完成的CAmount nValueIn view.GetValueIn(tx);。GetValueIn()会遍历所有vin累加每个coin.out.nValue。所以CheckInputs()里真正的金额检查是nValueIn tx.GetValueOut()。初学者常在此处误解以为每个输入都要单独覆盖总输出。AccessCoin()的双重含义view.AccessCoin(txin.prevout, coin)不仅查询UTXO是否存在更关键的是它会将coin标记为“已花费”如果该输入在内存池中已被其他交易引用。这是内存池实现双花检测的核心机制。coin.IsSpent()返回true意味着该UTXO已被占用不能再被新交易使用。因此CheckInputs()的成功不仅证明钱“有”更证明钱“还没被别人花”。CheckFinalTx()的时间锁博弈这个函数检查nLockTime和nSequence。对于nLockTime它比较tx.nLockTime与pblockindex-nHeight如果是高度或chainActive.Tip()-GetMedianTimePast()如果是时间戳。但注意GetMedianTimePast()是过去11个区块时间戳的中位数而非当前系统时间。这意味着一笔nLockTime700000的交易在区块高度699999时即使你的本地时间已是700001它依然无效——因为链尖的中位数时间可能还是699998。这是共识规则强制的“链上时间”而非“物理时间”。注意CheckInputs()的返回值state是一个TxValidationState对象它携带了详细的错误信息strRejectReason和错误码Invalid()。在调试时务必打印state.GetRejectReason()而不是只看false。我曾为一个strRejectReasonnon-BIP68-final的错误排查三天最后发现是测试网的nSequence值被设为0而BIP68要求相对锁定必须nSequence 0xFFFFFFFE。这个细节只有错误字符串会告诉你。4. 实操过程从构造原始交易到内存池接纳的完整链路理论终须落地。现在我们以一个最简场景——在本地测试网regtest上用bitcoin-cli构造一笔P2PKH交易并全程跟踪其在bitcoind进程内的处理路径——来完成一次完整的实操复现。这不是演示而是你明天就能在自己机器上敲出来的调试流程。4.1 构造与广播四步走通交易生命线步骤一准备环境与密钥启动一个纯净的regtest节点bitcoind -regtest -daemon -server -rpcusertest -rpcpasswordtest生成两个地址A为发送方B为接收方# A的地址用于发送 bitcoin-cli -regtest getnewaddress A legacy # 返回: mkZ...QdF # B的地址用于接收 bitcoin-cli -regtest getnewaddress B legacy # 返回: mka...XyG # 查看A的余额需先挖矿 bitcoin-cli -regtest generatetoaddress 101 $(bitcoin-cli -regtest getnewaddress)步骤二构造原始交易Raw Transaction这是最关键的一步也是最容易出错的一步。我们必须手动处理UTXO选择、签名、序列化。查询A的UTXObitcoin-cli -regtest listunspent 1 999999 [mkZ...QdF] # 返回类似 # [ # { # txid: a1b2...c3d4, # vout: 0, # amount: 50.00000000, # scriptPubKey: 76a914...88ac # } # ]创建原始交易指定输入、输出、找零bitcoin-cli -regtest createrawtransaction \ [{txid:a1b2...c3d4,vout:0}] \ {mka...XyG:0.1,mkZ...QdF:49.89990000} # 返回: 0200000001...00000000注意找零地址必须是A自己的地址mkZ...QdF且金额输入50 - 输出0.1 - 手续费约0.0001。createrawtransaction不计算手续费需手动预留。签名交易bitcoin-cli -regtest signrawtransactionwithwallet 0200000001...00000000 # 返回: {hex:0200000001...00000000,complete:true}此时hex字段就是完全签名的原始交易十六进制。步骤三广播与观察bitcoin-cli -regtest sendrawtransaction 0200000001...00000000 # 成功返回txid # 失败则返回错误如 mandatory-script-verify-flag-failed步骤四实时跟踪内存池# 查看内存池中是否有该交易 bitcoin-cli -regtest getmempoolentry txid_here # 查看内存池统计 bitcoin-cli -regtest getmempoolinfo # 关注 size交易数、bytes总字节数、usage内存占用4.2 源码级调试在gdb中捕捉交易处理瞬间光看命令行不够。我们要进入bitcoind进程内部亲眼看到CTransaction对象是如何被创建、校验、接纳的。以下是经过千锤百炼的调试方案环境准备编译带调试符号的bitcoind./configure --enable-debug make -j$(nproc)启动节点时附加-debugnet网络日志和-debugmempool内存池日志使用gdb附加进程gdb -p $(pgrep bitcoind)关键断点设置在gdb中设置以下断点它们是交易处理的“咽喉要道”# 断点1P2P消息接收入口收到tx消息时 (gdb) b ProcessMessage (gdb) cond 1 strCommand tx # 断点2交易反序列化完成CTransaction对象诞生 (gdb) b CTransaction::CTransaction (gdb) cond 2 this-GetHash() uint256S(txid_here) # 断点3标准性检查IsStandard失败时 (gdb) b IsStandard (gdb) cond 3 !IsStandard(tx) # 断点4EvalScript执行入口最核心的密码学验证 (gdb) b EvalScript (gdb) cond 4 script.size() 0 # 断点5CheckInputs()开始执行 (gdb) b CheckInputs (gdb) cond 5 tx.GetHash() uint256S(txid_here)调试现场记录当我对一笔故意构造的nLockTime1000000远超当前高度的交易设置断点5时gdb在CheckInputs()第一行停住。执行step进入很快停在CheckFinalTx()。查看变量(gdb) p pblockindex-nHeight $1 105 (gdb) p tx.nLockTime $2 1000000 (gdb) p CheckFinalTx(pblockindex, tx) $3 false清晰看到nLockTime1000000远大于当前链尖高度105CheckFinalTx直接返回false交易被拒绝。这就是共识规则在代码中的冰冷体现。实操心得gdb调试bitcoind有个大坑——它默认在main()线程运行而P2P网络消息是在ThreadMessageHandler线程中处理的。如果你只在main线程下断点会错过所有网络事件。正确做法是gdb附加后先info threads找到ThreadMessageHandler的线程ID如2然后thread 2切换过去再b ProcessMessage。否则你永远等不到断点命中。4.3 内存池接纳的“隐形门槛”手续费与冲突检测一笔交易通过CheckInputs()只是拿到内存池的“入场券”能否真正坐下还要看两个隐形门槛手续费率和双花检测。手续费率门槛minRelayTxFeebitcoind默认minRelayTxFee1000 satoshis/kB。这意味着你的交易每千字节必须支付至少1000聪手续费。计算方式fee_rate (input_total - output_total) / (tx_size_in_bytes / 1000)。如果交易大小为500字节总手续费为400聪则fee_rate 400 / 0.5 800 1000会被拒绝。解决方案要么增加手续费要么减小交易体积如用SegWit地址将签名数据移至隔离见证区大幅降低weight。双花冲突检测内存池维护一个mapNextTx键为COutPoint前序输出值为指向本交易的指针。当新交易tx_new的某个输入tx_new.vin[i].prevout已在mapNextTx中存在时说明该UTXO已被另一笔交易tx_old引用。此时AcceptToMemoryPool()会执行“手续费竞争”比较tx_new和tx_old的feerate费率高的胜出低的被踢出。这就是RBFReplace-By-Fee的底层机制。你可以用bitcoin-cli -regtest fundrawtransaction时加上{replaceable: true}参数主动开启RBF。检测项触发条件错误码解决方案手续费过低feerate minRelayTxFeeTX_NOT_STANDARD增加手续费或用settxfee全局提高双花冲突prevout已在mapNextTx中TX_CONFLICT提高手续费或等待tx_old被确认内存池满m_mempool.DynamicMemoryUsage() maxmempoolMEMPOOL_FULL增加-maxmempool300MB或清理旧交易5. 常见问题与排查技巧实录那些年踩过的坑源码分析不是纸上谈兵而是与bug、错误码、诡异行为的贴身肉搏。下面是我和团队在过去三年中从数千次调试中提炼出的高频问题速查表。每一个问题都附有真实场景、错误现象、根因分析和一招制敌的解决方案。它们不是教科书答案而是深夜两点、咖啡见底时真正救过命的经验。5.1 “mandatory-script-verify-flag-failed”脚本验证失败的万能错误现象sendrawtransaction返回{code:-26,message:16: mandatory-script-verify-flag-failed}。这是最常遇到、也最让人抓狂的错误。它像一个黑盒告诉你“脚本错了”却不告诉你哪错了。根因分析这个错误码SCRIPT_ERR_EVAL_FALSE是EvalScript()的兜底返回。它意味着脚本执行完毕但最终栈顶不是true即非空、非0。可能原因有数十种但90%集中在以下三点公钥/签名不匹配scriptSig中的pubkey与scriptPubKey中pubkey_hash对应的公钥经OP_HASH160后不一致。常见于地址生成时用了压缩公钥但签名时用了非压缩公钥反之亦然或SignatureHash()计算时sighash标志位如SIGHASH_ANYONECANPAY与scriptSig中嵌入的标志位不一致。nSequence值违规在启用BIP68相对时间锁的网络上nSequence必须小于0xFFFFFFFE。如果设为0xFFFFFFFF表示禁用时间锁EvalScript()会因OP_CHECKSEQUENCEVERIFY指令失败而报此错。脚本长度超限IsStandard()检查中scriptPubKey长度超过520字节或scriptSig超过1650字节会被提前拒绝但错误码仍显示为mandatory-script-verify-flag-failed。一招制敌不要猜用bitcoin-cli -regtest decodescript和bitcoin-cli -regtest verifyrawtransaction需启用-deprecatedrpcsignrawtransaction交叉验证。decodescript 76a914...确认scriptPubKey的typepubkeyhash和reqSigs1是否正确。verifyrawtransaction 020000...它会返回每个输入的scriptSig、scriptPubKey、witness以及resulttrue/false。如果resultfalse它还会告诉你error字段如Script evaluated without error but finished with a false/empty top stack element这就精准定位到栈顶为假。5.2 “bad-txns-inputs-missingorspent”UTXO不见了现象sendrawtransaction返回{code:-26,message:16: bad-txns-inputs-missingorspent}。字面意思是“输入缺失或已花费”。根因分析CheckInputs()中view.AccessCoin(txin.prevout, coin)返回false。但这绝不意味着UTXO真的不存在而更可能是UTXO已被确认但节点未同步你在A节点生成交易引用了B节点刚挖出的区块里的UTXO但A节点尚未收到该区块。getblockcount返回高度比B节点低1listunspent为空。UTXO在内存池中但被另一笔交易占用mapNextTx已记录该prevout被tx_old引用。你的tx_new想用同一个UTXO自然被拒。prevout的txid字节序错误如前所述txid在网络序列化中是反向的。你用hashlib.sha256().digest()得到正向字节直接填入createrawtransactionbitcoind解析时找不到对应交易。一招制敌执行bitcoin-cli -regtest getrawtransaction a1b2...c3d4 11表示详细模式。检查返回的blockhash字段如果有blockhash说明已上链用getblock blockhash确认该区块是否在你的链上。如果无blockhash说明在内存池用getmempoolentry a1b2...c3d4看它是否还在。如果两者皆无且你确定刚生成过那100%是txid字节序问题。解决方案用bitcoin-cli -regtest decoderawtransaction 020000...查看vin[0].txid把它复制出来与你构造时用的txid逐字节比对——99%会发现它们是互为反转的。5.3 内存池“静默消失”交易进了又没了现象sendrawtransaction成功返回txidgetmempoolentry也能查到但几秒后getmempoolentry返回null交易仿佛从未存在。根因分析这不是bug而是bitcoind的主动清理策略。内存池有三大自动驱逐机制超时驱逐Expiry默认-mempoolexpiry33614天。一笔交易在内存池中停留超过14天会被自动删除。常见于低手续费交易在熊市中长期无人打包。手续费驱逐Fee Bump当一笔高

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑