• 注册
  • 开发教程 开发教程 关注:23 内容:62

    MOAC区块链(MOAC BlockChain) 交易池(txpool)

  • 查看作者
  • 打赏作者
    • 开发教程
    • 大版主
      墨客战士

      区块链就是和交易打交道。本文主要从功能角度介绍交易处理过程中的一个重要组成部分:交易池(txpool)。

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

      1

      交易流程

      MOAC区块链(MOAC BlockChain) 交易池(txpool)

      流程分为以下几个步骤:


      • 发起交易:指定目标地址和交易金额,以及需要的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列表中等待执行。

      MOAC区块链(MOAC BlockChain) 交易池(txpool)

      根据不同的目的该部分功能可以分为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内容

      MOAC区块链(MOAC BlockChain) 交易池(txpool)

      很显然,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",

              gas: "0x15f90",

              gasPrice: "0x4a817c800",

              hash: "0x8e2777d752b6181f6e441b43c3affbb33e64f3fba700058630d185289eb7879f",

              input: "0xa6f9dae100000000000000000000000090834c9332720ecf952515379d511af7ac8795a2",

              nonce: "0x26",

              r: "0xd817b133e28ea0c59734f0e506468b109d9421887d2819a10977919e3da9b507",

              s: "0x2e6d5ea55c0a92d20631fbf16015c92a29222f9ce9c8b44ac376c5c35a52071c",

              shardingFlag: "0x0",

              syscnt: "0x0",

              to: "0x8e56f6e6e926d29acc0c76700bfe14f0aea1cbf3",

              transactionIndex: "0x0",

              v: "0xe9",

              value: "0x0"

            }

          },

          0x745c57ca5318093115D61Bbca3687cA02C298427: {

            1337: {

              blockHash: "0x0000000000000000000000000000000000000000000000000000000000000000",

              blockNumber: null,

              from: "0x745c57ca5318093115d61bbca368XXXXXXXXXXXX",

              gas: "0x186a0",

              gasPrice: "0x5d21dba00",

              hash: "0xed64933a5219dc8bdbaa85416293975a4c0b20645f6db9ef884f21e875395a5b",

              input: "0x",

              nonce: "0x539",

              r: "0xe573837157654e2419a3ffec034e4f34193b123449dcbf96d0c9f30e112c05c9",

              s: "0x47bc76d7adb55946f02e3582dceb45e5963dbbb2e9d067fca469616a944b0888",

              shardingFlag: "0x0",

              syscnt: "0x0",

              to: "0x08d4876b33baf2e0f37b92fedec0d50a7a4bd289",

              transactionIndex: "0x0",

              v: "0xea",

              value: "0x71afd498d0000"

            }

          }

        },

        queued: {

          0x745c57ca5318093115D61Bbca3687cA02C298427: {

            1500: {

              blockHash: "0x0000000000000000000000000000000000000000000000000000000000000000",

              blockNumber: null,

              from: "0x745c57ca5318093115d61bbca368XXXXXXXXXXXX",

              gas: "0x186a0",

              gasPrice: "0x5d21dba00",

              hash: "0xb4a62b52c7ba175794a7f004b9205a253953a11228ab938e81f28bc30435e80f",

              input: "0x",

              nonce: "0x5dc",

              r: "0x7e448414436b56c00f35739d998541d2a7a8879c0edd332b24da40707507aac9",

              s: "0x39b863a3027de4eb627130196994d0493a621ffd2cb8093cbe8a55cb32829d9e",

              shardingFlag: "0x0",

              syscnt: "0x0",

              to: "0x08d4876b33baf2e0f37b92fedec0d50a7a4bd289",

              transactionIndex: "0x0",

              v: "0xea",

              value: "0x71afd498d0000"

            }

          }

        }

      }

      >

      出现了三笔交易,pending两笔,queued一笔;其中:

      第一笔交易不是本地节点发送的交易,而是从交易池同步来的交易;nonce值为38;

      第二笔交易就是刚才发送的没有指定nonce的那笔交易;nonce值为1337;

      第三笔交易就是刚才发送的指定nonce = 1500的那笔交易;nonce值为1500;

      两分钟后重新查看:

      MOAC区块链(MOAC BlockChain) 交易池(txpool)

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

      请登录之后再进行评论

      登录
    • 做任务
    • 实时动态
    • 偏好设置
    • 帖子间隔 侧栏位置: