八哥之神【1】

2018 年 9 月 11 日,稣开特稣垃回德国,遇到一个女孩想搭便车,看起来并不是德国人,但应该是个学生,稣就同意了。

车开到德国大桥爬坡时,居然提示快没电了……难道多载一个人,里程就要打这么多折?为了省电下坡空档滑行,结果进入异次元空间……来到一个很陌生的村庄。

下车给老爸打电话,居然能通,他说那个地方是汽车坟场,凡是过德国大桥正好没油的车就可能掉进去。

这坑很大,而且历史悠久!进来的人都发展出一个村庄了,主要靠农业和修车维持生存。

一个大伯说:“这里的科技很原始,你这车很难修,得在村里生活很久才可能离开了。”

另一个大妈说:“呵呵,来到这里的人,还有离开的先例吗?”

大伯看起来像修车工,稣好奇地问:“修好要多少钱?”

大伯乐呵了:“钱是什么东西?这里没有银行、ATM,你上哪里取?就算取了,我们也不承认。”

原来,这里陆陆续续进来三四百人,至今没有人成功离开。无法离开的原因可能是:在原始农业时代修好工业时代的车——难;修好之后,开出这个异次元世界——更难!

不过大妈似乎并没有很绝望,眼神看起来很乐观,有种在这里一样能快乐地生活的气态。继续深聊,才发现当地有个传说:只要在这里生儿育女,就可以一个换一个,如果生两个孩子,那么在孩子都成年后,夫妻就可以离开,回到原来的世界。

这时候,身边的女孩说话了:“我愿意和你结婚、生子,等孩子长大,争取他们的同意,我们就离开!”

稣看着眼前这个相貌平平又没胸的女孩,惊讶不已!怎么能这么轻易就谈婚论嫁!?难道她看穿稣在现实世界有两千万存款?不对啊,外面的钱,在这里又没啥卵用。还是问点实际的:“都不知道你贵姓芳名呢?”

“我叫吴情,爱情的情。”女孩问答道,口气很平静,像在念口头禅。

稣隐约记得在现实世界,稣已经结婚生子,但还是好奇地问:“那你说说为什么要嫁给稣?我们才认识几分钟呢!”

“首先,你有车,说明在实现世界里,能力没问题,而且对车肯定比我更了解;其次,从你的年纪和身材来看,保持得挺好,在这边主要是靠农业和修车业,这两者你都会比我有优势很多。然后,刚才你让我搭便车,说明心地善良。最后,我是读金融的,在这里刚开始用处不大,但会越来越有用,只要我们结合,我会用专业帮助你更好地规划生活、事业和资产。”

听起来好像还不错,稣也一直很喜欢读财务、金融之类的女生,顿时有点心动,继续问:“你觉得我们在这里能活得好?”

“一定比大部分人活得好!说不定,我们会成为这里的富豪家族。”

哇嗷!成为一方富豪!?想想还有点期待……但还是有个隐患:“如果我们真的能活得很好,这里又是世外桃源,稣可能想留下,你会陪稣留下来吗?”

“不会。”吴情无情地回答。

稣:“为什么?”

吴情:“这里的时代落后我们原来生活的时代太多,我只能接受一时的倒退,不能是一辈子!我必须回去。”

“所以你,只是想互相利用,你并不喜欢稣,是吧?”稣失望地问。

“可能吧,我不确定以后会不会喜欢你,但即使喜欢,以我现在的心思,我大概率要离开。”有一刹那,女孩露出了羞涩的表情。

虽然有点诱惑,但稣最终还是做出决定,“对不起,稣不能娶你,没有感情基础,很为难,况且这个传说还没人实践过,说不定只是哪个有才的人,想出来娱乐大家的主意呢!”

“我是 98 年的,虽然没有很漂亮,但很年轻,你居然能拒绝我这个年龄段的女孩子!”吴情有点意外,继续问:“我想听听你有什么更好的方案!”

“稣没有什么更好的方案!不过很感谢你在某些程度上,对稣的肯定,作为答谢,车就送你吧!稣是暴走族,每次暴走灵思飞扬说不定可以穿越时空,想用走的试试……”

吴情忍不住笑了,可能是笑稣天真,但眼神里还带着一丝丝鄙视……不过,稣却发现她笑起来挺美的。

稣:“你要多笑!很美。再见!”

“好吧,再见!”她又笑了一下,这次没有鄙视。稣记住了这个年轻女孩的笑容。

说完,稣不顾一切反对,往东直走,三天三夜之后,终于累死在路上。

诗盗·草

《#诗盗#·草》:心怀蒙古大草原,脚踩都市小草团。心旷神怡思飞扬,每天冒死来上班。

注解

没错,“小草青青,脚下留情”这是反人类的标语,人类自古就喜欢踩草地。

可是人人都踩,草地就坏了鸭!草草这么可爱你怎么可以踩?

草,之所以被踩坏,是因为很多人焦虑总是走同一条最短路线。而稣不走寻常路,每天走不同曲线,踩不同的位置。草草这么口爱,稣就是要踩,只要不被人捅死就可以愉快地去上班。

程序员鼓励师系列:EOSIO 智能合约开发从入门到入定

作者: UMU @ MEET.ONE 实验室

常规入门流程

经典三步:

  1. 了解区块链基本概念,了解 EOS 基本情况;

  2. 官方开发者文档

  3. 开始愉快地写代码。

但是,有个很大的问题:开发语言居然是 C++!所以,鼓励师出场了……

这不是 C++,这不是 C++,这真是不是 C++

不信?我们就来试一试:

1
2
3
error: cannot use 'try' with exceptions disabled
try {
^

智能合约的编译目标是 WASM 文件,最终要在 WASM 的 VM 里运行,比如 wabt,这和常规情况下使用原生 C++ 开发可执行程序、静态库、动态库等,有很大不同。

受限部分包括:

  • 语言特性。比如上面举例的 try。

  • 可调用外部函数。比如 CRT 的 rand 函数,再比如您想用 socket 自由通信……没门。

  • 内存访问。这个比较难解释,后面再说。

如果您学过 Golang、Python、nodejs、Java 或其它相近语言,转到智能合约开发,可以说是轻而易举。理由如下:

  1. 智能合约关注业务逻辑,和大部分脚本语言类似。

  2. 智能合约有很强的约束范围,API 很有限,不会要求记忆大量知识。举个例子,它可以使用 boost,但也只是子集,无法使用完全的 boost。

  3. 智能合约有很强的套路,代码是满满的既定格式。熟悉 Hello world,会用基本的命令行工具进行测试,最多只需要 2 天,就会发出“原来这么简单”的感叹。

【高级话题】关于 WASM

1. 多语言支持

如果您学过 Golang、Rust 可能会注意到,它们可以编译成 WASM 文件。比如 Golang 的编译命令为:

1
GOOS=js GOARCH=wasm go build -o hello.wasm

但是找个 Hello world 编译一下,您可能会哭,产生的 WASM 文件有 2.3MB,就获得一个打印信息……(EOS 基本概念:RAM 挺贵的。)

虽然现状是 C++ 一枝独秀,但未来可能会有人开发专门的编译器支持 Golang、Rust 等语言开发 EOS 智能合约。

2. WASM 逆向

VSCode 安装插件后可以直接打开 WASM 文件,显示 WAST 代码,比如我们随便打开一个 hello.wasm,滚动到末尾,可能会看到以下两行:

1
2
(data (i32.const 8192) "read\00")
(data (i32.const 8197) "get\00malloc_from_freed was designed to only be called after _heap was completely allocated\00"))

下面我们写个 C++ 代码:

1
2
const char* p = reinterpret_cast<const char*>(8192);
eosio::print(p);

以上代码,打印出 read。如果把 8192 改为 8197,则打印 get;改为 8201,打印 malloc_from_freed was designed to only be called after _heap was completely allocated

这个例子可能吓倒大家,特别交代下,一般开发中,较难遇到逆向……只是想说明 WASM 的内存管理和常规 C++ 开发的可执行程序是不同的,后者把指针指向 8192,是 Process Working Set 的地址,通常来说去读这么低的地址,后果极可能是读异常,挂掉。

划重点:虽然你用 C++ 写代码,但编译后是 WASM 二进制编码,运行时使用 VM,受控性很强,降低了开发难度,也杜绝很多安全问题。

3. 性能问题

为了讨好 Python 程序员,下面用 Python 来写个开平方运算,有这样的:

1
2
3
import math

print(math.sqrt(2.0))

也有这样的:

1
2
3
import numpy

print(numpy.sqrt(2.0))

他们有个共同点——很快……相对 C++ 写的!!有点难以理解?

Python 的 sqrt 函数,其实都是用 C 语言实现的,最终都是调用解释器里的本地代码,速度很快。

原生 C++ 写的本地程序,几乎肯定是比 Python 快的,但我们前面说过:智能合约的 C++ 不是常规的 C++,当它被编译成 WASM 后,我们去看 WAST 代码,会发现 sqrt 的实现整个被塞进 WASM 里,它最终要用 VM 来执行,当然没有 Python 解释器快了!

4. 设计原则

打开任意 WASM 文件,可以看到里面很多 (import 开头的行,这些都是原生 C++ 实现的 API,它们的执行速度就是本地代码的速度,对应官网 API 文档里的 API。

有前面的性能问题,我们不禁要问 EOS 为什么不多做点 API 来提高性能?这是因为维护少量 API 代价比较可控,数量一多就有版本问题,各节点可能因为版本不同步而无法达成共识。

另外,目前的 wabt 功能强大,性能也过得去,对于 sqrt 此类可能并不常用的数学函数,即使用原生 C++ 实现了,性能提升带来的好处,也无法平衡多版本可能带来的风险。

原则上,BP 之间快速达成共识,提升 TPS 才是更值得做的。

EOSIO MongoDB 插件系列:管理技巧

作者: UMU @ MEET.ONE 实验室

总结同步主网数据到 MongoDB 时的常用操作,大部分以 transaction_traces 表为例。

1. nodeos 配置优化

1
2
3
4
5
read-mode = read-only
validation-mode = light

mongodb-queue-size = 2048
abi-serializer-max-time-ms = 15000

2. 首次启动 nodeos

https://eosnode.tools/blocks 下载最新 blocks data,以减少网络同步时间。

首次启动,应使用 --replay-blockchain 参数。

3. 守护 nodeos 进程

目前 nodeos 1.5+ 版本如果优雅退出,下次启动可以无需痛苦的 replay 过程,所以可以监控 nodeos 进程,如果退出就调用。

启动脚本 /home/ubuntu/shell/continue.sh:

1
nohup /usr/local/eosio/bin/nodeos --config-dir /home/ubuntu/nodeos/config-dir --data-dir /home/ubuntu/nodeos/data-dir > /home/ubuntu/shell/`date +%Y-%m-%d_%H-%M`.log 2>&1 &

守护脚本 /home/ubuntu/shell/autorun.sh:

1
ps -C nodeos || /home/ubuntu/shell/continue.sh

添加到计划任务,运行 sudo crontab -e,输入下行并保存、退出:

1
* * * * * /home/ubuntu/shell/autorun.sh

4. 读写用户分离

nodeos 需要写入,使用有写入权限的 EOS 用户,其余情况使用只读权限的 EOSReader 用户,数据库安装之后就尽量不使用管理员用户。

1
2
3
use EOS
db.createUser({"user" : "EOS", "pwd" : "Password", "roles" : [{role : "readWrite", "db" : "EOS"},"dbOwner"]});
db.createUser({"user" : "EOSReader", "pwd" : "password", "roles" : [{role : "read", "db" : "EOS"}]});

5. 查询同步进度

1
2
use EOS
db.transaction_traces.find({}, {"block_num" : 1, "block_time" : 1}, -1).sort({$natural:-1}).pretty()

6. 修复丢失数据

参考《EOSIO MongoDB 插件系列:从 log 中找回丢

EOSIO MongoDB 插件系列:从 log 中找回丢失的插入记录

作者: UMU @ MEET.ONE 实验室

问题

当 MongoDB 因不可抗力故障,nodeos 重启后会丢失上次故障时正在插入的记录。

解决

nodeos 会将插入语句连同错误原因等信息一起写入 log,这给了我们手动修复丢失的机会。下面以 transaction_traces 为例,介绍修复流程。

1. 找出所有失败记录

1
grep 'mongo exception, trans_traces insert:' *.log > lost.txt

2. 从 log 生成 mongo script

1
2
3
4
5
6
echo 'print("++++");
var eos = db.getSiblingDB("EOS");' > lost.js

cat lost.txt | sed -n 's/.*, trans_traces insert: \(.*\), line 920, code.*/eos.transaction_traces.insert(\1)/p' >> lost.js

echo 'print("----");' >> lost.js

3. 导入 MongoDB

1
nohup mongo mongodb://$user:$password@127.0.0.1:$port/admin lost.js > lost.log

纯数字 EOS 账号

作者: UMU @ MEET.ONE 实验室

问题

2018 年最后一个工作日,智能合约开发小哥哥遇到一个奇怪的现象:某个账号给我们合约转账,在 EOS 浏览器上都可以找到记录,但用 cleos get table 在合约的 RAM 里找却找不到!

现象

了解具体情况后,注意到两个事实:

  1. 只有某个特定账号有问题,其它账号很正常。

  2. 那个有问题的账号是纯数字的。

这是 EOS 账号解析的问题,UMU 曾经给 EOS 提过一个相关的 issue:get_table_by_scope parameter lower_bound is NOT properly converted, cause enumeration dead loop #5824,里面有问题产生原因和解决方案。

分析

eosio::name 本质是一个 uint64_t 数字的 base32 编码,编码形式是为了方便人类记忆。举个例子:

shengxiaokai 本质上是 14075216089888066784 (0xc3553675c6a40ce0)

cleos get table 在解析账号时,兼容了这两种表达形式,所以 14075216089888066784shengxiaokai 是等价的。

但本身是纯数字的账号可就有歧义了,比如 313131313131 是当成一个 uint64_t 解释,还是当成 base32?很不巧,解析代码是优先当成 uint64_t 解释的。

解决方案

给纯数字 EOS 账号加上个空格后缀,比如 111122223333 可以改为 "111122223333 "

EOSIO 找出谁为我质押

作者: UMU @ MEET.ONE 实验室

问题

很多 EOS 浏览器都只能显示别人给我抵押了多少 EOS,但不能看到是哪个账号帮我抵押的。

分析

1. 看抵押的实现代码

eosio.contracts/eosio.system/src/delegate_bandwidth.cppdelegatebw 函数开始分析。

它调用了 changebw,其中的查表操作是这样的:

1
2
del_bandwidth_table     del_tbl( _self, from.value );
auto itr = del_tbl.find( receiver.value );

scope 是 from,而 from 就是要求的未知项,直接粉碎我们用这路线继续求解的可能。

2. 找交易记录

MEET.ONE 之前发布过几篇关于 MongoDB 插件的文章,这些积累为我们继续求解提供了很大便利。

直接在 Mongo Shell 里尝试:

1
2
use EOS
db.transaction_traces.findOne({"action_traces.act.account" : "eosio", "action_traces.act.name" : "delegatebw", "action_traces.act.data.receiver" : "shengxiaokai"})

执行之后,找到一条 trx_id 为 9bd50c0fd6f0e1d0ed4c6f5c6f873a33976955ff9dae2ac3eb16cb7e9a44d106 的交易记录,显示 1freeaccountshengxiaokai 抵押:

1
2
3
4
5
6
7
{
"from" : "1freeaccount",
"receiver" : "shengxiaokai",
"stake_net_quantity" : "0.0000 EOS",
"stake_cpu_quantity" : "0.5000 EOS",
"transfer" : 0
}