您正在查看: EOS-新手教程 分类下的文章

eos 离线计算交易id

场景

由于一些场景需要提前计算出上传的交易ID,
类似问题,已提交issue https://github.com/EOSIO/eos/issues/7816
由于一些问题,(猜测单个nodeos推送了太多事务)导致事务推送返回超时,但实际上事务已成功推送。我暂时想到的解决方案是,我希望在提交交易之前预先计算交易的ID,以便在重复交易时首先确定交易ID是否已经存在。搜索之后,我发现'cleos get transaction_id'(#6830)似乎是我想要的功能,但是测试不同的数据,总是返回相同的id。我用方法问题?或者这个功能没有像这样使用

跟进

先提前跟一下代码
cleos get transaction_id
https://github.com/EOSIO/eos/blob/1418543149b7caf8fc69a23621e3db7f3c6d18ad/programs/cleos/main.cpp#L1306

struct get_transaction_id_subcommand {
   string trx_to_check;

   get_transaction_id_subcommand(CLI::App* actionRoot) {
      auto get_transaction_id = actionRoot->add_subcommand("transaction_id", localized("Get transaction id given transaction object"));
      get_transaction_id->add_option("transaction", trx_to_check, localized("The JSON string or filename defining the transaction which transaction id we want to retrieve"))->required();

      get_transaction_id->set_callback([&] {
         try {
            fc::variant trx_var = json_from_file_or_string(trx_to_check);
            if( trx_var.is_object() ) {
               fc::variant_object& vo = trx_var.get_object();
               // if actions.data & actions.hex_data provided, use the hex_data since only currently support unexploded data
               if( vo.contains("actions") ) {
                  if( vo["actions"].is_array() ) {
                     fc::mutable_variant_object mvo = vo;
                     fc::variants& action_variants = mvo["actions"].get_array();
                     for( auto& action_v : action_variants ) {
                        if( !action_v.is_object() ) {
                           std::cerr << "Empty 'action' in transaction" << endl;
                           return;
                        }
                        fc::variant_object& action_vo = action_v.get_object();
                        if( action_vo.contains( "data" ) && action_vo.contains( "hex_data" ) ) {
                           fc::mutable_variant_object maction_vo = action_vo;
                           maction_vo["data"] = maction_vo["hex_data"];
                           action_vo = maction_vo;
                           vo = mvo;
                        } else if( action_vo.contains( "data" ) ) {
                           if( !action_vo["data"].is_string() ) {
                              std::cerr << "get transaction_id only supports un-exploded 'data' (hex form)" << std::endl;
                              return;
                           }
                        }
                     }
                  } else {
                     std::cerr << "transaction json 'actions' is not an array" << std::endl;
                     return;
                  }
               } else {
                  std::cerr << "transaction json does not include 'actions'" << std::endl;
                  return;
               }
               auto trx = trx_var.as<transaction>();
               transaction_id_type id = trx.id(); // 计算交易id
               if( id == transaction().id() ) {
                  std::cerr << "file/string does not represent a transaction" << std::endl;
               } else {
                  std::cout << string( id ) << std::endl;
               }
            } else {
               std::cerr << "file/string does not represent a transaction" << std::endl;
            }
         } EOS_RETHROW_EXCEPTIONS(transaction_type_exception, "Fail to parse transaction JSON '${data}'", ("data",trx_to_check))
      });
   }
};

再查看主链代码
https://github.com/EOSIO/eos/blob/1e9ca55cc35b003c81ff4da780fb3cb869a16607/libraries/chain/transaction.cpp#L66

transaction_id_type transaction::id() const {
   digest_type::encoder enc;
   fc::raw::pack( enc, *this );
   return enc.result();
}

digest_type transaction::sig_digest( const chain_id_type& chain_id, const vector<bytes>& cfd )const {
   digest_type::encoder enc;
   fc::raw::pack( enc, chain_id );
   fc::raw::pack( enc, *this );
   if( cfd.size() ) {
      fc::raw::pack( enc, digest_type::hash(cfd) );
   } else {
      fc::raw::pack( enc, digest_type() );
   }
   return enc.result();
}

看实现代码,应该没问题
再看看关键词,每次都是返回374708fff7719dd5979ec875d56cd2286f6d3cf7ec317a3b25632aab28ec37bb
搜一下
https://github.com/EOSIO/eos/issues/6603
过期交易错误,会返回这种?
猜测是当前获取交易id的数据过期了,那使用新的交易体计算下。
待补充

等等,先去eosjs 那边看下,

反向到eosjs找下线索

const serializedTransaction = await api.transact({
        ....
      }, {
        blocksBehind: 3,
        expireSeconds: 3600,
        broadcast: false
      });
console.log(Crypto.createHash("sha256").update(serializedTransaction).digest("hex"));

前端可使用 Crypto https://github.com/crypto-browserify/createHash

java
https://github.com/adyliu/jeos/blob/f0ef9bd72b97a118b077f6d585146d36b9739015/src/main/java/io/jafka/jeos/util/SHA.java#L19

 public static byte[] sha256(final byte[] b) {
        Objects.requireNonNull(b);
        try {
            MessageDigest messageDigest = MessageDigest.getInstance("SHA-256");
            messageDigest.update(b);
            return messageDigest.digest();
        } catch (Exception e) {
            throw new RuntimeException(e.getMessage(), e);
        }
    }

添加 contextFreeActions
https://github.com/EOSIO/eosio-java/blob/a9202879f31edb4122e768df0dc7fca391eff7e7/eosiojava/src/main/java/one/block/eosiojava/session/TransactionProcessor.java#L261

public void prepare(@NotNull List<Action> actions, @NotNull List<Action> contextFreeActions) throws TransactionPrepareError {

serializedTransaction 计算
https://github.com/EOSIO/eosio-java/blob/a9202879f31edb4122e768df0dc7fca391eff7e7/eosiojava/src/test/java/one/block/eosiojava/session/TransactionProcessorTest.java#L175

@Test
    public void serialize() {
        this.mockDefaultSuccessData();
        TransactionProcessor processor = createAndPrepareTransaction(this.defaultActions());
        assertNotNull(processor);

        try {
            String serializedTransaction = processor.serialize();
            assertEquals(MOCKED_TRANSACTION_HEX, serializedTransaction);
        } catch (TransactionSerializeError transactionSerializeError) {
            transactionSerializeError.printStackTrace();
        }
    }

参考

https://github.com/EOSIO/eosjs/issues/460
https://github.com/EOSIO/eos/issues/5935
https://github.com/EOSIO/eos/issues/6830
https://eosio.stackexchange.com/questions/1941/transaction-id-is-a-hash-of-what
https://github.com/EOSIO/eos/issues/7816

EOS contract uint128 convert to std::string

static const char* charmap = "0123456789";

std::string uint128ToString(const uint128_t& value)
{
    std::string result;
    result.reserve( 40 ); // max. 40 digits possible ( uint64_t has 20) 
    uint128_t helper = value;

    do {
        result += charmap[ helper % 10 ];
        helper /= 10;
    } while ( helper );
    std::reverse( result.begin(), result.end() );
    return result;
}

https://eosio.stackexchange.com/questions/2927/how-can-i-convert-a-uint128-t-to-a-string

一笔交易的资源开销,可以由智能合约创建者来支付

最近项目上线,数据一致性等问题已处理完,准备做使用资源上的一些优化,
考虑到普通账户CPU和net的资源有限,需要考虑下由合约方出这部分资源,减少用户反复抵押相应资源,以及了解资源相关的学习成本。
按照思路,应该是执行合约相关的动作,用户和合约账户一同多签,并指定资源消耗方为合约账户。

记得之前已经看过相关的EOSIO技术规划,有提到相关,那根下代码把
https://github.com/EOSIO/eos/blob/1418543149b7caf8fc69a23621e3db7f3c6d18ad/libraries/chain/transaction_context.cpp#L230

// Record accounts to be billed for network and CPU usage
      if( control.is_builtin_activated(builtin_protocol_feature_t::only_bill_first_authorizer) ) {
         bill_to_accounts.insert( trx.first_authorizer() );
      } else {
         for( const auto& act : trx.actions ) {
            for( const auto& auth : act.authorization ) {
               bill_to_accounts.insert( auth.actor );
            }
         }
      }

此时“侧链”代码为 V1.6.6, 预计等1.8.x稳定后,会升级到新共识协议,所以为了最小的差异,先做最小的修改支持吧。

为了简单的先实现,所以改下代码(本想放到config.ini,但是代码嵌套层较深,并且这种协议上的不会在做更新,直接放到代码里吧)
libraries\chain\include\eosio\chain\config.hpp
中增加一字段

const static bool       support_only_bill_first_authorizer           = true; // support only bill first authorizer

修改源代码为

// Record accounts to be billed for network and CPU usage
      if( config::support_only_bill_first_authorizer ) {
         bill_to_accounts.insert( trx.first_authorizer() );
      } else {
         for( const auto& act : trx.actions ) {
            for( const auto& auth : act.authorization ) {
               bill_to_accounts.insert( auth.actor );
            }
         }
      }

方案的实现建立在,客户端签名后,将数据发给后端,重新签名,并将项目方的验证授权放到第一个,这样执行的资源消耗就都算在项目方的账号上了。

更新各个节点,先支持,等待1.8.x稳定,在做共识性质的更新。

参考

https://github.com/EOSIO/spec-repo/blob/master/esr_contract_trx_auth.md
https://github.com/EOSIO/eos/issues/6332
https://github.com/EOSIO/eos/pull/7089
https://bihu.com/article/1522267629

EOS 相同data与相同的私钥签出的签名一样(Duplicate transaction error)

由于EOS防护机制,相同的data使用相同的私钥签出的签名是一样的,避免重复交易攻击,
但由于某些需求场景,即使data一致,也想相同签名每次签的不一致,那我们跟一下代码把
根据关键词Duplicate transaction error
https://github.com/EOSIO/eos/blob/eb88d033c0abbc481b8a481485ef4218cdaa033a/libraries/chain/controller.cpp#L2990:18

bool controller::is_known_unexpired_transaction( const transaction_id_type& id) const {
   return db().find<transaction_object, by_trx_id>(id);
}

查看计算trx_id的方法
https://github.com/EOSIO/eos/blob/1418543149b7caf8fc69a23621e3db7f3c6d18ad/programs/cleos/main.cpp#L1306

get_transaction_id->set_callback([&] {
         try {
            fc::variant trx_var = json_from_file_or_string(trx_to_check);
            if( trx_var.is_object() ) {
               fc::variant_object& vo = trx_var.get_object();
               // if actions.data & actions.hex_data provided, use the hex_data since only currently support unexploded data
               if( vo.contains("actions") ) {
                  if( vo["actions"].is_array() ) {
                     fc::mutable_variant_object mvo = vo;
                     fc::variants& action_variants = mvo["actions"].get_array();
                     for( auto& action_v : action_variants ) {
                        if( !action_v.is_object() ) {
                           std::cerr << "Empty 'action' in transaction" << endl;
                           return;
                        }
                        fc::variant_object& action_vo = action_v.get_object();
                        if( action_vo.contains( "data" ) && action_vo.contains( "hex_data" ) ) {
                           fc::mutable_variant_object maction_vo = action_vo;
                           maction_vo["data"] = maction_vo["hex_data"];
                           action_vo = maction_vo;
                           vo = mvo;
                        } else if( action_vo.contains( "data" ) ) {
                           if( !action_vo["data"].is_string() ) {
                              std::cerr << "get transaction_id only supports un-exploded 'data' (hex form)" << std::endl;
                              return;
                           }
                        }
                     }
                  } else {
                     std::cerr << "transaction json 'actions' is not an array" << std::endl;
                     return;
                  }
               } else {
                  std::cerr << "transaction json does not include 'actions'" << std::endl;
                  return;
               }
               auto trx = trx_var.as<transaction>();
               transaction_id_type id = trx.id();
               if( id == transaction().id() ) {
                  std::cerr << "file/string does not represent a transaction" << std::endl;
               } else {
                  std::cout << string( id ) << std::endl;
               }
            } else {
               std::cerr << "file/string does not represent a transaction" << std::endl;
            }

所以相同的data会的到相同的trx_id,以及报Duplicate transaction error

那如何解决呢,查看eos有没有相应的解决方案
查看 cleos工具

  -f,--force-unique           force the transaction to be unique. this will consume extra bandwidth and remove any protections against accidently issuing the same transaction multiple times

支持相应参数,那我们看下实现的细节
https://github.com/EOSIO/eos/blob/1418543149b7caf8fc69a23621e3db7f3c6d18ad/programs/cleos/main.cpp#L325

if (tx_force_unique) {
    trx.context_free_actions.emplace_back( generate_nonce_action() );
}

https://github.com/EOSIO/eos/blob/1418543149b7caf8fc69a23621e3db7f3c6d18ad/programs/cleos/main.cpp#L278:15

chain::action generate_nonce_action() {
   return chain::action( {}, config::null_account_name, "nonce", fc::raw::pack(fc::time_point::now().time_since_epoch().count()));
}

查看eosio.null账户执行 https://bloks.io/account/eosio.null

解决方案

每次发送相同的data时,交易体添加一个针对 eosio.null的nonce的context_free_actions

使用例子

describe('custom transactions', function () {
    const authorization = [{
      actor: 'inita',
      permission: 'active'
    }]

    const eos = Eos({
      keyProvider: wif
    })

    it('context_free_actions', async function() {
      await eos.transaction({
        context_free_actions: [{
            account: "eosio.null",
            name: 'nonce',
            data:  "652511569",
            authorization: []
        }],
        actions: [{
        account: "bcskillsurou",
        name: 'hi',
        authorization: [{
            actor: "bcskillsurou",
            permission: 'active'
        }],
        data: {
            "user": "bcskillsurou"
        }]
      })
    })

    it('nonce', async function() {
      const trx = await eos.transaction({
        actions: [ Object.assign({}, nonce, {authorization}) ],
      })
    })
  })

备注

eosio.null账户为系统默认创建账户
https://github.com/EOSIO/eos/blob/eb88d033c0abbc481b8a481485ef4218cdaa033a/libraries/chain/controller.cpp#L928

create_native_account( config::null_account_name, empty_authority, empty_authority );

对于ecdsa.sign同样数据签名,然后签名一样是因为nonce为固定值。
https://github.com/EOSIO/eosjs-ecc/blob/7ec577cad54e17da6168fdfb11ec2b09d6f0e7f0/src/signature.js#L207

    nonce = 0;
    e = BigInteger.fromBuffer(dataSha256);
    while (true) {
      ecsignature = ecdsa.sign(curve, dataSha256, privateKey.d, nonce++);

context_free_actions

通过对eosio.null账户的nouce动作,可以将无签名的数据打包进入context_free_action字段,结果区块信息如下:

$ cleos --wallet-url http://127.0.0.1:6666 --url http://127.0.0.1:8000 get block 440
{
  "timestamp": "2018-08-14T08:47:09.000",
  "producer": "eosio",
  "confirmed": 0,
  "previous": "000001b760e4a6610d122c5aa5d855aa49e29f3052ac3e40b9e1ef78e0f1fd02",
  "transaction_mroot": "32cb43abd7863f162f4d8f3ab9026623ea99d3f8261d2c8b4d8bf920ab97e3d1",
  "action_mroot": "09afeaf40d6988a14e9e92817d2ccf4023b280075c99f13782a6535ccc58cbb0",
  "schedule_version": 0,
  "new_producers": null,
  "header_extensions": [],
  "producer_signature": "SIG_K1_K2eFDzbxCg3hmQzpzPuLYmiesrciPmTHdeNsQDyFgcHUMFeMC3PntXTqiup5VuNmyb7qmH18FBdMuNKsc7jgCm1TSPFbaj",
  "transactions": [{
      "status": "executed",
      "cpu_usage_us": 290,
      "net_usage_words": 16,
      "trx": {
        "id": "d74843749d1e255f13572b7a3b95af9ddd6df23d1d0ad19d88e1496091d4be2b",
        "signatures": [
          "SIG_K1_KVzwg3QRH6ZmempNsvAxpPQa42hF4tDpV5cqwqo7EY4oSU7NMrEFwG7gdSDCnUHHhmH1EwtVAmV1z9bqtTvvQNSXiSgaWG"
        ],
        "compression": "none",
        "packed_context_free_data": "",
        "context_free_data": [],
        "packed_trx": "8497725bb601973ea96f0000000100408c7a02ea3055000000000085269d000706686168616861010082c95865ea3055000000000000806b010082c95865ea305500000000a8ed3232080000000000d08cf200",
        "transaction": {
          "expiration": "2018-08-14T08:49:08",
          "ref_block_num": 438,
          "ref_block_prefix": 1873362583,
          "max_net_usage_words": 0,
          "max_cpu_usage_ms": 0,
          "delay_sec": 0,
          "context_free_actions": [{
              "account": "eosio.null",
              "name": "nonce",
              "authorization": [],
              "data": "06686168616861"
            }
          ],
          "actions": [{
              "account": "eosiotesta1",
              "name": "hi",
              "authorization": [{
                  "actor": "eosiotesta1",
                  "permission": "active"
                }
              ],
              "data": {
                "user": "yeah"
              },
              "hex_data": "0000000000d08cf2"
            }
          ],
          "transaction_extensions": []
        }
      }
    }
  ],
  "block_extensions": [],
  "id": "000001b8d299602b289a9194bd698476c5d39c5ad88235460908e9d43d04edc8",
  "block_num": 440,
  "ref_block_prefix": 2492570152
}

正常的actions的内容是hi智能合约的调用,而context_free_action中包含了无签名的data数据,是已做数字摘要后的形态。源码中的操作:

//lets also push a context free action, the multi chain test will then also include a context free action
("context_free_actions", fc::variants({
    fc::mutable_variant_object()
       ("account", name(config::null_account_name))
       ("name", "nonce")
       ("data", fc::raw::pack(v))
    })
 )

参考

https://github.com/EOSIO/eos/pull/422
https://github.com/EOSIO/eosjs-ecc/commit/01419633630da42bd76c21503cadb4298cee1ad9
https://github.com/EOSIO/eos/pull/6829
https://github.com/ganioc/eosjs3/blob/203cdae9a70198b53c86e35b18a10560a99c1c12/src/index.test.js
https://github.com/EOSIO/eosjs-ecc/issues/20

Hyperion History API 解决方案

Hyperion History API解决方案

History API可以说是EOS主网上几个月来最紧迫的问题。DApps,块浏览器和钱包必须查阅历史信息才能正常工作,而在EOS主网上运行完整的历史记录变得昂贵,复杂且耗时。

V1 History API已被弃用,少数BP坚持不断为整个网络提供完整的公共历史节点,(特别感谢Sw / eden,CryptoLions,EOS Tribe,Greymass,EOS Canada,EOS Asia和Oracle Chain!),而其他许多人也都为解决这个问题付出了巨大努力。

有些人认为这不是一个大问题,认为DApp可以找到一种商业模式来专门支付于他们交易的部分历史节点,而块浏览器和钱包可以使用光历史。然而,EOS社区普遍认为,提供历史链数据可能会妨碍EOS满足可扩展性预期的能力,而这种预测常常会自己实现。

在撰写本文时(2019年3月7日),EOS Blockchain包含大约4600万个块,因此对于新手开始提供服务,节点必须摄取所有这些块以及每秒附加到块链中的另外两个块。目前来看,是一个需要数周的过程。同步后,当前的v1 History Plugin需要超过5 Tb的存储空间才能运行。查询此数据库需要大量处理能力和网络带宽。因此,运行完整的历史记录可能会花费超过15,000美元/月。
一个新视角

几个月前,EOS Rio团队开始就此问题的可能性解决方案进行头脑风暴。我们决定从头开始,而不是专注于感知瓶颈以增加数据摄取,存储和查询功能。第一步是分析可以采取哪些措施来优化数据库大小本身。我们了解到History API v1存储了大量冗余信息。

原始的history_plugin与eosio捆绑在一起,提供了v1 API,存储了嵌套在根操作中的内联动作跟踪。每当用户请求给定帐户的操作历史时,将导致存储和传输过量数据。此外,内联操作通常用作“事件”机制,以通知交易方,并且存储它几乎没有价值。

Hyperion History实现了一种新的数据结构和存储方法:

  • 动作以展平格式存储。
  • 将父字段添加到内联操作以指向父全局序列。
  • 如果内联操作数据与父操作数据相同,则将其视为通知,从而从数据库中删除。
  • 没有存储块或事务数据,可以从动作重建所有信息。
  • 没有存储交易验证信息,因为可以使用Chain API在块信息上验证所有信息,DApp不使用历史记录。
  • 通过这些更改,API格式专注于缩短响应时间,降低带宽开销,并使UI / UX开发人员更易于使用。

数据结构存储如下:

小但强大
更改格式和减少数据冗余可将数据库大小减少约85%,从近5 Tb减少到约650 Gb。为了进一步提高性能,我们设计了一个多线程索引器,它从状态历史插件中提取数据,并且可以在大约72小时内通过适当的硬件优化来摄取完整的EOS区块链,而当前的解决方案可能需要数周时间。

我们还引入了“ABI历史缓存层”组件,以防止在ABI修改上并行处理历史数据时出现反序列化失败。
对于数据库,我们部署了一个Elasticsearch集群,该集群在位于巴西里约热内卢的一级基础架构上并置的两个定制组装裸机服务器上运行。
优化的数据结构倾向于减少CPU和带宽消耗,使基础架构更具可扩展性。运行完. 整历史API的其他BP已经在测试Hyperion并帮助其发展。

我们非常感谢来自EOS Cafe的Syed Jafri为Hyperion HTTP API创建了一个javascript库,并且已经在 bloks.i上集成了Hyperion History(v2 API)。还有Sw / eden团队在cleos上添加v2兼容性。非常感谢eosDAC,CryptoLions和BlockMatrix的贡献。

新history API标准的建议

  • 对于开发人员来说,提供扁平的结果优于今天的history API标准。当前的eosio历史插件不必要地使用冗余信息来扩充数据库(用于最终用户历史记录)。过滤内联操作的可能性允许减少API带宽消耗和编码复杂性。
  • 为适应这些变化,EOS Rio和其他开发历史解决方案的BP们正在倡导history API V2标准,也终将被EOS 社区采用。
  • 请您用一点儿宝贵时间对其进行评估并向我们发送反馈信息。

一个开源项目

EOS Rio已经在 https://eos.hyperion.eosrio.io/v2/docs/index.html上使用Hyperion提供History API。
项目代码和初步设置说明可在 https://github.com/eosrio/Hyperion-History-API获,我们将根据开源许可证发布此用于非商业用途

我们可以帮助任何想要运行Hyperion History的人,我们期待您的反馈信息。
下一步
下一步是为操作流实现WebSocket API。这就是我们现在正在做的事情。
完成后,下一个功能将实现Hyperion Analytics,这是Hyperion History API上的一个高级层,可提供详细的EOS统计信息。
相关链接
文档链接:https://eos.hyperion.eosrio.io/v2/docs/index.html
源代码链接:https://github.com/eosrio/Hyperion-History-API
Javascript库链接:https://github.com/eoscafe/hyperion-api

转载自:https://medium.com/@eosriobrazil/presenting-hyperion-history-api-solution-f8a8fda5865b
参考: https://www.boswps.io/#/poll_detail?proposal=hyperion.api