bitshares研究系列【问题集二】

在bitshares学习过程中,有一些零散问题整理在一块,互相之间并不一定存在关联性,可能也会穿插一些非bitshares的内容,计划每几个问题做一集发表。

1. bitshares节点数据同步

看看bitshares节点怎么同步数据的?

在启动node节点时可以指定-seed-node和--seed-nodes参数,指明启动时要连接的p2p节点,也可以在config.ini文件中指定。

seed-node 指定节点会直接连接

seed-nodes 指定为"[]"时不会连接线上节点,如果不指定则会连接代码中指定的世界各地缺省节点。

代码处理部分在application.cpp中:

      void reset_p2p_node(const fc::path& data_dir)
      { try {
         _p2p_network = std::make_shared<net::node>("BitShares Reference Implementation");
         ...
     }

node.cpp主要实现节点网络处理:

    void node_impl::connect_to_p2p_network()
    {
      VERIFY_CORRECT_THREAD();
      assert(_node_public_key != fc::ecc::public_key_data());

      assert(!_accept_loop_complete.valid() &&
             !_p2p_network_connect_loop_done.valid() &&
             !_fetch_sync_items_loop_done.valid() &&
             !_fetch_item_loop_done.valid() &&
             !_advertise_inventory_loop_done.valid() &&
             !_terminate_inactive_connections_loop_done.valid() &&
             !_fetch_updated_peer_lists_loop_done.valid() &&
             !_bandwidth_monitor_loop_done.valid() &&
             !_dump_node_status_task_done.valid());
      if (_node_configuration.accept_incoming_connections)
        _accept_loop_complete = fc::async( [=](){ accept_loop(); }, "accept_loop");
      _p2p_network_connect_loop_done = fc::async( [=]() { p2p_network_connect_loop(); }, "p2p_network_connect_loop" );
      _fetch_sync_items_loop_done = fc::async( [=]() { fetch_sync_items_loop(); }, "fetch_sync_items_loop" );
      _fetch_item_loop_done = fc::async( [=]() { fetch_items_loop(); }, "fetch_items_loop" );
      _advertise_inventory_loop_done = fc::async( [=]() { advertise_inventory_loop(); }, "advertise_inventory_loop" );
      _terminate_inactive_connections_loop_done = fc::async( [=]() { terminate_inactive_connections_loop(); }, "terminate_inactive_connections_loop" );
      _fetch_updated_peer_lists_loop_done = fc::async([=](){ fetch_updated_peer_lists_loop(); }, "fetch_updated_peer_lists_loop");
      _bandwidth_monitor_loop_done = fc::async([=](){ bandwidth_monitor_loop(); }, "bandwidth_monitor_loop");
      _dump_node_status_task_done = fc::async([=](){ dump_node_status_task(); }, "dump_node_status_task");
    }

其它节点连上来时会取得此节点所知道的节点信息吗?

    void node_impl::fetch_updated_peer_lists_loop()
    {
      VERIFY_CORRECT_THREAD();

      std::list<peer_connection_ptr> original_active_peers(_active_connections.begin(), _active_connections.end());
      for( const peer_connection_ptr& active_peer : original_active_peers )
      {
        try
        {
          active_peer->send_message(address_request_message());
        }
        catch ( const fc::canceled_exception& )
        {
          throw;
        }
        catch (const fc::exception& e)
        {
          dlog("Caught exception while sending address request message to peer ${peer} : ${e}",
               ("peer", active_peer->get_remote_endpoint())("e", e));
        }
      }
      ...
    }

2. 怎么修改operation费用

  1. 初始可以在genesis.json文件中设置每个operation的费用,如下:
  "initial_parameters": {
    "current_fees": {
      "parameters": [[
          0,{
            "fee": 2000000,
            "price_per_kbyte": 1000000
          }
        ],[
          1,{
            "fee": 500000
          }
        ]
        ...
  1. 可以通过cli_wallet的提议修改,如下:
propose_fee_change "nathan" "2018-06-15T00:00:00" {"transfer":{"fee":12,"price_per_kbyte":2}} true

{
  "ref_block_num": 30364,
  "ref_block_prefix": 1398860509,
  "expiration": "2018-05-31T03:26:35",
  "operations": [[
      22,{
        "fee": {
          "amount": 2000007,
          "asset_id": "1.3.0"
        },
        "fee_paying_account": "1.2.17",
        "expiration_time": "2018-10-01T00:00:00",
        "proposed_ops": [{
            "op": [
              31,{
                "fee": {
                  "amount": 100000,
                  "asset_id": "1.3.0"
                },
                "new_parameters": {
                  "current_fees": {
                    "parameters": [[
                        0,{
                          "fee": 12,
                          "price_per_kbyte": 2
                        }

再同意此次的提议即可。

3. 怎么修改全局参数?

在cli_wallet执行提议修改:

propose_parameter_change "nathan" "2018-06-15T00:00:00" {"block_interval":3} true

在修改全局参数和修改费用时都碰到以下错误:

Assert Exception: !o.review_period_seconds || fc::seconds(*o.review_period_seconds) < (o.expiration_time - d.head_block_time()): Proposal review period must be less than its overall lifetime.

这个是因为审查周期大于等于提议过期时间,当时测试时review_period_seconds为

    "committee_proposal_review_period": 1209600,

也就是14天,所以提议过期时间得设为超过14天才行。当然提案都得有人同意才会生效。

4. 比特币每笔交易的最小数量?

0.00000546BTC

这也是Omni转帐时的缺省最小比特币数量。

为什么有限制?测试下的确有限制,认为数量太小就是DUST!

浪费0.00000374BTC测试一下,的确是最小转帐数量0.00000546BTC,看起来矿工费可以小于这个值![这笔交易](](https://btc.com/f88d677a817c90c485b1599148fb9f57008457d6d2bdcaffb84ab5e13c5f82dd)也是我比特币转帐金额和矿工费都最少的一次交易了

5. cli_wallet非得手动解锁吗?

在业务中许多时候需要用到cli_wallet,例如水龙头等通过cli_wallet调用,如果每次启动cli_wallet时都需要手动解锁,那还是很麻烦的,而且发现的确很多人是这么做的。

首先cli_wallet可以支持daemon运行,只要在启动时指定参数:--daemon

然后cli_wallet提供了判断是否锁定、解锁、锁定接口:

bool is_locked()
void lock()
void unlock(string password)

这样我们可以在通过rcp调用cli_wallet的程序中,先判断是否锁定,锁定了则解锁再操作,当然需要有钱包的密码。

6. 为什么在genesis.json中可以创建帐号带下划线而以后不可以?

cli_wallet是调用以下方法创建帐号:

signed_transaction create_account_with_brain_key(string brain_key, string account_name, string registrar_account, string referrer_account, bool broadcast)
signed_transaction register_account(string name, public_key_type owner, public_key_type active, string registrar_account, string referrer_account, uint32_t referrer_percent, bool broadcast)

"水龙头"也是调用 register_account 创建帐号。

wallet.cpp

   signed_transaction register_account(string name,
                                       public_key_type owner,
                                       public_key_type active,
                                       string  registrar_account,
                                       string  referrer_account,
                                       uint32_t referrer_percent,
                                       bool broadcast = false)
   { try {
      FC_ASSERT( !self.is_locked() );
      FC_ASSERT( is_valid_name(name) );
      account_create_operation account_create_op;
      ...
  }

而create_account_with_brain_key是通过tx.validate()进行检测的。

创世区块初始化是直接应用operation,而不检查帐号有效性:

db_init.cpp

            account_create_operation cop;
            cop.name = asset.symbol + "-collateral-holder-" + std::to_string(collateral_holder_number);
            boost::algorithm::to_lower(cop.name);
            cop.registrar = GRAPHENE_TEMP_ACCOUNT;
            cop.owner = authority(1, collateral_rec.owner, 1);
            cop.active = cop.owner;
            account_id_type owner_account_id = apply_operation(genesis_eval_state, cop).get<object_id_type>();

db_block.cpp

operation_result database::apply_operation(transaction_evaluation_state& eval_state, const operation& op)
{ try {
   int i_which = op.which();
   uint64_t u_which = uint64_t( i_which );
   FC_ASSERT( i_which >= 0, "Negative operation tag in operation ${op}", ("op",op) );
   FC_ASSERT( u_which < _operation_evaluators.size(), "No registered evaluator for operation ${op}", ("op",op) );
   unique_ptr<op_evaluator>& eval = _operation_evaluators[ u_which ];
   FC_ASSERT( eval, "No registered evaluator for operation ${op}", ("op",op) );
   auto op_id = push_applied_operation( op );
   auto result = eval->evaluate( eval_state, op, true );
   set_applied_operation_result( op_id, result );
   return result;
} FC_CAPTURE_AND_RETHROW( (op) ) }

7. 很多操作中的参数"AAAAA"是什么意思

在Bitshares中有许多api参数为 lowerbound,一般用"AAAAA"就可以取到数据,如list_assets,很多新人并不知道这个lowerbound是什么?看一下list_assets的代码:

vector<asset_object> database_api_impl::list_assets(const string& lower_bound_symbol, uint32_t limit)const
{
   FC_ASSERT( limit <= 100 );
   const auto& assets_by_symbol = _db.get_index_type<asset_index>().indices().get<by_symbol>();
   vector<asset_object> result;
   result.reserve(limit);

   auto itr = assets_by_symbol.lower_bound(lower_bound_symbol);

   if( lower_bound_symbol == "" )
      itr = assets_by_symbol.begin();

   while(limit-- && itr != assets_by_symbol.end())
      result.emplace_back(*itr++);

   return result;
}

从list_assets中可以看到,碰到这个lowerbound可以用""空参数来代替,会从第一个数据开始查找。

根据要找的数据字符位置,选择字符开始字母和个数,注意这个是区分大小写的。

8. 帐户为什么有多个密钥对

Bitshares的帐户有OwnerKey、ActiveKey、MemoKey

  • Owner权限 控制帐户
  • Active权限 控制资金
  • Memo权限 读取备注

Owner和Active可以有多个密钥对,而Memo只支持一个密钥对。

Onwer权限和Active权限通过多个密钥对、权重、阀值做多重签名,那Memo权限为什么不用多个密钥对而只有一个呢?因为Memo需要发送方和接收方一起加密数据。

看看Memo怎么做到双方可以读取数据的,诀窍全在memo.cpp中:

void memo_data::set_message(const fc::ecc::private_key& priv, const fc::ecc::public_key& pub,
                            const string& msg, uint64_t custom_nonce)
{
   if( priv != fc::ecc::private_key() && public_key_type(pub) != public_key_type() )
   {
      from = priv.get_public_key();
      to = pub;
      if( custom_nonce == 0 )
      {
         uint64_t entropy = fc::sha224::hash(fc::ecc::private_key::generate())._hash[0];
         entropy <<= 32;
         entropy                                                     &= 0xff00000000000000;
         nonce = (fc::time_point::now().time_since_epoch().count()   &  0x00ffffffffffffff) | entropy;
      } else
         nonce = custom_nonce;
      auto secret = priv.get_shared_secret(pub);
      auto nonce_plus_secret = fc::sha512::hash(fc::to_string(nonce) + secret.str());
      string text = memo_message(digest_type::hash(msg)._hash[0], msg).serialize();
      message = fc::aes_encrypt( nonce_plus_secret, vector<char>(text.begin(), text.end()) );
   }
   else
   {
      auto text = memo_message(0, msg).serialize();
      message = vector<char>(text.begin(), text.end());
   }
}

string memo_data::get_message(const fc::ecc::private_key& priv,
                              const fc::ecc::public_key& pub)const
{
   if( from != public_key_type() )
   {
      auto secret = priv.get_shared_secret(pub);
      auto nonce_plus_secret = fc::sha512::hash(fc::to_string(nonce) + secret.str());
      auto plain_text = fc::aes_decrypt( nonce_plus_secret, message );
      auto result = memo_message::deserialize(string(plain_text.begin(), plain_text.end()));
      FC_ASSERT( result.checksum == uint32_t(digest_type::hash(result.text)._hash[0]) );
      return result.text;
   }
   else
   {
      return memo_message::deserialize(string(message.begin(), message.end())).text;
   }
}

string memo_message::serialize() const
{
   auto serial_checksum = string(sizeof(checksum), ' ');
   (uint32_t&)(*serial_checksum.data()) = checksum;
   return serial_checksum + text;
}

memo_message memo_message::deserialize(const string& serial)
{
   memo_message result;
   FC_ASSERT( serial.size() >= sizeof(result.checksum) );
   result.checksum = ((uint32_t&)(*serial.data()));
   result.text = serial.substr(sizeof(result.checksum));
   return result;
}

实际上Memo的加密是用aes加密,但加密时的密钥是通过“发送方的私钥和接收方的公钥” 获得的,也就是上面代码的 priv.get_shared_secret(pub)。

参考

https://www.peerplays.com/wp-content/uploads/2017/11/AdvisorRole_PeerplaysBlockchain.pdf


感谢您阅读 @chaimyu 的帖子,期待您能留言交流!

H2
H3
H4
Upload from PC
Video gallery
3 columns
2 columns
1 column
1 Comment