智能合约替用户承担事务的开销

需求

用户可能因为资源(NET 和 CPU)匮乏,无法愉快地使用 EOS 智能合约。

解决思路

智能合约承担事务的开销(NET 和 CPU)。

具体方案

1. ONLY_BILL_FIRST_AUTHORIZER 特性

eos 1.8 的 ONLY_BILL_FIRST_AUTHORIZER 特性,通过只向事务的首个授权方收费的方式,部分地解决这个问题。这一特性允许应用提供者对用户的每一笔事务进行联合签名,通过这一方式从公共池中支付事务的开销。

缺陷:联合签名操作门槛高,安全性堪忧。

2. 合约调用 accept_charges

Contracts Paying Transaction Costs 提出一种无需联合签名的方法(注意:目前还只是提案,尚未实现)。

关键函数如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
bool accept_charges(
uint32_t max_net_usage_words, // Maximum NET usage to charge
uint32_t max_cpu_usage_ms // Maximum CPU usage to charge
);

void get_accept_charges(
name* contract,
uint32_t* max_net_usage_words,
uint32_t* max_cpu_usage_ms,
);

void get_num_actions(
uint32_t* num_actions,
uint32_t* num_context_free_actions,
);

简单地说,在合约调用 accept_charges 函数,如返回 true,则事务的开销将会由合约账户承担。

具体规则如下:

  • 如果多个合约调用了 accept_charges,则首个调用者会被收取费用。accept_charges 会返回 true 给该合约,而返回 false 值给其他的合约。

  • 如果首个合约调用了多次该函数(用于修改限制),无论是在相同的 action 还是不同的多个 action 之中,每次都会返回 true。

关键点讲完了,其余请参考 Contracts Paying Transaction Costs 原文或译文

诗盗·今年七夕不送礼送礼只送学区房吓醒

《#诗盗#·今年七夕不送礼送礼只送学区房吓醒》:一年三度情人谋,先虐情侣后虐狗。今日没钱空回首,粗茶淡饭再买楼。

注解

商家为了赚钱,一年催大家过三次情人节,堪比谋杀……先虐情侣的钱包,再通过情侣秀恩爱虐单身狗。
今天想日却发现没钱,只能回首空悲切,过几年艰苦的日子后再考虑买学区房吧。

改编自唐代诗人罗隐的《绵谷回寄蔡氏昆仲》:

一年两度锦城游,前值东风后值秋。
芳草有情皆碍马,好云无处不遮楼。
山将别恨和心断,水带离声入梦流。
今日因君试回首,淡烟乔木隔绵州。

优化思维【4】Bash for 循环

常规教材

Bash 循环有三种写法。

1. 类 C 语言语法

1
2
3
4
5

for ((i = 1; i <= 10000000; ++i))
do
echo $i
done

2. for in 语法

1
2
3
4
for i in {1..10000000}
do
echo $i
done

3. 使用外部命令 seq

1
2
3
4
for i in `seq 1 10000000`
do
echo $i
done

分析

这三种方法中,性能最好的是第一种,最差的是第三种。

方法 1 是语言层面的循环语法,循环会立刻开始,而后两种,作为对比,并不会立刻开始!

方法 2 中的 {1..10000000} 会产生一个 1 到 10000000 的序列,然后再开始循环。

方法 3 中的 seq 1 10000000 是调用 seq 产生一个 1 到 10000000 的序列,然后再开始循环。涉及到外部进程调用和管道传递,所以比方法 2 更慢。

总结

  • 方法 1 写起来最麻烦,性能却是最好的。“做一件事,有很多种方式”,有时候不是好事,有对比,就有伤害……优化往往是和人性作对!

  • 语言原生的方法一般比调用外部命令好。

同类经验

Java 的 for 和 while 本质上一样(其实 while 可以去掉),而同是 JVM 语言的 Scala 的 for 却不同于 while。Scala 的 for <- 也会先产生序列,再循环,所以超大规模循环时,for 性能不如 while。

刽子手

刽子手死后下地狱,阎王判他转世为畜。

刽子手不服,问:“我为正义执法,难道不是有功德!其他的刽子手都这么判?”

阎王:“多数被你执杀的人投诉你技术不好,导致他们没迅速死亡。其他技术好的刽子手没大量投诉,我判他们投胎为人。”

刽子手还是不服,辩道:“他们不是该死吗?怎么死还有差别?”

阎王:“他们是该死,被判死刑已经付出代价。但你身居司法机构,技术不好,在阳间没人投诉你,导致你从来不改进,在我阴间你要付出代价。”

诗盗·九九六

《#诗盗#·九九六》:笑叙狐朋猪友,傲砍尸老霉幽。此计未卜证太周,虎去猴留,空画春秋,休休,占得几朝风流?九九六。

注解

笑谈一些两面三刀的人和猪队友,傲慢导致招了很多八哥,最后尸变发霉,要砍掉才能净化。
两年前预料到的事情,已然成真,好像验证了我能未卜先知。

改编自霹雳角色“占云巾”出场诗:

笑许云朋霞友,傲看石老梅幽。
此生未卜学太周,龙去虎留,明日墟丘。
休休,占得几家风流?算春秋。

诗盗·影帝

《#诗盗#·影帝》:信佛信道信耶稣,拜天拜神拜父母。腹中 U 墨黑灰白,哭遍影帝不归路。

注解

曾经不信任何东西,包括自己在天地之间的状态,也不真心膜拜任何东西,人生过去一半后,都信了。
特别的人在人间探索,要学习别人对事情的反应,就可能被认为是同类,但其实不是,陷入叠加态。
曾经质疑神看不懂稣,会判断稣是邪恶的,所以自称量子邪稣。后来明白,神的观测无所不能,牠能看透任何面具。
明白神并不反对人处于亦正亦邪的状态,于是我不再像《追日者》眼里只有黑和白,灰色的人格才是世人常态。
那些令我羡慕的影帝,很多并不是天生的,他们也是付出很多泪水和汗水才练成的。

八哥之神【番外篇6】

听说鲁豫要来采访稣

1. 听说稣特别能装逼是这样吗?能不能传授一下给大家?

这就得分析一下装逼的本质,一般人会觉得装逼就是吹牛,其实是一大误解,装逼的奥义在于——说实话!

举两个例子:

  • 想买个六米长的迈巴赫,却发现自己的 C1 驾照开不了,算了,不买了!反正钱也不够。——大实话!

  • 稣不喝酒,不喝茶,有人来做客就拿出绮怡招待,500ml 一瓶 35 元,结果被说太装逼。其实这水健康,换成酒岂止要 35 元,还致癌!您说到底谁装逼呢?

2. 好像很有道理,您赢了!我们还是回到剧情。您不是说过自己是无神论,为什么剧中还出现神?

简单地说,本剧出现的神指的是每个人的心头之师,可以认为它是每个人内心的原始愿力,它也受各种规则制约,和神话故事里那些有超能力的神完全不是一回事。

  • 西方的“宿命论”认为,在人的一生中,上帝每分钟都看着你,你没有神的许可,连一根头发都不会脱落。这显然是伪科学。

  • 本剧的“命运无神论”完全没有神祗的意志成分,也无前生所造之业的因果成分,而是自然而然,谁都不可能预测或改变。简单地说,头发掉了就掉了呗。

3. 那您相信有那种有超能力的神吗?

我相信有超能力的神在天道前一样是卑微。

4. 稣为什么从神变成人?

这是一个循环,不是单向变化,也有从人变成神的。

时机一到,神自然就变成人。如果有神主动变成人,那可能是因为做神太快乐,以致于满足贫穷,本来就穷,还因为太快乐被人嫉妒,这不科学。做凡人呢,虽然没有做神开心,但有动力去赚钱呀。

世界上有很多更重要的东西,但前提是有钱。举个例子,小学就学过一寸光阴一寸金,但长大后发现,上班族不就是出售自己的时间换钱?有钱时,时间才宝贵;有钱时,感情才珍贵。

5. 是不是因为作者不想写了,强行把稣写死?

当然不是!中国人的平均寿命是 76 岁,稣曾经在佛塔前立誓折寿守护亲人长寿,一命赔多命,消耗掉 10 年寿命,死于 66 岁,完全科学合理。

梦见的就是这样死的,我有什么办法?咳!

6. 稣的身体不是新的吗?

身体是新的,但记忆是旧的,而且处于慢慢恢复的过程,恢复到一定程度后,稣领悟到转世的意义,自主选择转世。这事情已经发生过无数遍,不用在意死不死。

7. 《八哥九九八十一难》后来去哪里了?

这本书是高中时,妈妈为了让稣不因为早恋耽误学习、正确理解异性心理而传授的。但他的作者是未来的稣……时空悖论。

里面的科研成果是花了巨大心力得来的,但失忆的稣看它其实是不太相信的,所以离婚时期稣去实践了几个,付出一些代价后相信。

和大部分时空悖论的处理方式一样,和秦阳结婚后,稣亲自把它烧毁。

8. 30 集中有一段写稣对陈博士的质疑,所以她到底是不是稣的前妻?

不知道……真的!虽然我是作者,但这些故事是梦见的,所以也说不清楚!第 27 集里,稣在和陈立姻讲和秦阳的故事,那些记忆都是从识界复制而来的,识界里稣的前妻叫陈提姻,而不是陈立姻。至于 AVILab 的那个陈立姻,到底是谁,作者至今没搞懂!

诗盗·买币送禅

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

注解

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

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

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

优化思维【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 都是对象,应该直接转化才对。只是实现起来就不是一行能搞定的。先挑简单的方式实现,后期再优化,这是一种挺常规的做法。

SQLite Node.js

选型

mapbox / sqlite3: Asynchronous, non-blocking SQLite3 bindings for Node.js

安装:

1
yarn add sqlite3

常见操作

1. 创建数据库

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
const path = require('path')
const sqlite = require('sqlite3')

const dbPath = path.join(__dirname, 'test.db')
const db = new sqlite.Database(dbPath)

const sqls = [`CREATE TABLE test(
id CHAR(64) NOT NULL PRIMARY KEY CHECK(LENGTH(id) == 64),
timeStamp INTEGER NOT NULL,
state INTEGER NOT NULL DEFAULT 0)`
, 'CREATE INDEX index_id ON txs(id)'
, 'CREATE INDEX index_timeStamp ON txs(timeStamp)'
, 'CREATE INDEX index_state ON txs(state)'
]

db.serialize(() => {
for (let sql of sqls) {
db.run(sql, (err) => {
if (err) {
console.error(err)
} else {
console.log('SQL executed.')
}
})
}
})

db.close()

2. 插入数据

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
const items = [
{ hash: 'D141925E39814FB5256615A1A94EC82B7043D983F68423D8C149A2AE360B623C'
, ts: 1563273661316, state: 0 }
, { hash: '2E9F26F5D0A73AE5DAFC8A1C22264725972AA997A22522A906D8CD7E225096ED'
, ts: 1563273661317, state: 1 }
, { hash: '0FF5F5F5E96664939D07D94975342D71F824747EFECE1D24FDDBB3B29DD91DCB'
, ts: 1563273661318, state: 0 }
]

db.serialize(() => {
const stmt = db.prepare("INSERT INTO test VALUES (?, ?, ?)")
for (const item of items) {
stmt.run(item.hash, item.ts, item.state, (err) => {
if (err) {
console.error(err)
} else {
console.log('INSERT', item.hash)
}
})
}
stmt.finalize()
})

3. 查询

1
2
3
4
5
6
7
8
9
10
11
db.serialize(() => {
db.each("SELECT * FROM test WHERE state=0", (err, row) => {
if (err) {
console.error('SELECT state=0 error:', err)
} else {
// do something here
}
}, (err, count) => {
// do something here
})
})

4. 更新

1
2
3
4
5
6
7
db.run("UPDATE test SET state=1 WHERE state=0", (err) => {
if (err) {
console.error('UPDATE txs error:', err)
} else {
console.log('UPDATE state to 1')
}
})

参考

http://www.sqlitetutorial.net/sqlite-nodejs/