当前位置:网站首页 > 最新资讯 > 正文

走近科学:一笔奇怪的交易竟然引出了NEO区块链浏览器中的问题

0 Scream Scream 2018-10-17 16:38 37850 巴比特

问题现象

neoscan和neotracker是NEO社区最受欢迎的两款区块链浏览器。最近在一笔交易中,Jarvis+发现两款浏览器都存在同一个问题。

在一笔NEO 交易中同时打包多笔转账,比如NEO交易收据值为:

0x2326eb9e7d5dda11ea71c9b4428f4fa036003066134fce8d93195732514ed488 的这一笔交易。在该交易中,一共打包了5笔转账。在该笔交易中,分别向5个不同的地址分别打入了999个JWT资产。在转账结束后,neotracker 和neoscan 两款主流的NEO区块链浏览器上仅可以查询到3笔转账记录,而不是5笔。虽然3笔转账记录上显示已经为地址打入999个JWT资产,但是当使用这3个接收JWT 资产的地址中的任意一个向外继续转账JWT 资产时,会因为余额不足而导致转账失败。

问题分析

下面,neotracker 这款区块链浏览器来查看和分析一下问题。该笔交易的详情,请访问:

https://neotracker.io/tx/2326eb9e7d5dda11ea71c9b4428f4fa036003066134fce8d93195732514ed488

如下图所示,这笔交易是在2018年7月27日下午生成的,交易信息由570个字节组成,被打包在第2551742个NEO区块内。在Transfers一栏中,可以看到一个地址AKNmRJUiB3pwFgNrVYuQ4LdAsfRS9RuKiu,分别给另外三个地址打入了999个JWT资产。

事实真的如此吗?为了继续分析问题,直接在当前交易详细信息页面上打开Script 一栏进行查看,Script 中显示的内容就是打包上传到区块链上的交易字节信息,一共是570个字节。详细信息显示如下:

0000

USHBYTES8   0×0007814217000000

0009

USHBYTES20   0x3c2ecf82be1671982a6cf420845f96003ad12a85

0030

USHBYTES20   0x2781bccf8be5f34c88f904b48a7e478f07c5e41f

0051

USH3

0052

ACK

0053

USHBYTES8   0x7472616e73666572

0062:APPCALL   0xb4cf9f4af5272d209b80ad1a06885e21568a4bfa

0083

USHBYTES8   0×0007814217000000

0092

USHBYTES20   0xfead57b656edda0dbecc5d54b4130cba91dbbf54

0113

USHBYTES20   0x2781bccf8be5f34c88f904b48a7e478f07c5e41f

0134

USH3

0135:PACK

0136:PUSHBYTES8   0x7472616e73666572

0145:APPCALL   0xb4cf9f4af5272d209b80ad1a06885e21568a4bfa

0166:PUSHBYTES8   0×0007814217000000

0175:PUSHBYTES20   0x0272b6b14a339e02b78799acc3e2b3825e25564c

0196:PUSHBYTES20   0x2781bccf8be5f34c88f904b48a7e478f07c5e41f

0217:PUSH3

0218:PACK

0219:PUSHBYTES8   0x7472616e73666572

0228:APPCALL   0xb4cf9f4af5272d209b80ad1a06885e21568a4bfa

0249:PUSHBYTES8   0×0007814217000000

0258:PUSHBYTES20   0x4a866e47b8974937279a405c31c898da6a7adfd1

0279:PUSHBYTES20   0x2781bccf8be5f34c88f904b48a7e478f07c5e41f

0300:PUSH3

0301:PACK

0302:PUSHBYTES8   0x7472616e73666572

0311:APPCALL   0xb4cf9f4af5272d209b80ad1a06885e21568a4bfa

0332:PUSHBYTES8   0×0007814217000000

0341:PUSHBYTES20   0xb18b4948eaabcc8bde828e005a0554d09e4ecd58

0362:PUSHBYTES20   0x2781bccf8be5f34c88f904b48a7e478f07c5e41f

0383:PUSH3

0384:PACK

0385:PUSHBYTES8   0x7472616e73666572

0394:APPCALL   0xb4cf9f4af5272d209b80ad1a06885e21568a4bfa

先看第一行,PUSHBYTES8 0×0007814217000000 按照NEO 虚拟机的指令,这是把一个8字节的数字压栈。0×0007814217000000 是按照从低位字节到高位字节的顺序排列的。实际上,这个数字应该是十六进制的0×1742810700,也就是十进制的99900000000。由于JWT 的精度是8位,所以这个数字实际上代表的是999个JWT。

第二行PUSHBYTES20 0x3c2ecf82be1671982a6cf420845f96003ad12a85 是往NEO虚拟机中压栈了20个字节的数组,这个数组实际上就是NEO地址对应的公钥,根据这个公钥可以反算到NEO的地址。同样的,第三行也是一个地址,是用来发放JWT 的NEO地址。

指令PUSH3 用来告诉NEO 虚拟机这次智能合约的调用有三个参数,也就是刚才压入堆栈的三个参数。PACK 告诉NEO虚拟机把这三个参数打包。

随后的指令PUSHBYTES8 0x7472616e73666572 是入栈了一个8个字节的数组。这个数组实际上是一个字符串,也就是” transfer”。即告诉NEO虚拟机调用智能合约中的transfer 方法。

第七行,APPCALL 0xb4cf9f4af5272d209b80ad1a06885e21568a4bfa 是告诉NEO 虚拟机要调用的智能合约的地址。

以上的OpsCode 形成了这个交易中的一笔转账,近似内容重复了5次,也就是标明这笔交易打包了5笔转账操作。

那为什么在交易的详情中只能看到三笔交易呢? 这就是亟待研究的问题了。

根本原因

很显然,既然是同样的转账操作,每笔转账操作除了接收JWT 的地址不一样以外,其他部分都完全一样。这包括调用的智能合约地址、转账方法、转出JWT 资产的地址和转的JWT资产的数量。在这种情况下,有些转账能够成功,有些转账不成功。就要从其他方面入手了。

可能一:用于转账的JWT 地址没有足够的JWT 资产额度。

经过确认,改地址是有充足的JWT 资产用于转账的。通过NEP-5 合约的balanceOf() 方法就可以查询到。

可能二:用于执行转账的Gas 不足。

在NEO 链上,执行智能合约是需要消耗一定的Gas的,Gas的消耗量与智能合约执行的时间成正比。为了方便简单的智能合约调用,NEO允许每次执行合约时给出免费Gas 的额度。这样一来,即使是一个不含有任何资产的NEO 地址也可以调用简单的智能合约了,降低了智能合约的使用门槛。这个免费的额度可以通过查询NEO 源代码了解到。参见代码:

https://github.com/neo-project/neo/blob/master/neo/SmartContract/ApplicationEngine.cs#L45

可以了解到Gas 的免费额度是10。

现在就要看看这笔交易到底能消耗掉多少Gas了。实际上智能合约Gas 的计算是根据智能合约使用的系统调用方法的价值和次数来确定的。统计调用智能合约消耗的Gas 代码在ApplicationEngine.cs 文件的322 -327 行。可以访问下面的链接直接查看:

https://github.com/neo-project/neo/blob/master/neo/SmartContract/ApplicationEngine.cs#L322-L327

在代码中,执行引擎逐个遍历系统调用指令,然后根据每个系统调用指令的单价乘以费率进行累加,得到该交易的Gas 消耗量。

费率(ratio) 是一个固定值,通过查询NEO 源代码可以知道是100000。由于Gas 的精度也是8位,所以这个值代表0.01个Gas。每个系统调用的价格是在GetPrice函数中确定的。代码在ApplicationEngine.cs 的351-380行。可通过下面的链接进行查看:

https://github.com/neo-project/neo/blob/master/neo/SmartContract/ApplicationEngine.cs#L351-L380

如果仔细看一下,发现其实执行智能合约好像并不贵。PUSH压栈操作一律免费(return 0),调用智能合约是10,也就是0.1个Gas。5笔转账加在一起,消耗的Gas并没有多少。但是,这里有一个隐含的调用,就是验证签名。验证签名是用来在transfer 方法中校验当前发起转账调用的一方是否就是当前转出JWT 资产的地址的所有者。这是处于安全的考虑,如果不对智能合约的发起方做这样的调用,那么就会出现不具备JWT 资产的第三方C掏一点点Gas 就把拥有JWT 资产的A的JWT 资产莫名转给了B。因此,在智能合约的转账操作中,校验执行合约发起方的签名是绝对有必要的!校验签名收费1个Gas(100 * 100000)。5笔转账操作单是校验签名就已经消耗掉一半的免费额度了。实际上,一笔转账操作所花费的Gas大约是2.742,5笔转账操作加在一起就超过10个Gas 的免费额度了。

这笔交易其实是执行失败了。失败的原因是调用智能合约时没有足够的Gas。免费的Gas额度仅够3笔转账操作的。

按照正常的NEO 处理交易的规则,一旦交易中途执行失败,就应该返回FAULT_BREAK错误提示,交易结果数据不能打包上链。但是由于neotracker 和neoscan 两个区块链浏览器没有正确地处理该笔交易,导致区块链浏览器显示该笔交易中的前三个转账操作成功,后两个转账操作失败(不显示)。

问题解决

这个问题出现以后,引起Jarvis+的高度重视。及时地分析了问题,并对转账失败的5个地址补发了JWT 资产。

从这个问题上可以得出下面的最佳实践:

在免费的资产转帐操作中,一次最好不要同时给三个以上的地址进行转账。否则,最好是在调用资产转账智能合约时给予足够的Gas额度。

对于NEO区块链浏览器neoscan 和neotracker 来说,需要他们及时地升级NEO 节点到最新版本。避免类似情况再次发生。

Jarvis+已经在Github上向两款NEO区块链浏览器提交了这个问题。详情请参考下面链接地址:

向neoscan 提交的问题链接:https://github.com/CityOfZion/neo-scan/issues/322

向neotracker 提交的问题链接:https://github.com/neotracker/neotracker/issues/53

有任何问题,都可以通过官方渠道联系我们哦~

Scream

Scream

TA很懒,啥都没写...

©2018 Changfu.org All Rights Reserved | ©长富财经版权所有