主页 > token.im钱包下载 > 比特币探索之三:交易池

比特币探索之三:交易池

token.im钱包下载 2023-05-28 06:27:45

我们先来看一条旧闻:

图 3-1 关于比特币内存池的旧闻

这里提到的比特币内存池包含了尚未并入区块链的交易,因此也被称为交易池。当用户使用比特币发起一笔交易时,它会通过 P2P 网络广播到各个节点,然后各个节点将这笔交易放入交易池中。然后矿工挖矿,也就是出块的时候,然后从交易池中选择一部分交易进行打包。区块生成后,相关交易从交易池中移除。因此可以想象,当交易特别火爆时,交易数量急剧增加,但出块速度依然稳定,可能会导致交易大量积压,长时间无法处理。情况。

那么问题来了,既然我们要选择交易的一部分进行打包,那么该选择谁,不该选择谁呢?基于什么?很简单,比特币规定在每一笔交易中,所有输入的总和必须大于或等于所有输入的总和。注意这里大于等于!也就是说,你可以输入 101 个比特币,只能输出 100 个比特币,好吗?当然!额外的 1 比特币去哪儿了?答案是作为交易费用,奖励给挖矿的矿工。回到上一个问题,矿工选择什么交易,不选择什么交易?显然,看看哪个交易的费用更高!

所以,小伙伴们,如果想让自己的交易更优惠地打包确认,记得适当增加交易手续费。目前各大交易平台都会对手续费给出一个参考值,基本上都是取平均值。那么,这个成本是怎么计算的呢?交易金额越大,手续费越高吗?胖兔告诉你,不,手续费只和交易本身的大小有关。交易规模是多少?一般来说比特币流动池是什么,就是把所有的输入输出相加,总共占用多少字节。如果一次转账1000 BTC,但只使用一个UTXO,交易规模会非常小;也许你只转了 1 个 BTC,但使用了 10 个 UTXO,那么对不起,交易量反而会增加,极端情况下,

图 3-2 每日交易费用图表

上图是区块链给出的每日交易费用图表。在撰写本文时,每日总交易费用为 16.57 个比特币。最高点出现在2017年12月22日,当天的手续费达到1495比特币!不知道比特币的价值?看看下面的图片:

图 3-3 单笔交易费用图表

这是每笔交易的平均费用,以美元计。目前的平均费用为每笔交易 7 美元6.54 美元,高峰时为 160 美元!这个费用不便宜!

图 3-4 交易池中的交易总数

上图是比特币交易池中交易总数的趋势图。现在矿池中的交易数量保持在 2700 到 2800 左右,峰值峰值出现在 2017 年 12 月,当时比特币最有价值,总交易量突破 18 万!这么多交易,交易池一定是巨大的!看看下面的图表:

图 3-5 交易池大小

可以看到交易池在最大的时候一度达到了139M,但是和现在的电脑内存容量相比,这根本不算什么,一部手机就能轻松搞定。

好了,让我们进入源代码时间。

交易池的代码定义在 txmempool.h/cpp 中,主要涉及 CTxMemPoolEntry 和 CTxMemPool 两个类。

CTxMemPoolEntry

CTxMemPoolEntry 是指交易池中的单个条目,包含交易和相关数据。它的数据声明部分是这样的:

图 3-6 CTxMemPoolEntry 数据成员

注意这里的数据成员都是私有的。所有数据都在构造函数中赋值或计算,然后只能通过类成员函数进行修改。

tx 是交易指针,指向当前条目对应的交易。

nFee 是交易费用。

nTxWeight 是交易权重,是在 2017 年比特币引入隔离见证时添加的,未来会研究隔离见证。

nUsageSize 是事务的内存使用量。这三个数据被缓存起来,方便统计计算。

nTime 是事务被添加到事务池的时间。

entryHeight 是加入交易池时区块链的高度。

花费Coinbase是指交易是否花费了一定的Coinbase。所谓Coinbase就是矿工在挖矿过程中的奖励。比特币规定,所有成功挖出某个区块的矿工,除了交易手续费外,还有额外的红利。2009 年比特币刚问世的时候,挖一个区块可以奖励 50 个比特币,然后每 4 年减半,现在只有 12.5 个比特币。

sigOpCost 为签名操作消耗,即本次交易的签名需要多少条指令。关于签名脚本,我们以后会详细研究。

feeDelta 为费用增量,增加的费用为 nFee + feeDelta。其目的是在挖矿时调整交易优先级,所以初始化时这个值必须为0。

lockPoints 是锁定点。所谓锁点,可以是区块高度,也可以是时间,也就是说,交易只有达到一定的区块高度或者一定的时间点,才能被打包进区块。

接下来的两组数据分别对应后代事务和祖先事务。所谓后代事务,就是依赖于当前事务的后续事务。比如交易A是张三给李四10个BTC,交易B是李四拿了这10个BTC给王五8个BTC,那么交易B就是交易A的后代,交易A就是交易B的祖先。如果没有选择交易A进行打包,交易B自然要在后面等待。

nCountWithDescendants、nSizeWithDescendants 和 nModFeesWithDescendants 分别是指当前交易和所有后代交易,它们的总数、总大小和总手续费。

nCountWithAncestors、nSizeWithAncestors、nModFeesWithAncestors、nSigOpCostWithAncestors分别是指当前交易和所有后代交易,它们的总数、总大小、总手续费和总签名消耗。

CTxMemPool

交易池的实现是比特币源代码中比较复杂的部分。类声明前有 70 行注释,而且都是巨大的句子。她看起来很沮丧,她想吐血。

由于CTxMemPool中的数据和成员函数太多了,一一介绍太累了,所以我们就挑重点吧(Skinny Rabbit:别听他的,其实很多肥兔都不会还没明白~)。源码就不贴了,小伙伴们可以自行阅读理解。

地图TX。这是第一个重要的数据成员,也是 CTxMemPoolEntry 的集合。其数据类型为indexed_transaction_set,定义如下:

图 3-7 索引事务集合的类型声明

这里用到了boost库的multi_index_container,它是一个多索引容器,允许为一系列数据添加索引。容器中的元素类型为CTxMemPoolEntry,一共建了4个索引:第一,交易ID其实就是交易的hash;然后是手续费比率,也就是手续费除以交易规模,注意这里的手续费比率包括后代交易。CompareTxMemPoolEntryByDesendantScore 是一个自定义算子,比较费率;第三个是进入交易池的时间;最后一个是包含祖先交易的费用比率。从上图我们可以知道最大事务数可以达到18万,但是有了这四个索引,大多数情况下,在搜索事务的时候,

vTxHash。用于存储所有隔离见证哈希。它的类型是std::vector >,是一个向量,每个元素都是一对。pair的第一个元素是witness hash,第二个元素类型txiter的定义是indexed_transaction_set::nth_index::type::const_iterator其实就是mapTx的第一个index的迭代器,通过它可以遍历所有的CTxMemPoolEntry,即遍历事务。

地图链接。用于存储与每个事务相关的父节点和子节点。它的类型是std::map,其中TxLinks 是一个包含两个数据成员,父母和孩子的结构。这两个成员的类型称为setEntries,是txiter(std::set)的集合。注意这个数据是私有的,只在类内部使用,主要是为了加速上下节点的访问。

地图下一个Tx。它的类型是indirectmap,是比特币中为了方便比较指针所指向的数据而自定义的数据类型。我们知道每笔交易的输入实际上来自于之前的输出,mapNextTx 存储了从每一个 COutPoint 到后续交易的链接。它和mapLinks的区别在于mapLinks中指向的父子交易还在交易池中,但是mapNextTx中的COutPoint可能已经写入了block。

地图三角洲。存储的是每次交易所对应的交易手续费增量。

其他几个重要的私有数据成员,主要是:

nCheckFrequency,表示需要在 2^32 秒内对事务池进行多次检查。

nTransactionsUpdated,当前更新了多少笔交易,用于触发挖矿操作。

minerPolicyEstimator 是用于挖掘策略评估的 CBlockPolicyEstimator 类变量。

totalTxSize,所有交易的大小,不包括见证数据,与交易存储时占用的大小不同。

cachedInnerUsage,所有map成员的累积元素占用的动态内存。包括mapTx、mapNextTx、mapDeltas等。

lastRollingFeeUpdate,上次调整费率的时间。

blockSinceLastRollingFeeBump比特币流动池是什么,最后一次费率调整后是否产生了块。

rollingMinimumFeeRate,最后调整的最低费率。

最后介绍比较重要的成员函数:

check 和 setSanityCheck,setSanityCheck 用于设置对交易池进行完整性检查的频率。check是检查整个交易池,包括:每笔交易的输入是现有的UTXO还是交易池中其他交易的输出;每个交易输入都在 mapNextTx 中;祖先交易状态正常;可以通过 mapNextTx 找到后代交易;还要检查事务大小和内存使用情况。注意这里的check可以防止双花交易,我们知道每个输出只能作为输入一次,用完就会销毁,但是如果一个输出被使用了两次,就叫做双花-spend or double-spend,这是数字货币必须解决的问题。

addUnchecked、removeUnchecked 是添加或删除新事务。

HasNoInputsOf 用于检查交易的所有输入是否不在交易池中。也就是说,所有的祖先交易都被打包进了区块。

PrioritiseTransaction、ApplyDelta、ClearPrioritisation 用于调整交易优先级,一般在挖矿时使用。

RemoveStaged 用于从事务池中移除部分事务。这里删除的原因可能是过期、大小限制、重组、打包成块、冲突、被取代等。删除交易后,其相关祖先和后代交易中的数据将一起更新。

UpdateTransactionsFromBlock,受挖矿机制影响,有时矿工会提前挖出后面的区块,但由于没有链入主链,这个区块中包含的交易会被放回交易池,其后代会进行交易此时。可能已经在交易池中,UpdateTransactionsFromBlock 会找到这些后代交易并更新相关数据。

CalculateMemPoolAncestors,CalculateDescendants,当一个事务放入mempool时,CalculateMemPoolAncestors用于查找其所有祖先事务,CalculateDescendants查找所有后代事务。

GetMinFee 用于计算加入交易池所需的最低费用。

TrimToSize 用于调整事务池的大小,此时可能会移除部分事务(原因设置为大小限制)。

过期用于设置早于指定时间过期的所有事务。

交易池是比特币非常重要的一个部分,下面的很多内容如果不细细细细琢磨一下这部分内容是不容易理解的。嗯,这次就到这里了。下一章已准备好开始考虑挖矿。

本文原作者胖兔(魏兆华)首发于简书,欢迎转载,转载请注明出处。