请选择 进入手机版 | 继续访问电脑版
MOAC社区
区块链就是和交易打交道。本文主要从功能角度介绍交易处理过程中的一个重要组成部分:交易池(txpool)。

从字面意思就能看出来,交易池就是存放交易的池子。只要有新交易,无论是本节点创建的,还是其他peer节点广播来的,都会先加入到交易池里,在打包区块的时候,就从这个池子里提取,区块产生之后,共识区块,交易上链。

1:交易流程
1.jpg
流程分为以下几个步骤:
发起交易:指定目标地址和交易金额,以及需要的gas price/gas limit;
交易签名:使用账户私钥对交易进行签名;
提交交易:把交易加入到交易缓冲池txpool中(会先对交易签名进行验证);

广播交易:把交易信息广播给其他节点。

2:交易池的主要功能

交易池的主要功能包括四个部分:

缓存功能

交易池(txpool)作为存放交易的缓冲区,大量交易到来时,先存起来;
txpool由两部分构成,分别是pending和queued:
queued:提交但是当前无法执行的交易,放在queue中等待执行。比如nonce值设置过大的交易。举个例子,当前账户的nonce是10,txpool中有该账户的第100号交易,但txpool中没有第11~99号交易,这些交易的缺失,造成第100号交易无法执行,所以第100号交易就是未来的交易、不可执行的交易,存放在queue中。
pending:等待执行的交易会被放在pending队列中;比如我们把上面的11~99号交易补全了,那么第100号交易都可以进入到pending,因为这些交易都是连续的,都可以打包进区块。
当节点收到交易(本地节点发起的或peer广播来的)时,会先存放到queued,txpool在某些情况下,把queued中可执行的交易,转移到pending中。

打包区块服务功能

这是txpool最核心的功能,矿工在打包区块的时候,会获取所有的pending交易,但这些交易还存在txpool中,矿工只是读取出来,至于txpool何时删除交易,可以从txpool清理交易的角度看。

清理交易

txpool清理交易有以下几种条件,符合任意以下1条的,都是无效交易,会被从pending或者queued中移除:
交易的nonce值已经低于账户在当前高度上的nonce值,代表交易已过期,交易已经上链就属于这种情况;
交易的GasLimit大于区块的GasLimit,区块容不下交易;
账户的余额已不足以支持该交易要消耗的gas费用;
交易的数量超过了queued和pending的缓冲区大小,需要进行清理。
交易清理主要有3个场景:
txpool订阅了ChainHeadEvent事件,该事件代表主链上有新区块产生,txpool会根据最新的区块,检查每个账号的交易,有些无效的会被删除,有些由于区块回滚会从pending移动到queued,然后把queued中可执行的交易移动到pending,为下一轮区块打包做好准备。
queued交易移动到pending被称为“提升”(promote),这个过程中,同样会检查交易,当交易不符合以上条件时,就会被直接从queued中删除。
删除停留在queued中超过3小时的交易,3小时这个超时时间是可以通过启动参数调整的。txpool记录了某个账户交易进入pending的时间,如果这个时间超过了3小时,代表该账号的交易迟迟不能被主链打包,既然无法被主链接受,就删除掉在queued中本来就无法执行的交易。

惩罚恶意账号

恶意用户会造成:
占用txpool空间;
浪费节点大量内存和CPU;
降低打包性能。txpool会涉及多个锁,当txpool中的交易很多时,性能会很低,这也会影响到区块的打包。

为了防止恶意账户发起大量垃圾交易。底层有一个处理策略。
只有当交易的总数量超过缓冲区大小时,txpool才会认为有恶意账户发起大量交易。过滤/惩罚发送大量交易的账户(攻击者)。

pending和queued缓冲区大小不同,但处理策略类似:
pending的缓冲区容量是4096,当pending的交易数量多于此时,就会运行检查,每个账号的交易数量是否多于16,把这些账号搜集出来,进行循环依次清理。什么意思呢?就是每轮只删除(移动到queued)这些账号的每个账号1条交易,然后看数量是否降下来了,不满足再进行下一轮,直到满足。
queued的缓冲区容量是1024,超过之后清理策略和pending差不多,但这里是真删除了。
该部分功能未抽象成单独的函数,而是在promote中,就是在每次把queued交易转移到pending后执行的。
本地交易有特权,txpool虽然对交易有诸多限制,但如果交易是本节点的账号发起的,以上数量限制等都对他无效。所以,如果用本节点账号不停地发送交易,并不会被认为是攻击者,用txpool.status命令,可以查看到交易的数量,这种情况是可以大于4096的。


3:交易池逻辑

提交交易的目标是:
先把交易放入queue中记录在案,然后再从queue中选一部分放入pending中进行处理。如果发现txpool满了,则依据priced中的排序,剔除低gas费的交易;
如果是本地(local)提交的交易,默认情况下会尽可能地保证被放入txpool中,除非显式关闭该配置。
txpool逻辑由两个主要函数实现:add()和promoteExecuteables(),下面分别讨论这两个函数。

TxPool.add()

add()会判断是否应该把当前交易加入到queue列表中。
1)先计算交易的hash值,然后判断是不是已经在txpool 中,在的话就直接退出。

2)验证交易的有效性,主要进行以下几个方面的检查:
数据量必须<32KB
交易金额必须非负(>=0)
交易的gas limit必须低于block的gas limit
签名数据必须有效,能够解析出发送者地址
交易的gas price必须高于pool设定的最低gas price(除非是本地交易)
交易的nonce值必须高于当前链上该账户的nonce值(低于则说明这笔交易已经被打包过了)
当前账户余额必须大于“交易金额 + gasprice * gaslimit”
交易的gas limit必须大于对应数据量所需的最低gas水平

3)当前txpool已满的情况下,剔除掉低gas的交易。数据存储中有个priced字段存储了按gas price以及nonce排序的交易列表,这里会先把当前交易的gas price和当前池中的最低价进行比较:
如果低于最低价,直接丢弃该交易返回
如果高于最低价,则从txpool中剔除一些低价的交易

4)如果用户发起了一笔交易,在还没有被执行之前又用同样的nonce发起了另一笔交易,此时两个交易nonce相同,则只会保留gas price高的那一笔。

5)如果之前的那些检查都没有问题,就真正调用enqueueTx()函数把交易加入到queue列表中了。

6)最后,如果发现这个账户是本地的,就把它加到一个白名单里,默认会保证本地交易优先被加到txpool中。

TxPool.promoteExecuteables()

promoteExecuteables()会从queue中选取一些交易放入pending列表中等待执行。

2.jpg

根据不同的目的该部分功能可以分为3块,分别以粉色、紫色、绿色标识。

1)粉色部分主要是为了把queue中的交易“提拔”到pending中。当然在这之前需要先要进行一番检查:
丢弃nonce < 账户当前nonce的交易,也就是已经被打包过的交易
丢弃转账金额 + gas消耗 > 账户余额的交易,也就是会out-of-gas的交易
丢弃gas limit > block gas limit的交易,这部分交易可能会导致区块生成失败

2)紫色部分主要是为了清理pending列表,使其满足GlobalSlots和AccountSlots的限制条件:
如果有些账户的交易数超过了AccountSlots,则先按交易数最少的账户进行均衡。举例来说,如果有10个账户交易数超过了AccountSlots(默认16),其中交易数最少的账户包含20笔交易,那么先把其他9个账户的交易数量削减到20。
如果经过上面的步骤,pending的长度还是超过了GlobalSlots,那就严格按照AccountSlots进行均衡,也就是把上面的10个账户的交易数进一步削减到16。

3)绿色部分主要是为了清理queue列表,使其满足GlobalQueue和AccountQueue的限制条件:
如果每个账户的交易数超过了AccountQueue,丢弃多余交易
如果queue的长度超过了GlobalQueue,则把账户按最后一次心跳时间排序,然后依次去除账户中的交易,直到满足限制条件位置
这里提到一个最后一次心跳时间,其实就是账户最近一次交易的时间,用来作为账户活跃度的判断。

4:交易池查看

参数设置
针对txpool有哪些参数项可以设置:
--txpool.nolocals            为本地提交交易禁用价格豁免
--txpool.journal value       本地交易的磁盘日志:用于节点重启 (默认: "transactions.rlp")
--txpool.rejournal value     重新生成本地交易日志的时间间隔 (默认: 1小时)
--txpool.pricelimit value    加入交易池的最小的gas价格限制(默认: 1)
--txpool.pricebump value     价格波动百分比(相对之前已有交易) (默认: 10)
--txpool.accountslots value  每个帐户保证可执行的最少交易槽数量  (默认: 16)
--txpool.globalslots value   所有帐户可执行的最大交易槽数量 (默认: 4096)
--txpool.accountqueue value  每个帐户允许的最多非可执行交易槽数量 (默认: 64)
--txpool.globalqueue value   所有帐户非可执行交易最大槽数量  (默认: 1024)
--txpool.lifetime value      非可执行交易最大入队时间(默认: 3小时)

查看txpool内容

3.jpg

很显然,txpool中由两部分构成:pending和queued。
运行以下代码在本地节点发送两笔交易:

var Chain3 = require('chain3');
var chain3 = new Chain3(new Chain3.providers.HttpProvider('http://localhost:8545'));
var address = "0x745c57ca5318093115d61bbca368XXXXXXXXXXXX";
var account = {address:"0x745c57ca5318093115d61bbca368XXXXXXXXXXXX",secret:"bb673026deda3c3cd0c63f6ccddfb02a7ae320078aa8XXXXXXXXXXXXXXXXXXXX"};
var toAddress = "0x08d4876b33baf2e0f37b92fedec0XXXXXXXXXXXX";
var amount = 0.002;
send(chain3, account.address, account.secret, toAddress, amount, txCount = -1);  //不定义nonce
send(chain3, account.address, account.secret, toAddress, amount, txCount = 1500);//定义了超过现有值的nonce
function send(chain3, fromAddress, fromSecret, toAddress, amount, txCount = -1){
  var mc = chain3.mc;
  var txcount = txCount >= 0 ? txCount : chain3.mc.getTransactionCount(fromAddress);
  console.log("Get tx account", txcount);
  var gasPrice = 25000000000;
  var gasLimit = 100000;
  var value = chain3.toSha(amount, 'mc');
  var gasTotal = gasPrice * gasLimit + Number(value);
  console.log(gasPrice, gasLimit, value, chain3.fromSha(gasTotal, 'mc'));
  var rawTx = {
    from: fromAddress,
    to: toAddress,
    nonce: chain3.intToHex(txcount),
    gasPrice: chain3.intToHex(gasPrice),
    gasLimit: chain3.intToHex(gasLimit),
    value: chain3.intToHex(value),
    shardingFlag: 0, //default is global contract
    chainId: chain3.version.network
  };
  var signedTx = chain3.signTransaction(rawTx, fromSecret);
  mc.sendRawTransaction(signedTx, function(err, hash) {
      if (!err){
          console.log("succeed: ", hash);
          return hash;
      }else{
          console.log("error:", err);
      console.log('raw tx:', rawTx);
      }
  });
}

其中,第一笔交易没有指定nonce,第二笔交易指定nonce值为1500,远超过现有值。

这时我们再看一下txpool中的内容:

> txpool.content
{
  pending: {
    0x6F0b08F13128060102F7C13a2DAB2401d4659c2f: {
      38: {
        blockHash: "0x0000000000000000000000000000000000000000000000000000000000000000",
        blockNumber: null,
        from: "0x6f0b08f13128060102f7c13a2dab2401d4659c2f",
   4.jpg

发现pending的两笔交易已经打包,而queued中的交易仍然没有进入pending,它会等到nonce值1338~1500之间的交易补齐后才会进入pending并被打包。

推荐阅读:

科普 | MOAC区块链基础知识问答[url=http://www.moacchina.net/forum.php?mod=viewthread&tid=5177]http://www.moacchina.net/forum.php?mod=viewthread&tid=5177[/url]
分享到 :
0 人收藏

1 个回复

倒序浏览
gubulin 实名认证  高级会员 | 2019-1-31 10:21:28
技术贴好长
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

QQ|Archiver|手机版|小黑屋|MOAC社区

Powered by Discuz! X3.4 © 2001-2016 Comsenz Inc.

返回顶部