您正在查看: EOS 分类下的文章

激活 ACTION_RETURN_VALUE

激活动作返回值协议

cleos push action eosio activate '["c3a6138c5061cf291310887c0b5c71fcaffeab90d5deb50d3b9e687cead45071"]'

查询是否已开启

curl -X POST http://localhost:8888/v1/producer/get_supported_protocol_features | jq -r
...
{                                                                                                                                      
    "feature_digest": "c3a6138c5061cf291310887c0b5c71fcaffeab90d5deb50d3b9e687cead45071",
    "subjective_restrictions": {
      "enabled": true,
      "preactivation_required": true,
      "earliest_allowed_activation_time": "1970-01-01T00:00:00.000"
    },
    "description_digest": "69b064c5178e2738e144ed6caa9349a3995370d78db29e494b3126ebd9111966",
    "dependencies": [],
    "protocol_feature_type": "builtin",
    "specification": [
      {
        "name": "builtin_feature_codename",
        "value": "ACTION_RETURN_VALUE"
      }
    ]
  }
...

EOS 链mongo 节点数据分片shard key 选取

由于目前旧链还是基于链mongo同步节点存储数据,mongo服务使用的阿里云云数据库 MongoDB,单片存储已经接近最大存储值(2T),所以需要做分片扩容。
主要的注意点有

  1. 每个表 shard key的选择,以及如何设置
  2. 检查各片均衡同步是否正常

shard key选择

每个表shard key的选择要查看链源码mongo插件源码中的upsert(true)所对应的update_one的查询索引进行一致设置,否则会报错

codeName" : "ShardKeyNotFound", "errmsg" : "Failed to target upsert by query :: could not extract exact shard key"

upsert = true, 是说使用update_one代替insert_one,这样方便去重,如果不存在则insert,存在则update。
对于mongo分片集合建立的索引要与各表upsert=trueupdate_one所需的查询索引一致。

对于upsert=true 各表索引如下

account_controls
  1. controlled_account
  2. controlled_permission
  3. controlling_account
accounts
  1. name
action_traces

block_states
  1. block_id

如果开启了mongodb-update-via-block-num

  1. block_num
blocks
  1. block_id

如果开启了mongodb-update-via-block-num

  1. block_num
pub_keys
  1. account
  2. public_key
  3. permission
transaction_traces

transactions
  1. trx_id

设置shard key

参考: https://helpcdn.aliyun.com/document_detail/99411.html

所在的数据库启用分片功能

sh.enableSharding("EOS")

transactions

db.transactions.createIndex({trx_id:1})             // 对片键的字段建立索引
sh.shardCollection("EOS.transactions",{"trx_id":1}) // 对集合设置数据分片。
trx_id 不存在添加shard key出错
"message" : "There are documents which have missing or incomplete shard key fields ({ : null }). Please ensure that all documents in the collection include all fields from the shard key.",

将没有trx_id的记录删掉,此记录为无效数据

db.transactions.deleteMany({trx_id: {$exists: false}})
generic server error

由于空交易块,会不存在trx_id,导致upsert失败,所以将空块排除
config增加

mongodb-filter-out = eosio:onblock:

blocks

db.blocks.createIndex({block_id:1})             // 对片键的字段建立索引
sh.shardCollection("EOS.blocks",{"block_id":1}) // 对集合设置数据分片。

block_states

db.block_states.createIndex({block_id:1})             // 对片键的字段建立索引
sh.shardCollection("EOS.block_states",{"block_id":1}) // 对集合设置数据分片。

account_controls

db.account_controls.createIndex({controlled_account:1})                // 对片键的字段建立索引
db.account_controls.createIndex({controlled_permission:1})             // 对片键的字段建立索引
db.account_controls.createIndex({controlling_account:1})               // 对片键的字段建立索引

sh.shardCollection("EOS.account_controls",{"controlled_account":1})    // 对集合设置数据分片。

accounts

db.accounts.createIndex({name:1})             // 对片键的字段建立索引
sh.shardCollection("EOS.accounts",{"name":1}) // 对集合设置数据分片。

pub_keys

db.pub_keys.createIndex({account:1})                // 对片键的字段建立索引
db.pub_keys.createIndex({public_key:1})             // 对片键的字段建立索引
db.pub_keys.createIndex({permission:1})             // 对片键的字段建立索引
sh.shardCollection("EOS.pub_keys",{"account":1})    // 对集合设置数据分片。
sh.shardCollection("EOS.pub_keys",{"public_key":1}) // 对集合设置数据分片。
sh.shardCollection("EOS.pub_keys",{"permission":1}) // 对集合设置数据分片。

其他表

对于其他表,并没有用到upsert=true,所以可以根据需要做处理

action_traces
db.action_traces.createIndex({trx_id:1})             // 对片键的字段建立索引
sh.shardCollection("EOS.action_traces",{"trx_id":1}) // 对集合设置数据分片。
transaction_traces
db.transaction_traces.createIndex({id:1})             // 对片键的字段建立索引
sh.shardCollection("EOS.transaction_traces",{"id":1}) // 对集合设置数据分片。

操作细节

买好阿里的云数据库 MongoDB,再附加买一个分片,此时查看db.stats()
能看到2个分片存储

{
    "raw" : {
        "mgset-40806644/..." : {
            "db" : "EOS",
            ...
            "ok" : 1.0
        },
        "mgset-40806643/..." : {
            "db" : "EOS",
            ...
            "ok" : 1.0
        }
    },
    "objects" : 767319,
    ...
}

配置好同步节点,先往数据库推送链数据
此时看到,只有mgset-40806643在增加数据,因为我们还没有让数据库做分片处理

设置分片

细节就是执行上面的设置shard key
每增加一个shardCollection,发现"mgset-40806644中的collections会对应的增加(需要数量到一定级别(2个chunk以上,1个chunk默认是64MB,sh.status()查看chunk数量)才会做均衡,第一次均衡后新加的分片才会创建对应的集合)
添加shardCollection后,对应集合的索引会自动同步过去
查看对应表的stats,比如db.action_traces.stats(),能看到sharded = true

注意

对于上面的操作,大部分索引已经默认添加,无需重复操作,但是对于用到upsert=true的表shardCollection必须与插件一致,否则报ShardKeyNotFound

参考

https://docs.mongodb.com/manual/core/sharding-shard-key/?spm=a2c4g.11186623.2.25.544e1d8d2ORfYO#choosing-a-shard-key
https://help.aliyun.com/document_detail/100658.html?spm=a2c4g.11186623.6.762.5b3155f12yRKuU
https://helpcdn.aliyun.com/document_detail/99411.html

get_table对于time_point类型得定位查询

根据场景需求需要对已存储得数据做更新查询,所以需要对update_timepoint做查询索引

struct [[eosio::table, eosio::contract("bcskill.com")]] xxxx_info {
        uint64_t id;                                        // 记录id
        eosio::checksum256 trx_id;                          // 交易id
        ....
        time_point create_timepoint;                        // 创建时间
        time_point update_timepoint;                        // 更新时间

        uint64_t primary_key() const { return id; }
        eosio::checksum256 second_key() const { return trx_id; }
        uint64_t third_key() const { return update_timepoint.time_since_epoch().count();} // 把time_point类型转换成微妙(1/1000000 秒)
    };

table中测试数据如下

{
  "rows": [{
      "id": 0,
      "trx_id": "d78c4da408a4d9c75ba03af4a481d79fa80e2b8e215e68a1fe1865fd866cc7bc",
      ...
      "create_timepoint": "2020-12-17T11:24:07.500",
      "update_timepoint": "2020-12-17T11:27:18.500"
    },{
      "id": 1,
      "trx_id": "7bb0038be49b171f8a394bfdd7da15f9a005908b25ca1b6aed31b2975d721730",
      ...
      "create_timepoint": "2020-12-17T11:29:01.500",
      "update_timepoint": "2020-12-17T11:30:01.500"
    },{
      "id": 2,
      "trx_id": "8cf1c32d38692dd93870c7a2376d617f85efa4f093588dbce94f9981d4e1818e",
      ...
      "create_timepoint": "2020-12-17T11:30:01.500",
      "update_timepoint": "2020-12-17T11:30:01.500"
    }
  ],
  "more": false
}

UTC时间转时间戳,目前没有找到现成的工具可以直接转换,
https://tool.lu/timestamp/ 只找到这个北京时间转时间戳的工具(稍后找到其他直接工具,或者单独开发个工具再做文章更新)

UTC 转北京时间(+8)

暂时先将UTC时间加8小时转换成本地时间,测试的UTC时间为2020-12-17T11:27:18.500,转换为本地时间为2020-12-17 19:27:18 其中的.500为0.5秒,后面再单独加上。

北京时间转时间戳

利用上面的在线工具2020-12-17 19:27:18得到1608204438,加上上面的0.500得到1608204438.5

根据合约类型需要转换为微妙

1608204438.5 * 1000000 得到 1608204438500000

根据测试数据演示下查询

cleos get table bcskillsurou bcskillsurou ammtb --index 3 --key-type i64 -L 1608204438500000 -U 1608204438500000

得到指定的记录

{
  "rows": [{
      "id": 0,
      "trx_id": "d78c4da408a4d9c75ba03af4a481d79fa80e2b8e215e68a1fe1865fd866cc7bc",
      ...
      "create_timepoint": "2020-12-17T11:24:07.500",
      "update_timepoint": "2020-12-17T11:27:18.500"
    }
  ],
  "more": false
}

EOSIO v2.1.0 动作返回值

昨晚期待已久的2.1.0 rc 版本终于发版了,
https://github.com/EOSIO/eos/releases/tag/v2.1.0-rc1
新的协议功能:ACTION_RETURN_VALUE。
https://github.com/EOSIO/eos/pull/8327
激活后,此功能提供了一种方法,该方法可以将返回的值在操作中的块头中严格落实到外部进程中,而不必依赖get_table或通过print语句使用调试控制台。这使智能合约开发人员能够直接处理操作的返回值。进一步简化智能合约开发流程。一个例子可以在这里看到。

演示例子

合约代码

[[eosio::action]]
int sum(int valueA, int valueB) {
    return valueA + valueB; // 合约返回结果
}

前端推送完交易后,直接获取返回值

 const transactionResult = await api.transact({
        actions: [{
          account: 'returnvalue',
          name: 'sum',
          authorization: [{
            actor: 'returnvalue',
            permission: 'active',
          }],
          data: {
            valueA: numbers.first,
            valueB: numbers.second
          }
        }]
      }, {
        blocksBehind: 3,
        expireSeconds: 30
      }) as any
      setResult(transactionResult.processed.action_traces[0].return_value_data) // 直接获取返回值

EOS 合约 hex 字符串与checksum256互转

hex to checksum256

eosio::checksum256 hex_to_checksum256(const std::string& in) {
    eosio::check(in.length() == 64, "checksum size is error");
    std::string hex{"0123456789abcdef"};
    eosio::checksum256 out;
    for (int i = 0; i < 32; i++) {
      auto d1 = hex.find(in[2 * i]);
      auto d2 = hex.find(in[2 * i + 1]);
      eosio::check(d1 != std::string::npos || d2 != std::string::npos,
                  "invalid sha256");

      // checksum256 is composed of little endian int128_t
      reinterpret_cast<char*>(out.data())[i / 16 * 16 + 15 - (i % 16)] =
          (d1 << 4) + d2;
    }
    return out;
}

checksum256 to hex string

static string to_hex(const checksum256 &hashed) {
    // Construct variables
    string result;
    const char *hex_chars = "0123456789abcdef";
    const auto bytes = hashed.extract_as_byte_array();
    // Iterate hash and build result
    for (uint32_t i = 0; i < bytes.size(); ++i) {
        (result += hex_chars[(bytes.at(i) >> 4)]) += hex_chars[(bytes.at(i) & 0x0f)];
    }
    // Return string
    return result;
}

参考

https://github.com/EOSIO/eos/issues/4012