ECC Node.js

前情

上篇《基于 ECC 的私钥转为公钥的过程》讲到求椭圆曲线上的点时,用的是基于 Python 的 SAGE。为了方便 Node.js 程序员理解和实现完整流程代码,本篇用 Node.js 库实现椭圆曲线点的计算。

用 Node.js 求椭圆曲线的点

库的选型考虑 eosjs 用的 ecurveelliptic

1. ecurve

1
2
3
4
5
6
7
8
const ecurve = require('ecurve')
const BigInteger = require('bigi')

const k1 = ecurve.getCurveByName('secp256k1')
const pk = BigInteger.fromHex('d2653ff7cbb2d8ff129ac27ef5781ce68b2558c41a74af1f2ddca635cbeef07d')
const pub = k1.G.multiply(pk)
pub.affineX.toHex()
pub.affineY.isEven()

得到 x 值为 c0ded2bc1f1305fb0faac5e6c03ee3a1924234985427b6167ca569d13df435cf,y 为偶数,和 SAGE 计算结果一样。

2. elliptic

流程基本一样,所以这里给出完整转换代码。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
#!/usr/bin/env node

/**
* @author UMU618 <umu618@hotmail.com>
* @copyright MEET.ONE 2019
* @description Use block-always-using-brace npm-coding-style.
*/

'use strict'

const bsc = require('bs58check')
const ripemd160 = require('ripemd160')
const bs = require('bs58')
const elliptic = require('elliptic')
const BN = require('bn.js')

function encodePublicKey(payload) {
const checksum = new ripemd160().update(payload).digest()

return bs.encode(Buffer.concat([
payload,
checksum.slice(0, 4)
]))
}

function privateKeyToPublicKey(privateKey) {
const buf = bsc.decode(privateKey)
if (0x80 !== buf[0]) {
throw new Error('Not a private key.')
}
const k1 = elliptic.curves.secp256k1
const pvt = new BN(buf.slice(1), 'be')
const pub = k1.g.mul(pvt)
const y = pub.getY().isEven() ? 2 : 3
return 'EOS' + encodePublicKey(Buffer.from([y].concat(pub.getX().toArray())))
}

const privateKey = '5KQwrPbwdL6PhXujxW37FSSQZ1JiwsST4cqQzDeyXtP79zkvFD3'
const publicKey = privateKeyToPublicKey(privateKey)

console.log(privateKey)
console.log(publicKey)

3. 对比

elliptic 比较好用,比较快。

代码

https://github.com/UMU618/secp256k1-tools

基于 ECC 的私钥转为公钥的过程

基本知识

ECC 体系中,私钥是一个大型随机数,而公钥则是私钥乘以椭圆曲线上的基点后对应的点。

meet-one/private-to-public 是 MEET.ONE 开发的私钥转公钥工具。

EOS 支持的 EC 有两种:secp256k1(以下简称 k1)、secp256r1,下面以 k1 为例,结合 Node.js 和 Python 代码介绍转换过程。

涉及算法

  • BASE58:编解码私钥、公钥。
  • SHA-256:校验私钥,本文忽略此步。
  • ECC, secp256k1:计算公钥。
  • RIPEMD-160:校验公钥。

转换过程

1. 解码私钥

假设私钥为:5KQwrPbwdL6PhXujxW37FSSQZ1JiwsST4cqQzDeyXtP79zkvFD3,这是 base58 编码,用 bs58 库解码:

1
2
3
const bs = require('bs58')

bs.decode('5KQwrPbwdL6PhXujxW37FSSQZ1JiwsST4cqQzDeyXtP79zkvFD3')

得到 <Buffer 80 d2 65 3f f7 cb b2 d8 ff 12 9a c2 7e f5 78 1c e6 8b 25 58 c4 1a 74 af 1f 2d dc a6 35 cb ee f0 7d aa 08 64 4a>,其中第 1 个字节 0x80 是类型,末尾的 4 字节是校验码。

这里我们不关心校验码,也可以直接用 bs58check 解码:

1
2
3
const bsc = require('bs58check')

bsc.decode('5KQwrPbwdL6PhXujxW37FSSQZ1JiwsST4cqQzDeyXtP79zkvFD3')

得到 <Buffer 80 d2 65 3f f7 cb b2 d8 ff 12 9a c2 7e f5 78 1c e6 8b 25 58 c4 1a 74 af 1f 2d dc a6 35 cb ee f0 7d>,去掉首字节后为:

65 3f f7 cb b2 d8 ff 12 9a c2 7e f5 78 1c e6 8b 25 58 c4 1a 74 af 1f 2d dc a6 35 cb ee f0 7d```
1

这是 256bit 整数的 Big endian 字节流表示,转为 16 进制整形为 `0xd2653ff7cbb2d8ff129ac27ef5781ce68b2558c41a74af1f2ddca635cbeef07d`,记为 `pk`。

## 2. 构造椭圆曲线,求公钥的坐标

根据 [SECG](http://secg.org/) 规定的 k1 的参数,我们用基于 Python 的 [SAGE](https://sagecell.sagemath.org/) 构造 k1 对应的椭圆曲线,然后计算 `pk * G`:

```python
a = 0
b = 7
p = 0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc2f
Gx = 0x79be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798
Gy = 0x483ada7726a3c4655da4fbfc0e1108a8fd17b448a68554199c47d08ffb10d4b8
E = EllipticCurve (GF(p), [a, b])
pk = 0xd2653ff7cbb2d8ff129ac27ef5781ce68b2558c41a74af1f2ddca635cbeef07d
G = E(Gx, Gy)
pk * G

结果为:(87237761414843254130560834629777710286905276524352264071298714336416392033743 : 108016191455113306196371645921919775466659772908675410052799661524790827329728 : 1)

注:这里的 (x : y : z) 是射影坐标,一般采用笛卡尔坐标系表示,为 (x/z, y/z)。

3. 编码公钥

取 x 值:87237761414843254130560834629777710286905276524352264071298714336416392033743

16 进制为:0xc0ded2bc1f1305fb0faac5e6c03ee3a1924234985427b6167ca569d13df435cf

Big endian 字节流表示为:[0xc0, 0xde, 0xd2, 0xbc, 0x1f, 0x13, 0x05, 0xfb, 0x0f, 0xaa, 0xc5, 0xe6, 0xc0, 0x3e, 0xe3, 0xa1, 0x92, 0x42, 0x34, 0x98, 0x54, 0x27, 0xb6, 0x16, 0x7c, 0xa5, 0x69, 0xd1, 0x3d, 0xf4, 0x35, 0xcf]

由于 y 值是偶数,所以添加一个前缀 2,得到:[2, 0xc0, 0xde, 0xd2, 0xbc, 0x1f, 0x13, 0x05, 0xfb, 0x0f, 0xaa, 0xc5, 0xe6, 0xc0, 0x3e, 0xe3, 0xa1, 0x92, 0x42, 0x34, 0x98, 0x54, 0x27, 0xb6, 0x16, 0x7c, 0xa5, 0x69, 0xd1, 0x3d, 0xf4, 0x35, 0xcf]

注:若 y 为奇数,则前缀为 3。

注:为什么这么规定?只用 x 值和 y 值的奇偶性表示一个点,这叫公钥的压缩格式。因为只要有 x,就可以通过 k1 的椭圆曲线方程式 $y^2 = x^3 + 7 \mod p$ 求出 y,但此时 y 会有两个解,又由于 p 是一个大质数,必定为奇数,故两个 y 解的和 mod p 一定等于 0,即一奇一偶,所以用 x 和 y 的奇偶性标志即可代表这个点。

接着求校验码:

1
2
3
const ripemd160 = require('ripemd160')

new ripemd160().update(Buffer.from([2, 0xc0, 0xde, 0xd2, 0xbc, 0x1f, 0x13, 0x05, 0xfb, 0x0f, 0xaa, 0xc5, 0xe6, 0xc0, 0x3e, 0xe3, 0xa1, 0x92, 0x42, 0x34, 0x98, 0x54, 0x27, 0xb6, 0x16, 0x7c, 0xa5, 0x69, 0xd1, 0x3d, 0xf4, 0x35, 0xcf])).digest()

得到:<Buffer eb 05 f9 d2 c6 dd 62 f7 f2 a0 f7 61 ea 1d 8c 0b 84 4a 3b 52>,取前 4 字节,添加到末尾,得到:[2, 0xc0, 0xde, 0xd2, 0xbc, 0x1f, 0x13, 0x05, 0xfb, 0x0f, 0xaa, 0xc5, 0xe6, 0xc0, 0x3e, 0xe3, 0xa1, 0x92, 0x42, 0x34, 0x98, 0x54, 0x27, 0xb6, 0x16, 0x7c, 0xa5, 0x69, 0xd1, 0x3d, 0xf4, 0x35, 0xcf, 0xeb, 0x05, 0xf9, 0xd2]

然后,base58 编码:

1
2
3
const bs = require('bs58')

bs.encode(Buffer.from([2, 0xc0, 0xde, 0xd2, 0xbc, 0x1f, 0x13, 0x05, 0xfb, 0x0f, 0xaa, 0xc5, 0xe6, 0xc0, 0x3e, 0xe3, 0xa1, 0x92, 0x42, 0x34, 0x98, 0x54, 0x27, 0xb6, 0x16, 0x7c, 0xa5, 0x69, 0xd1, 0x3d, 0xf4, 0x35, 0xcf, 0xeb, 0x05, 0xf9, 0xd2]))

得到:6MRyAjQq8ud7hVNYcfnVPJqcVpscN5So8BhtHuGYqET5GDW5CV,加上前缀 EOS,即为公钥。

诗盗·买币送禅

《#诗盗#·买币送禅》:天花娉婷坠如雨,帆冻阴飙吹不举。苦却乐,乐却苦,数字黄金忽如土。

注解

改编自唐朝贯休的《送颢雅禅师》:

霜锋擗石鸟雀聚,帆冻阴飙吹不举。芬陀利香释驎虎,
幡幢冒雪争迎取。春光主,芙蓉堂窄堆花乳,
手提金桴打金鼓。天花娉婷下如雨,狻猊座上师子语。
苦却乐,乐却苦,卢至黄金忽如土。

空仓观测,甚至还有心情写诗。

优化思维【3】消除没必要步骤

故事

四月底给 EOSIO / eos 提了一个优化 MongoDB 插件性能的 PR,被连续感谢好几个 Release

分析

原先的流程:fc::variant -> JSON string -> BSON,实现起来很简单,因为 JSON 是很常见的,fc::variant 和 BSON 都有到 JSON 的转化,所以实现代码很简单,一行两个函数。

但数据大时,性能问题就暴露了,这个过程先把 fc::variant 对象序列化为 JSON 字符串,然后反序列化到 BSON 对象。两步都是 CPU 密集型操作,由于 nodeos 及其插件暂时对多核支持不好,导致单核跑爆。

两个过程都要用递归实现,调用栈可能很深。调用函数可能有入栈出栈的消耗,有一种优化思路正是用 inline 减少函数的频繁调用

回归到本质,fc::variant 和 BSON 都是对象,应该直接转化才对。只是实现起来就不是一行能搞定的。先挑简单的方式实现,后期再优化,这是一种挺常规的做法。

如何炒?

本文只求打脸,不构成任何投资建议。

1. 为什么稣不敢炒癌股?

  • 没有可靠信息

上市公司的财报是可以做的,有的奇葩公司的秘书、高管,竟能跳出来说不保证财报准确……

参考:

这些股票你敢买吗?老总竟不能保证财报真实性

对财报内容“不保真”,上市公司高管成了“摆设”

  • 没有先机

交易数据只有交易数据……这个后面再解释。

  • 主力控盘

发布好消息后,涨了,就是利好所以涨。

发布好消息后,跌了,就是利好出尽所以跌。

韭菜们记性不一定不好……更可能是看不懂。

  • 即使在一家上市公司,也未必能掌握自家股价的可能走向

稣在 300017 时,就遇到过很多次领导自己把握不准。

多年前首次听老板预估股价要到多少。后来真的到了,稣立刻卖掉,结果继续大涨好多倍……所以连老板自己也低估自家股价。为什么呢?因为风口、概念这个东西不是老板就能掌握的,很多时候是时代造就了老板。

而且高管一般希望大家买自家股票是亏的,因为如果赚了,怎么避嫌是不好处理的,外面的人说内幕消息要搞事怎么办?盈利是不是要上交?只有真正的高管们,才有权力内咳幕交易。普通职工想用丈母娘账号当韭菜也是可以的……

最后,炒自家容易感情用事。

2. 为什么稣敢炒柚子?

  • 有可靠信息

区块链数据不可篡改性、公开性。

稣在这个领域工作,容易获得行业信息,甚至能参与一些造势活动。

  • 交易数据不只交易数据

Dapp 立功了。举个最常见的菠菜游戏:水手和菠菜商的博弈。

一阵疯狂交易后,水手盈利——卖;菠菜商盈利——还是卖。反正都会卖,差别只是谁卖,所以价格就跌。

这里举的例只为说明其可分析性,实操需谨慎。

  • 有先机

之前稣发现 2.5GHz CPU 在处理链上数据时,遇到大块是处理不过来的,会导致入库的块和线上头块的差距越来越大,引发运维事故。

但多次经验下来,发现大块里面如果是大量菠菜博弈,就能大概预测 7 天后的价格下跌,然后进入横盘。这时候就是买入点。

另外,行业内的研究人员也会有一些可以参考的成果,比如我司高材生有优秀毕业论文,多次准确预测价格走势。

3. 到底怎么炒?

如果您不在合适的位置,还是别炒。

本文只值 200 个柚子。如果您问:为什么稣不多下点注?稣哭着回答:因为穷。

EOSIO 数据同步

作者: UMU @ MEET.ONE 实验

最近三个月尝试各种方案把 EOSIO 链上交易数据同步到数据库中,踩了不少坑,现总结一下经验。

1. 使用 MongoDB 插件同步 transaction_traces 和 action_traces

原始需求是要链上交易数据,所以先是把 transaction_traces 和 action_traces 都同步。

踩坑:无奈地发现速度跟不上,服务器的时间成本比较高,只能舍弃。

2. 使用 MongoDB 插件同步 transaction_traces

研究插件代码,发现 action_traces 是从 transaction_traces 拆出来的,是重复的,所以把 action_traces 去掉,这次成功追上主网区块高度。

踩坑:transaction_traces 在查询 actions 时不太方便,因为 actions 是放到 transaction_traces 内部的一个数组,要查询具体一个 action 就得分两步走,先在 MongoDB 查询出某个 trx,然后再 actions 数组里遍历。数据库使用端的工程师觉得这样太麻烦,无奈继续放弃这到手的肥肉。

3. 使用 MongoDB 插件同步 action_traces

明确 action_traces 才是客户端想要的后,就只同步 action_traces。

踩坑:action_traces 条数比 transaction_traces 多了三倍以上,又出现追不上区块的问题……

4. 使用 MongoDB 插件同步 action_traces,但只要 transfer 数据

客户端最关心的是 transfer 数据,既然跟不上,就舍弃其它数据。

踩坑:舍弃的数据后期不好补。

5. 考虑 kafka_plugin

有人说 kafka_plugin 同步数据很快,可以追上主网区块。

踩坑:从 kafka_plugin 代码就能看出它没有处理 action_traces,如果还要去后端再拿出来处理,再插入到 MongoDB 里,那开发成本和服务器成本一样又上去了。

6. 从 2019 年的区块开始同步

从 35058781 块开始,插入数据库。之前的区块(1 - 35058780)处理后,仅插入数据量相对很小的 account_controls、accounts、pub_keys,其它数据量大的表不插入。

做这个尝试很重要,因为发现重要的线索:

  • 大约在 2200 万块开始,nodeos 的处理速度下降很多,平均每块要 2-3ms,所以同步慢的原因在于跑 nodeos 的服务器的性能。

  • 在 1 开始的早期区块阶段,同时插入 transaction_traces 和 action_traces,并不能看出比只插入 action_traces 慢,说明 MongoDB 端压力很小。

7. 结论

  • 要追上主网区块高度,nodeos 机器性能要好,2.5GHz CPU 不够用。之前听闻 BOS 要求 BP 使用 4.0GHz 的 CPU,现在看起来也是有道理的……以性能成本换取时间。

  • MongoDB 集群,按之前的文章《为 EOSIO MongoDB 插件搭建高可用集群》的配置,插入阶段毫无压力。

  • 插件代码有些问题,需要优化,最明显的就是 queue_size 的设计不合理,打印处理时间太长的提示也不合理。

    • queue 函数是个模板,所有的 queue 都调用它,但 max_queue_size 和 queue_sleep_time 缺只有一份,这可能导致一个 queue 导致的 queue_sleep_time 加大,影响到其它 queue,即整体的休眠时间会无用地加大。

    • 打印处理时间没有按照 max_queue_size 变化,当 max_queue_size 设置大时,打印就很频繁,带来延迟。

  • 建议:一定要注意成本问题!如果查询量不是很巨大,找点友商的数据源用用就好了,自己搭建的成本好高……但如果查询量太大,或者友商卖太贵,以上经验就是很好的参考了。

程序员鼓励师系列: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

多语言支持

如果您学过 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 智能合约。

WASM 逆向

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

1
(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,受控性很强,降低了开发难度,也杜绝很多安全问题。

性能问题

为了讨好 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 解释器快了!

设计原则

打开任意 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 "