这几天工作中遇到需要后端的 nodejs 与 EOS 合约 进行交互的需求,简单记录下一些开始的配置项目
合约
假设我们有一个 micromsg
合约,有两个动作 action : send
用于留言,read
用于设置留言已经读过和一张表 messages
用于记录用户留言
留言的字段如下
字段 | 类型 | 说明 |
---|---|---|
id | uint64_t | 自增 id |
from | eosio::name | 谁留的言 |
to | eosio::name | 给谁的留言 |
msg | std::string | 留言内容 |
tm | uint64_t | 留言时间 |
readed | uint8_t | 是否已读 |
索引
这里为了简单起见,我们只给自增 id
添加索引,实际上,应该给 to
也添加一个索引
uint64_t primary_key() const { return id;}
动作 send
用于发送留言,原型如下
[[eosio::action]] void send(name from, name to, string msg);
动作 read
用于设置留言已经读取,原型如下
[[eosio::action]] void read(name to, uint64_t msg_id);
完整的合约代码如下
microsmg/micromsg.cpp
#include <eosiolib/eosio.hpp> #include <eosiolib/multi_index.hpp> #include <eosiolib/system.h> using namespace std; using namespace eosio; class [[eosio::contract]] micromsg: eosio::contract { public: using eosio::contract::contract; [[eosio::action]] void send(name from, name to, string msg) { require_auth(from); eosio_assert(msg.size() > 0, "msg must not be empty"); // 通知发送者 require_recipient(from); // 通知接受者 require_recipient(to); messages _tbl(_self,_self.value); auto id = _tbl.available_primary_key(); _tbl.emplace(_self, [&](auto &s) { s.id = id; s.from = from; s.to = to; s.msg = msg; s.tm = now(); s.readed =0; }); print("#", id, " ",from , " send '" ,std::string(msg) , "' to " , to ," at " , now()); } [[eosio::action]] void read(name to ,uint64_t msg_id) { require_auth(to); messages _tbl(_self,_self.value); auto it = _tbl.find(msg_id); if ( it != _tbl.end()) { if ( it->to == to && it->readed == 0 ) { _tbl.modify(it,same_payer,[&](auto &s){ s.readed = 1; }); } print("ok"); } } struct [[eosio::table]] message { uint64_t id; name from; name to; string msg; uint64_t tm; bool readed; uint64_t primary_key() const { return id;} }; typedef multi_index<"messages"_n, message> messages; }; EOSIO_DISPATCH(micromsg,(send)(read))
编译部署合约
使用下面的命令编译
eosio-cpp -o micromsg.wasm micromsg.cpp --abigen
接着在本地 nodeos 环境中使用下面的命令创建三个账号
你需要将公钥换成你自己的
-
合约账号
cleos create account eosio micromsg EOS8iQXBhFiiBogGiqacVRCuk3mxPzELe15iA89A8wuw5SKpXzoZr EOS8iQXBhFiiBogGiqacVRCuk3mxPzELe15iA89A8wuw5SKpXzoZr
-
小明
cleos create account eosio xiaoming EOS8iQXBhFiiBogGiqacVRCuk3mxPzELe15iA89A8wuw5SKpXzoZr EOS8iQXBhFiiBogGiqacVRCuk3mxPzELe15iA89A8wuw5SKpXzoZr
-
小红
cleos create account eosio xiaohong EOS8iQXBhFiiBogGiqacVRCuk3mxPzELe15iA89A8wuw5SKpXzoZr EOS8iQXBhFiiBogGiqacVRCuk3mxPzELe15iA89A8wuw5SKpXzoZr
接着使用下面的命令部署合约
cleos set contract micromsg ../micromsg -p micromsg
测试
最后,我们使用下面的命令来检查下合约是否正确
-
首先 小明给小红发信息
cleos push action micromsg send '["xiaoming","xiaohong","i miss you"]' -p xiaoming
可以看到输出如下
cleos push action micromsg send '["xiaoming","xiaohong","i miss you"]' -p xiaoming executed transaction: 56d236d199415fc14b2a711c296014131b9a2504439567fcc2c24c4a824fff97 120 bytes 705 us # micromsg <= micromsg::send {"from":"xiaoming","to":"xiaohong","msg":"i miss you"} >> #0 xiaoming send 'i miss you' to xiaohong at 1543709972 # xiaoming <= micromsg::send {"from":"xiaoming","to":"xiaohong","msg":"i miss you"} # xiaohong <= micromsg::send {"from":"xiaoming","to":"xiaohong","msg":"i miss you"}
从输出结果中可以看出,我们发送的消息 id 是 0
重复使用该命令多发送几条
-
然后我们使用
cleos get table
命令查看下我们发送的数据,完整命令如下micromsg yufei$ cleos get table micromsg micromsg messages
输出结果如下:
{ "rows": [{ "id": 0, "from": "xiaoming", "to": "xiaohong", "msg": "i miss you", "tm": 1543709972, "readed": 0 },{ "id": 1, "from": "xiaoming", "to": "xiaohong", "msg": "i miss you", "tm": 1543710047, "readed": 0 },{ "id": 2, "from": "xiaoming", "to": "xiaohong", "msg": "i miss you", "tm": 1543710048, "readed": 0 },{ "id": 3, "from": "xiaoming", "to": "xiaohong", "msg": "i miss you", "tm": 1543710048, "readed": 0 },{ "id": 4, "from": "xiaoming", "to": "xiaohong", "msg": "i miss you", "tm": 1543710049, "readed": 0 },{ "id": 5, "from": "xiaoming", "to": "xiaohong", "msg": "i miss you", "tm": 1543710049, "readed": 0 } ], "more": false }
-
接着, 我们使用小红的账号,设置
#id
为0
的信息设置为已读cleos push action micromsg read '["xiaohong",1]' -p xiaohong executed transaction: b82ca3430652c6a07c1d73c15096d54397bf516d097f090cdc6f4d7109740157 112 bytes 367 us # micromsg <= micromsg::read {"to":"xiaohong","msg_id":1} >> ok
好了,关于合约方面,我们已经开发、部署、测试完毕了,现在开始使用 nodejs 与合约进行交互
nodejs eos 环境搭建
nodejs 与 EOS 合约交互一般使用 eosjs
包,可以使用下面的命令来安装
npm i eosjs
接着在当前目录下新建一个文件 micromsg.js
,这个文件将包含所有与 EOS 合约交互的代码
touch micormsg.js
然后打开 micormsg.js
文件,开始我们的代码之旅
首先,我们需要引入 eosjs
const Eos = require('eosjs');
其次,我们要做的就是创建 EOS 链配置,需要配置的项目有
const config = { account: { contract: "micormsg", // 合约账号 from: "xiaoming", // 发送者账号 to: "xiaohong" // 接收者账号 }, network: { protocol:'http', // 协议 http or https host: 'localhost', // 远程主机 port: '8080', // 远程端口号 keys:['5J6Ly95q876iEwG1c4bBcWAWqHbibRmhDsxtSrnsSswpT7r7gAC'], // 私钥 chainId: 'cf057bbfb72640471fd910bcb67639c22df9f92470936cddc1ade0e2f2e7dc4f', // EOS 链 id timeout: 10000 // 10s 超时 } }
配置项需要根据你自己的 nodeos 的节点配置,尤其是是 keys
,因为我三个账号共用一个公钥,所以它们的私钥也就一个了。
3. 创建 Eos 的实例
var eos = Eos(config);
4. 测试获取 Eos 链信息,用户检查我们的配置项是否正确
这里,我们需要使用到 eos.getInfo
函数。
注意,所有的 eosjs
函数,如果不传递任何参数,那么就是显示该函数的帮助信息
console.log(eos.getInfo());
输出结果如下
USAGE getInfo - Return general network information. PARAMETERS none RETURNS "string" ERRORS nothing special
如果函数参数为空,则需要传递空对象 {}
如果函数执行成功,那么就会返回一个 Promise
console.log(eos.getInfo({}));
输出结果如下
Promise { <pending> }
对于返回的 Promise
,我们可以调用 then()
来获取返回结果,而调用 catch
来捕获错误,使用方式大概如下
eos.getInfo({}).then(rs => console.log(rs)).catch(e => console.log(e))
运行结果如下
{ server_version: '59626f1e', chain_id: 'cf057bbfb72640471fd910bcb67639c22df9f92470936cddc1ade0e2f2e7dc4f', head_block_num: 67187, last_irreversible_block_num: 67186, last_irreversible_block_id: '00010672d813e96d11b5d32f0123fc3b64a7328e68b15f45f067d382d7a3647b', head_block_id: '00010673664500983ec1d7a6a004f62f98f70af18462d6a0acc7e4ae98985ba1', head_block_time: '2018-12-02T00:53:52.500', head_block_producer: 'eosio', virtual_block_cpu_limit: 200000000, virtual_block_net_limit: 1048576000, block_cpu_limit: 199900, block_net_limit: 1048576, server_version_string: 'v1.4.4' }
看到这个结果,表示我们的配置是正确的。
所有的代码如下
const Eos = require('eosjs'); const config = { account: { contract: "micormsg", // 合约账号 from: "xiaoming", // 发送者账号 to: "xiaohong" // 接收者账号 }, network: { protocol:'http', // 协议 http or https host: 'localhost', // 远程主机 port: '8080', // 远程端口号 keyProvider:['5J6Ly95q876iEwG1c4bBcWAWqHbibRmhDsxtSrnsSswpT7r7gAC'], // 私钥 chainId: 'cf057bbfb72640471fd910bcb67639c22df9f92470936cddc1ade0e2f2e7dc4f', // EOS 链 id timeout: 10000 // 10s 超时 } } var eos = Eos(config.network); eos.getInfo({}).then(rs => console.log(rs)).catch(e => console.log(e))