您正在查看: EOS-优秀转载 分类下的文章

数字身份,Scatter 与 RIDL简介

Scatter 目前是 EOS 生态之中不可缺少的一部分,尽管,你可能并不知道它是什么。

你每日使用MeetOne、TP等 EOS 手机钱包玩游戏、访问dApps的时候,就是得益于 Scatter 这一项目所提供的协议。另外,Scatter 也创建了桌面版应用和Chrome插件形式的钱包(已经下架),也是 EOS 社区之中所推崇的最安全的 EOS 钱包之一。

不过,Scatter 的野心不止于成为EOS生态中使用最广的钱包而已,在数字身份领域的探索,也值得关注。自从EOS主网启动之前,Scatter 团队就发布了他们的设想,而如今看来,在数字身份和信任网络领域的探索,他们正在一步一步走来。

当然,值得一提的是,项目与代币的价格,并不会一一对应,本文不构成任何投资建议,仅供希望了解RIDL的朋友参考。另外,在数字身份领域,除了Scatter之外,也有包括IBM、MyKey Lab团队在内的国内外多个公司团队在进行探索和实验,也期待未来更多的成果出现,找到数字身份ID和可信网络这一圣杯。

数字身份有什么用?

对这一赛道,我现在所做的有限的了解,并不足以支撑我作出更为全面的判断,因此,此部分所谈仅供参考,如有错误,欢迎随时指正批评。
在互联网初兴起之时,有个段子:你不知道屏幕另外一侧的聊天者,是人还是狗。

在去中心化的网络之中,增加了数字化资产这一层,在交易之时,如何确认对方的身份?如果你使用一个dapp,除了在网络上搜索相应信息之外,是否有方法可以快速了解到这个dapp靠不靠得住?
如果你使用某个交易所,在使用之前对此一无所知,是否会恶意拔网线?是否会暴露你的隐私?…
另外,如何证明你是你?

如上的种种问题,都是涉及到两个事情:

  • 交易参与的实体(网站,账号,地址,公钥,dapps,合约等等)是否是你所预想的交易对象?
  • 交易实体,是否可信?

安全和可信,降低交易风险,不需要借助于信念或者运气来参与交易,是可信网络和数字身份这一领域所重点关注的问题之一。

圣杯

咕噜曾于2018年9月发文,提到区块链领域的圣杯之一:
基于密码学的身份体系,例如自主身份(Self-soveignein Identity),以及衍生出来的信任网络(Web-of-Trust)
而对于加密身份体系,咕噜认为,“是今后web3.0的入口,是每个人的必需品”。他主要看好两个部分:

加密身份体系的成熟,有助于加密资产在互联网用户中的渗透,以及区块链应用的普及
基于加密身份体系所形成的信任网络(network of trust), 通过信息交叉验证的方式,对于当前误解的新闻和信息造假的问题,提供了最可靠的方式。
在加密身份体系(数字身份)和可信网络的领域,已经有许多的玩家。除了所提到的IBM、scatter团队和MyKey团队以外, BitPortal的创始人在一次访谈之中也提到了他个人所关注的几个团队:

  • ETH上的ERC-725
  • 国内的Ont
  • 国内的BTM
  • 国外的Sorvin

其中Ont和Sorvin都会提供去中心化的KYC系统,对合规和一些落地的应用比较有益,BTM的身份方案基于ODIN,主要用于标识资产

Scatter 团队发文提到说即将发布RIDL,不过按照实际情况看来,上线仍然要等1~2个月。
在RIDL实际上线之前就写文章谈论,难免因为资料有限理解有误,不过,好奇心趋势下,我仍然试着聊聊 RIDL。也随时欢迎朋友们一起交流和指正。
昨天谈到了数字身份和信任网络这一领域的概况,今天就整体聊聊 RIDL 这一EOS社区中的数字ID产品,谈谈它的设计思路。
我所写的任何文章,都不构成投资建议,本文也不例外,仅为提供信息参考之用。

RIDL 方案:为区块链添加声誉和数字身份层

RIDL是什么?


RIDL 的全称有些拗口:声誉与身份层(Reputation and Identity Layer)。
用来解决:

  • 在区块链上的数字身份问题(主要是基于EOSIO的区块链,也会支持其他链)
  • 某个身份实体的信誉问题,公开可见,为安全交易提供参考

RIDL 有两重含义:

  • RIDL是产品,通过RIDL 可以对网站/用户/合约等实体进行评级,使用者也可以根据对应的评级来得到
  • RIDL 也是代币名称,在RIDL中使用该代币进行评级

RIDL的运行机制

这个RIDL上线后,围绕声誉和数字身份,是怎么运行的?这部分简单一聊。主要分为三个过程:

  • 注册一个数字身份,由RIDL所承载,全网唯一
  • 通过“大众点评”方式,形成对于数字身份实体的综合评价。评分会消耗RIDL
  • 得到的评级,链上可查,供交易时参考。

展开聊聊。

1. 注册数字身份(Identity)

简单来说:在RIDL中,引入了身份(Identity)这一概念. 对身份的一个通俗解释:

什么是身份?身份是一个“壳”,壳里装的是“我”。
—- MyKey 白皮书

在 RIDL 中,一个网站、app、合约、用户等实体,都可以注册来绑定一个身份,并接受评价。
你可能留意到: Scatter 桌面版中有一个设置为:Identity,即身份。这是为RIDL所设计的。

目前,其实没多少用处。我们用Scatter 去与dApp交互的时候,不会见到身份的存在,而是要选择对应的EOS 账号。

而在RIDL发布之后,这一身份就派上用场了。 可以将这个身份理解为对账号的一层封装,该身份可以关联多个 EOS 账号。
有几个特点:

  • 付费获得, 年费 $25
    按照Scatter团队说法, 是为了避免羊毛党或者刷评价者恶意占用。如果次年不续费,则身份所积累的信誉会清零,任何人都可以付费去注册该身份.
    此前在EOS上线前 Scatter 曾经做过预注册的方式,可以通过MetaMask 来预留身份 100年。在 RIDL 上线后,可以支付100 RIDL代币,领取所预留的身份。
  • 名称全网唯一
  • 网站、app、合约、用户等实体,都可以绑定身份
  • 可以对身份进行评价,并且该评价是在区块链上公开可查询的,下文会详述
  • 注册了身份之后,可以使用 RIDL Defender,在存在欺诈和恶意行为可能时会提供预警,保护交易的安全

2. 对身份的评级

这部分我认为是RIDL的重点:如何对实体进行评价?评分系统是否可信?
并且,需要在两者之间进行权衡综合:

  • 激励用户参与对某个主体的评价
  • 避免用户恶意刷评价,且消除恶意负面评价的影响

这里,RIDL 采用了评级挖矿的方式来设计。下面会展开聊一聊。先看看身份评级这部分功能的一些特征。

设想如下场景:
你在玩某个dApp游戏,感觉体验很不好,想对此进行评价。

  • 首先,只有注册了身份之后,才能够对其他对身份进行评级。注册身份需要消耗代币,门槛不低,Scatter 团队是希望以此打压羊毛党恶意注册。是否会有效?还需观察。
  • 其次,评价会分为多个维度,最多五个维度。如下图所示,可以从多个角度去打分:


评价需要消耗RIDL代币

看白皮书中的例子:

这是给某个身份(比如,一个交易所)的评分,总计的评级是 10 个RIDL代币,意味着,这一次评级,消耗了你10个代币。
许多人肯定会疑问:对我有什么好处?为何要花费代币去评价?
看看Scatter团队的回复:

Rami 给出的理由是:

  • 用户参与了一个系统,允许我们定义在线安全性。这本身是个卖点。
  • 如果你是第一个评价者,或者在前一个评价者的矿工资格过期(3个月)后,你可以成为“评级矿工”,获得RIDL的代币收益。
  • 你评价之后,会成为“最后评价者”。如果有个人在你之后评价成为了评价矿工,你能够分到一部分的评级RIDL代币。
  • 参与评级,也能够增加你的身份中的RIDL额度。(注:你能够得到的评级是有限的,如果额度扩大,意味着你可以得到更多的RIDL)使用越多,投票得到更多的尊重。

简单说来:
参与评价,可以以挖矿的方式,分得 RIDL 代币奖励;并且,每个月 RIDL 会进行空投,给符合一定标准的评价者(至少评价了10个不同的身份实体)从RIDL的保留代币池中空投一部分RIDL代币给参与评价者。
被评价的身份主体会累积 REP
你花费了 10 个RIDL 代币,评级了某个交易所;那么,在RIDL系统中,这个交易所绑定的身份,会立即得到10个REP。
REP 不可以转账,不能够交易,只是一个标记工具,用来表示评价该身份实体所消耗的RIDL总量。

3. 获取评级信息,供安全交易参考

RIDL上线后,会提供两个工具:

  • RIDL网页
  • RIDL的浏览器插件


通过这些入口,可以查看某个身份主体的评分详情。并且,在 Scatter 内部,还会通过 RIDL Defender 的方式,来对于交易进行预警。比如,如果你准备发起交易的对象,是被RIDL系统中标记为诈骗,则会提示你。

Scatter 发了一个视频,演示插件的效果,可以看看:

https://twitter.com/i/status/1096053747766226944

RIDL 代币机制

最后,顺带聊下RIDL的代币发行情况。

值得一提的是,Scatter 团队没有选择直接给自己留代币份额,而是选择了类似 EOS 发行的众筹方式(他们称之为捐赠),拿出来了16.6% 的代币,即2.5亿 RIDL,用于 Scatter 开发基金。

如果参加”捐赠”,则可以按照参与当期EOS的比例,分得一定的 RIDL 代币作为酬谢。

为期两年共730天,每期12个小时,每期的上限为2千个EOS,释放17万 RIDL。
下图可以看到,当前的 RIDL “捐赠”是第428期。预计还有10分钟结束。


通过这种方式,使得RIDL的价格维持相对平衡的水平。交易所中价格高,可以通过“捐赠”方式得到更多的RIDL代币,拿去交易所卖掉;交易所价格低,则会刺激RIDL的购买者放弃“捐赠”方式得到代币,而直接去交易所购买。

链接为: https://ridl.get-scatter.com/#/

参考信息来源

上周 Scatter 发文: Scatter即将发布RIDL,剑指三大圣杯之一——数字身份ID

RIDL 白皮书: 阅读白皮书

转载自:https://mp.weixin.qq.com/s/egoWhoYN0yRWUv2l9arH0A (EOS42)

昨日EOS RAM偷窃漏洞复盘

昨日EOS紧急更新了一个偷窃RAM漏洞的补丁,今天和远航讨论到该漏洞,远航提供了昨日官网修复该漏洞的patch,于是又有了想复现漏洞的冲动,最后验证并在测试网络还原了该漏洞。

漏洞详情
该漏洞是因为EOSIO系统对于合约inline调用合约内的其他函数不会进行权限检测,从而恶意合约可以使用任何其他账号的权限调用该合约的其他方法。比如下图:

漏洞复现操作如下

漏洞解决
合约内inline调用也需要eosio.code授权

漏洞复现源码
https://github.com/itleaks/eos-contract/tree/master/stealram2-exp

附录
再次感谢远航提供资料

转载自:https://mp.weixin.qq.com/s/gYEuGB2_fZf8OHlQqGBJ8A

一张图演绎EOS DApp各种攻击方法

你没看错,我今天要回顾的是DApp的各种攻击方式,其实这个主题已经有好多人写了,且都还不错,只是最近正好有大佬要深入了解,因而作者尝试从技术角度来解读这些攻击方法。

菠菜游戏运行机制

菠菜游戏都是钱袋子,所以目前的DApp攻击基本都是针对菠菜游戏,有钱的美女人人都爱很正常。
要理解攻击方法,就必须先了解DApp的工作方法。我们知道,菠菜游戏都离不开筹码(EOS),因而将筹码转账给DApp是核心一环。不像以太坊调用智能合约函数的同时通过value字段直接给智能合约转账ETH,EOS下给智能合约转账EOS就相对复杂一点。可以通过如下两种方式实现:

1. 用户授权eosio.code权限给DApp, DApp执行eosio.token合约的transfer函数实现用户给智能合约转账EOS。

该方法用户敞开的风险太大,因为一旦将eosio.code权限授予给DApp, DApp就完全控制了用户账号的,其中就包括用户的EOS资产,DApp可以任意转出EOS。

2. 用户直接调用eosio.token的transfer函数给DApp账号转账EOS

由于eosio.token合约的transfer合约有require_receipt逻辑,require_receipt(to)会调用to(DApp合约地址)的transfer函数然后DApp就可以执行自己的代码。


由于第一种方法缺陷明显,目前菠菜DApp都采用第二种方法,具体流程如下:

函数调用流程:
 eosio.token::transfer(from, dapp, memo)
            ->dapp::apply(receiver, code, action, ...)
                   ->dapp::transfer(from, dapp, memo)
                              ->…

可见用户的一个transfer动作,其实大概经历了5个阶段,这5个阶段都可能出bug进而导致被攻击。接下来就从这个几个流程来分析下各种攻击方法。

eosio.token::transfer阶段

该阶段处于系统智能合约eosio.token的逻辑,肯定是没有bug的, 就算有bug,也是DApp没有用好,所以该阶段无漏洞。

apply阶段攻击(假EOS攻击)

“假EOS”攻击出现于EOSBet平台上 ,后又扩散到NewDex交易所。
该攻击的核心是恶意合约完全模拟eosio.token合约,并发行假“EOS”代币。然后给DApp转假的"EOS“代币,和eosio.token逻辑一样,恶意合约会通过require_receipt(dapp)进入dapp的apply, 再调用transfer函数,由于transfer函数里已没有receipt调用者信息,只能识别代币的名字是否“EOS”,而这个检测很明显可以通过,进而实现了用“假EOS”玩DApp目的。具体如下流程如下:

evilcontract::transfer(from, dapp, memo)
            ->dapp::apply(receiver, code, action, ...)
                   ->dapp::transfer(from, dapp, memo)
                              ->...

解决方案是Apply函数里增加来源检测,具体如下:

transfer阶段攻击(假EOS转账通知攻击)

假EOS应该算是开启了DApp攻击热潮,很快EosBet遭受了第二波攻击,即"假EOS转账通知攻击", 那这个又是怎么回事呢?
有了“假EOS“攻击的经验,DApp们都加强了防备,都在Apply添加了检测,这道锁是严实了。于是黑客们盯上了下一步transfer函数,于是”假EOS转账通知攻击"产生了。
“假EOS转账通知攻击”攻击是指攻击者通过eosio.token给自己的智能合约账号(evilcontract)转真EOS, eosio.token合约会通过require_receipt代码调用恶意智能合约transfer函数。但是恶意合约会通过require_receipt主动调用dapp, 导致dapp的transfer函数被调用。由于最开始调用的确实是eosio.token合约,导致code是eosio.token, 从而合法通过了上述Apply中的"eosio.token"检测逻辑进而顺利进入dapp的transfer函数。

eosio.token::transfer(from, evil, memo)
            ->evilcontract::apply(receiver, code, action, ...)
                   ->evilcontract::transfer(from, evil, memo)
                              ->dapp::apply
                                        ->dapp::transfer(from, evil, memo)

从上面可以看出,该攻击是完美避过Apply检测逻辑,但是还是会留下蛛丝马迹,这个就是transfer的参数to是‘evil'而不是dapp,因而在transfer函数里添加to是否为本合约检测逻辑即可防御攻击,具体如下:

详情请查考【Eosbet再遭攻击,亟待官方的权威开发指南

receipt和reveal阶段攻击(随机数攻击)

receipt和reveal才算进入DApp的核心逻辑,这部分核心逻辑就是负责收集处理随机数和开奖。因而这两个阶段的攻击其实就是攻击随机数,即预测结果或者产生有利结果的随机数。
目前传统App使用的随机数也是伪随机数,存在被攻击的可能,但是概率极低。而目前区块链里的随机数攻击是指那些能稳定预测结果或者影响结果的攻击,其实不属于伪随机数的问题,而是编程逻辑的问题。
目前主流菠菜DApp基本都采用了 tapos_block_prefix, tapos_block_num 做为随机数种子。这两个参数都跟ref_block_num有关。

tapos_block_num = ref_block_num & 0xffff
tapos_block_prefix = getBlock(ref_block_num).ref_block_prefix

那ref_block_num这个值如何得到的呢?
它是我们调用cleos或者eosjs发起交易时指定的,如果不指定,则为last_irreversible_block_id

到这里,你知道这个变量的用处了吧,该参数可以减少分叉影响。因为大部分交易都是将ref_block_num指定为last_irreversible_block_id,那些不包含last_irreversible_block_id的分叉自然交易量少,所以哪怕系统出现分叉,但是由于交易量少,影响也不会很大。
好了,回到随机数,既然这个ref_block_num客户端即用户可以指定,那就存在可控性,攻击者就可以选择ref_block_num,进而提前计算出随机数。所以这种使用历史数据的随机数不能算做随机数,自然不可能成为主流DApp的随机数。
事实上,从一开始大部分DApp就是通过defer action的方式延迟开奖,defer action里的ref_block_num是下注区块(block1)的信息,该区块信息在下注时是未知信息,即实现了使用未来数据作为随机数。具体逻辑如下:

到此,DApp其实已经算相对安全了,已经能够称得上伪随机数了,接下来的攻击理论上基本都是属于概率攻击,即猜测或者影响下注区块信息,这个属于概率学上的攻击,不在此篇攻击分析范围。但事与愿违,DApp开发者们为了出奇制胜创新完美,自己想了不少额外的方法,进而就被攻击,比如如下几个攻击。

Eosbet第一次随机数攻击(使用已知数据作为随机数)

尽管延迟兑奖,但是还是使用老的ref_block_num,即不使用block1而是将block0作为ref_block,进而导致随机数被预知。

Eosbet第二次随机数攻击(碰撞账号余额)

有了第一次攻击经验,Eosbet学好了,采用下注block作为ref_block,但是鬼使神差的将玩家的余额作为随机数。这个想法的出发点估计是随机数输入越多越好越随机,殊不知却掉坑了。开发者没想到余额不像其他随机数数种子,他的值是可以变化的。攻击者完全模拟dapp的代码,
也使用延迟action,只不过会不停修改balance然后尝试开奖逻辑,直到碰撞到中奖结果。

同时受该攻击的还有EosDice。

同步开奖攻击(回滚攻击)

所有同步开奖的DApp都会遭遇回滚攻击。比如Dice3D和LucyGo
回滚攻击的思路是恶意合约在dapp的action之后插入一个盈利检测action, 该action在reveal action之后执行,自然可以通过检测合约的余额来判断是否盈利。如果不盈利则触发assert从而导致整个交易回滚,最开始的转账action也作废即筹码回滚,攻击者不损失任何EOS,从而达到稳赢的结果。具体流程如下:

该攻击方法的核心代码是合约中获取账号余额,最简单的方法就是直接访问eosio.token的accounts数据库,但是要获取里面的balance余额,就必须保持一模一样的数据结构,因而拷贝eosio.token.hpp文件到该合约目录是最简单的方法。
详情请查考【EOS游戏合约遭受回滚攻击

总结

留意receipt来源,必须延迟开奖,不要引入其他可控的随机数因子

转载自:https://mp.weixin.qq.com/s/FSJzAQt6W5KQX1UZRBqUYA

智能合约之 eosio.cdt 我们需要知道的那些事

eosio.cdt 在 1.2.x 和 1.3.x 的改动比较大, 虽然虚拟机是向后兼容的, 但是为了避免意外情况, 我们都会将陆续将合约代码升级。下面来介绍一下大致的改动

# 安装 eosio.cdt, 因为 llvm 库比较大, 所以执行 clone 的时候比较慢
$ git clone https://github.com/EOSIO/eosio.cdt.git
$ git submodule update --init --recursive
$ ./build.sh
$ sudo ./install.sh

1.2.x 和 1.3.x 的区别

eoslib C API

uint64_t 别名 account_name, permission_name, scope_name, table_name, action_name 全部移除, 新增 typedef capi_name uint64_t
symbol_name 别名移除,用 symbol_code 代替
移除 time , weight_type typedefs
移除 transaction_id_type, block_id_type typedefs
checksum160 -> capi_checksum160, checksum256 -> capi_checksum256, checksum512 -> capi_checksum512, public_key -> capi_public_key, signature -> capi_signature
移除掉未实现的 api : require_write_lock 和 require_read_lock

eoslib C++ API

移除 bytes typedefs
移除文件 eosiolib/types.hpp
移除文件 eosiolib/optional.hpp, 用 std::optional 代替
移除 eosiolib/core_symbol.hpp 文件, 以后合约需要自行声明 core_symbol
增加文件 eosiolib/name.hpp

eoslib/types.hpp

将 typedef eosio::extensions_types 移到 eosiolib/transaction.hpp
移除掉对 checksum struct 的 == 和 != 的重载
移除掉 API eosio::char_to_symbol, eosio::string_to_name, eosio::name_suffix, 都整合进了 name struct
移除掉宏命令 N(X), 重载运算符 ""_n ,例如 "foo"_n 或者 name("foo") 来转换成 name struct 类型
将 eosio::name struct 的定义 和 ""_n 运算符 移至 eosiolib/name.hpp
ps: 读者可以使用 #define N(X) name(#X) 来减少代码的改动了哈。

eosiolib/name.hpp

移除name 显式 隐式转换成 uint64_t
添加 enum class eosio::name::raw : uint64_t 用于从 name struct 隐式转换成 raw
添加 bool 类型转换,会返回 name struct 转化成 uint64_t 是否为 0
构造函数都用 constexpr, 确保 name struct 实例化时,都会给 value 赋初值
添加新的 constexpr 方法 eosio::name::length, eosio::name::suffix
添加 name struct 的比较函数

eosiolib/symbol.hpp

移除 eosio::symbol_type strcut , 用 eosio::symbol class 代替
添加 eosio::symbol_code struct
移除掉 eosio::string_to_symbol, eosio::is_valid_symbol, eosio::symbol_name_length 方法,都整合进了 symbol_code struct
移除宏命令#define S(P,X) ::eosio::string_to_symbol(P,#X), 直接实例化 symbol class eg: symbol(symbol_code("SYS"), 4) or symbol("SYS", 4)
重构 eosio::extended_symbol struct

eosiolib/asset.hpp

构造器现需要显式传入 quantity 和 symbol, 不再有默认值

eosiolib/contract.hpp

Rename EOSIO_ABO to EOSIO_DISPATCH, 更加明确的表达该宏指令的作用
根据 contract 的改动重构 EOSIO_DISPATCH

eosiolib/multi_index.hpp

索引不能直接用 name struct 需要使用 eosio::name::raw
multi_index code 不再使用 uint64_t, 使用 eosio::name

eosiolib/singleton.hpp

同 multi_index, 用 eosio::name 替代 uint64_t

eosiolib/action.hpp

添加 inline function: eosio::require_auth, eosio::has_auth, eosio::is_account
重构 eosio::permission_level, 用 eosio::name 替换 uint64_t
移除 宏命令 ACTION,整合到了 eosio.hpp
新增 action_wrapper struct, 它的出现,让我们对inline action 的使用更加便利化,相当于把 inline action 封装成一个 struct,直接实例化便可以发送一个 inline action, 下面会写例子。

eosiolib/permission.hpp

修改 eosio::check_transaction_authorization 参数类型 std::set to std::set , 使得能和 eosio 的 public_key 兼容。
eosio::check_permission_authorization 参数 account, permission 类型从 uint64_t 修改成 eosio::name

eosiolib/ignore.hpp

新增 ignore struct, 会让ABI 生成对应的类型, 但datastream 不会去序列化它
新增 ignore_wrapper, 方便其他合约调用声明的 action。

下面我们挑些改动比较大的地方来说下。

1.移除 uint64_t 的多数别名,只留下了一个 capi_name。

其中最大的地方当属 去掉了 uint64_t 的别名,需要用 name struct 来代替, 不应该用新的别名 capi_name。 不说了,笔者改代码改到想哭了。但为什么要做这个改动呢, 目前对于 account_name 等所使用的都是 implicit, 这意味着可能有一些 bad implicit。
Eg:

//@abi action
void hi(){
  name acc = name{10};
  print(acc==10);
}

我本意是要判断 两个 name struct 是否相等, 但是隐式转换使我直接比较整数 10 也能返回 true。
所以重构了 name struct,消除了这种风险。
这次的改动也变得比较偏面向对象思维, 像 eosio::char_to_symbol, eosio::string_to_name, eosio::name_suffix 都被整合进了 name struct 里面。
symbol 和 symbol_code 也被重构了。宏命令 S 被移除,不能直接用 S(4, SYS) 去声明一个 token symbol, 要用 symbol(symbol_code("SYS"), 4) or symbol("SYS", 4)去实例化一个symbol 对象, 也将一些针对 symbol 的函数整合进了 class。

2.重构了contract.hpp , EOSIO_ABI 修改成 EOSIO_DISPATCH

contract( name receiver, name code, datastream<const char*> ds ):_self(receiver),_code(code),_ds(ds) {}

构造函数增加 code 和 ds 参数。增加 ds 参数是为了方便我们手动解析数据。 这跟后面要说到的 ignore struct 有比较大的关系。
这种改动也意味着我们重写 apply 的方式要改动.
Eg:


extern "C" {
  void apply( uint64_t receiver, uint64_t code, uint64_t action ) {
    auto self = receiver;
    // 拦截 失败的 deferred_trx
    if( code == "eosio"_n.value && action == "onerror"_n.value ) {
      const auto act_data = unpack_action_data<onerror>();
      auto sender = uint64_t( act_data.sender_id >> 64);
      if( sender == self){
        test bos(eosio::name(receiver), eosio::name(code),datastream<const char*>(nullptr, 0));
        bos.resend( act_data.unpack_sent_trx(), uint64_t( act_data.sender_id) );
      }
    // 拦截 eosio.token 的 EOS 转账操作
    } else if ( code == "eosio.token"_n.value ){
      test bos(eosio::name(receiver), eosio::name(code),datastream<const char*>(nullptr, 0));
      const auto t = unpack_action_data<transfer_args>();
      if(t.from.value != self && t.to.value == self){
        bos._transfer(t.from, t.to, t.quantity, t.memo);   
      }
    }else if ( code == self || action == "onerror"_n.value ) {
      switch( action ) {
        EOSIO_DISPATCH_HELPER( test, (hi))
      }
    }
  }
}

3. ignore struct , ignore_wrapper 和 action_wrapper 的使用

在 action 的参数加上 ignore struct, 会告诉虚拟机,不要解析此数据, 让自己手动解析。
使用 action_wrapper 把 hello:hi action 包装起来。
使用inline action 时,用 ignore_wrapper 表明该参数是一个 ignore 类型。
Eg:

#include <eosiolib/eosio.hpp>
#include<eosiolib/ignore.hpp>
using namespace eosio;

CONTRACT hello : public eosio::contract {
 public:
   using contract::contract;

   ACTION hi( name user, ignore<uint64_t>, ignore<std::string>) {
     print_f( "Hello % from hello", user );

     // 读取 ignore 数据。
     uint64_t test;
     _ds >> test;
     printui(test);
     std::string str;
     _ds >> str;
     prints_l(str.c_str(),str.size());
   }

   // 用 action_wrapper , 把 hello::hi action 包装起来
   using hi_action = action_wrapper<"hi"_n, &hello::hi>;

   ACTION inlineaction( name user, name inlinecode ){
     print_f( "Hello % from send_inline", user );
     // constructor takes two arguments (the code the contract is deployed on and the set of permissions)
     // 实例化 hi_action, 并进行调用。
     // inlinecode 参数及对应的 hi action 的合约账号。
     hello::hi_action hi(inlinecode, {_self, "active"_n});
     hi.send(user,ignore_wrapper(22),ignore_wrapper("asdfa"));
   }

};
EOSIO_DISPATCH( hello, (hi)(inlineaction) )

结论:两个版本的主要改动是消除隐式转换的风险。 也重构了一些模块的代码, 变得更加直观。 新增了 action_wrapper struct, 使得 inline action 的使用更加方便快捷。

转载自 https://segmentfault.com/a/1190000017092129#articleHeader15

谈谈 EOS 的出块时间,不可逆时间,BFT

EOS出块时间

我们知道,新生产节点必须基于上一个区块生产新区块,因此新生产节点必须在指定的时候内接收到上一个区块的内容,否则只能跳过(只能基于上上一个区块生产)。如果出块时间太短,新节点很大可能接收不到上一个区块的内容,进而频繁出现跳块。只要有跳块,系统就会出现临时分叉,尽管EOS的DPOS的定时出块和最长链共识让系统很大可能最终达成共识,但是也会造成更多缺块,进而降低了有效单位出块数量,得不偿失。

因而一个合适的出块时间就显的很重要了,比如STEAM, BTXS的出块时间就设置为3S。那凭啥EOS能将出块时间设置为0.5S呢?因为EOS做了如下改进:

  • 1个生产节点连续出12个块
    1个生产节点内的12个块不存在接收等待问题,是0等待,肯定也不存在跳块问题。比如生产区块2时,肯定能拿到区块1的数据
  • 21个生产节点的出块顺序是固定的,且直连
    当A个节点完成12个区块时,系统会切换生产者,新的生产者B就需要通过网络接收上一个生产者的区块。由于A的区块是生产一个就广播一个,12区块传播到生产者B的可用时间最短, 由于网络时延和拥堵问题,B不一定能接收A的12区块。因此为了减少跳块,必须降低A->B的时延。由于STEEM,BTSX中,生产节点是随机排序的,A->B的时延是不确定的,可能A在美国,B在中国,美国和中国的来回传播就可能需要600ms, 同时A和B可能没有直连还需要中间节点跳转才能传播,时延就更久了,因而需要设置一个比较大的出块时间,最后测试调试下来,STEEM和BTXS就设置了一个比较合理的3S经验值。EOS为了减少相邻两个节点的时延,按照节点之间的时延安排出块顺序,即时延小的安排在邻近,比如A安排为香港,B为中国就比A是美国,B为中国好太多。同时,由于这21个节点的信息是透明的,这21个节点可以直连,进一步降低传播时延。有了这两个改进后,0.5s变为可能了。当然这种固定出块顺序的确定性也带来了不安全因素,比如攻击者可以准确预估每个出块节点,就可以更容易发起攻击行为。

然后我们来看下节点地域和实际出块顺序:
![]()
中国的节点没有将服务器部署在中国大陆的,要么在香港要么在日本,新加坡,甚至美国。

EOS区块不可逆时间

EOS中,21个生产节点轮流出块。一个区块是通过后续节点基于该区块的生产行为来间接确认的,为了实现BFT级别的不可逆,自然需要得到2/3+1的节点确认。由于每个节点生产12个区块,所以需要12(2/321+1)=12*15=180个区块确认。因而大家第一感觉是1个区块只需要后续180个区块即可变成不可逆状态。然而,如果大家在EOS区块链上查询区块的实时信息,就会发现一个区块都是在300多区块的时候才会被标识为不可逆状态。这是因为区块的确认分为两个阶段,第一个阶段是pre-commit阶段,该阶段需要接受2/3+1个节点的确认表明,超过2/3节点认可该区块。但是此时并不意味着超过2/3的节点已经了解到这个2/3确认信息。因而再次需要2/3的commit签名确认过程。两阶段签名确认流程类似下图:

EOS的特殊BFT

常规的BFT都是生产一个区块,等待共识,然后再生产一个区块,比如Tendermint和Cosmos里的PBFT的实现。由于PBFT共识需要比较长的时间(至少1s以上),因而采用传统的PBFT是没法满足0.5s出块需求的。因而EOS 的BFT采取了不同的实现方式,出块和共识是流水并行工作的,区块生产完成后,不等待PBFT共识,继续生产同时参与并处理上一个区块的PBFT共识,当PBFT共识完成后即修改为不可逆状态。

EOS是否已采用BFT

其实很多人都在问我这个问题,我只能说,根据我目前了解点到的信息是代码和测试代码都有,但还没实行。其实从目前不可逆时间也可推测出BFT没有启用,如果BFT已经启用,1个区块在一个BFT共识完成后(该共识一般只需1s多)即可被确认,而不是目前的3分钟。

转载自 https://mp.weixin.qq.com/s/DlKA5xBhNUFC203t7sKy4A