bitshares研究系列【operation的实现】

Words
735
Reading
4 min
9y
Listen to this post
~5 min

前面分几篇文章研究了bitshares api的实现,api主要用来获取数据,还有重要的一个内容就是operation,operation顾名思义就是用来操作了,operation一般都会导致链上的数据发生变化。

operation数据格式

先看一个bitshares节点打印出的operation数据:

    {"op":[0,{"fee":{"amount":10940,"asset_id":"1.3.0"},"from":"1.2.833110","to":"1.2.169701","amount":{"amount":"1000000000000","asset_id":"1.3.0"},"memo":{"from":"BTS6CyeZHdLCGqM8nMtksgNNPsM6po6pVnd4Z6mQW7zQU4wrECRdi","to":"BTS6CyeZHdLCGqM8nMtksgNNPsM6po6pVnd4Z6mQW7zQU4wrECRdi","nonce":"389710969817853","message":"78f07e88fb308a977e3158d4e36747a3"},"extensions":[]}]}

在这个数据中op_id为0,0是什么operation呢?bitshares-core定义在 /libraries/chain/include/graphene/chain/protocol/operations.hpp中:

   typedef fc::static_variant<
            transfer_operation,
            limit_order_create_operation,
            limit_order_cancel_operation,
            call_order_update_operation,

第一个值transfer_operation就是0,bitshares-ui的定义更容易看懂,在/bitsharesjs/es/chain/src/ChainType.js中,全部列出方便参照:

ChainTypes.operations = {
    transfer: 0,
    limit_order_create: 1,
    limit_order_cancel: 2,
    call_order_update: 3,
    fill_order: 4,
    account_create: 5,
    account_update: 6,
    account_whitelist: 7,
    account_upgrade: 8,
    account_transfer: 9,
    asset_create: 10,
    asset_update: 11,
    asset_update_bitasset: 12,
    asset_update_feed_producers: 13,
    asset_issue: 14,
    asset_reserve: 15,
    asset_fund_fee_pool: 16,
    asset_settle: 17,
    asset_global_settle: 18,
    asset_publish_feed: 19,
    witness_create: 20,
    witness_update: 21,
    proposal_create: 22,
    proposal_update: 23,
    proposal_delete: 24,
    withdraw_permission_create: 25,
    withdraw_permission_update: 26,
    withdraw_permission_claim: 27,
    withdraw_permission_delete: 28,
    committee_member_create: 29,
    committee_member_update: 30,
    committee_member_update_global_parameters: 31,
    vesting_balance_create: 32,
    vesting_balance_withdraw: 33,
    worker_create: 34,
    custom: 35,
    assert: 36,
    balance_claim: 37,
    override_transfer: 38,
    transfer_to_blind: 39,
    blind_transfer: 40,
    transfer_from_blind: 41,
    asset_settle_cancel: 42,
    asset_claim_fees: 43
};

再列出几个其它的operation数据,如果你运行bitshares节点的话,在控制台上可以看到很多这种数据:

    {"op":[43,{"fee":{"amount":578918,"asset_id":"1.3.0"},"issuer":"1.2.442525","amount_to_claim":{"amount":"121900000000000001","asset_id":"1.3.2241"},"extensions":[]}]}

    {"op":[1,{"fee":{"amount":578,"asset_id":"1.3.0"},"seller":"1.2.805079","amount_to_sell":{"amount":1300000000,"asset_id":"1.3.113"},"min_to_receive":{"amount":"16455696202","asset_id":"1.3.0"},"expiration":"2023-03-27T06:31:10","fill_or_kill":false,"extensions":[]}]}

    {"op":[33,{"fee":{"amount":1157836,"asset_id":"1.3.0"},"vesting_balance":"1.13.3701","owner":"1.2.501749","amount":{"amount":"114324157461","asset_id":"1.3.1564"}}]}

    {"op":[32,{"fee":{"amount":578918,"asset_id":"1.3.0"},"creator":"1.2.828047","owner":"1.2.828047","amount":{"amount":"78898829000000001","asset_id":"1.3.1564"},"policy":[1,{"start_claim":"2018-03-24T12:36:09","vesting_seconds":31536000}]}]}

    {"op":[8,{"fee":{"amount":69470219,"asset_id":"1.3.0"},"account_to_upgrade":"1.2.825548","upgrade_to_lifetime_member":true,"extensions":[]}]}    

输出中还看到"proposal"的消息,看来还有一些不是"op"的消息,以后有时间再去分析!

    {"proposal":{"id":"1.10.8901","expiration_time":"2018-03-23T23:24:38","proposed_transaction":{"ref_block_num":0,"ref_block_prefix":0,"expiration":"2018-03-23T23:24:38","operations":[[8,{"fee":{"amount":69470219,"asset_id":"1.3.0"},"account_to_upgrade":"1.2.825548","upgrade_to_lifetime_member":true,"extensions":[]}]],"extensions":[]},"required_active_approvals":["1.2.825548"],"available_active_approvals":["1.2.825548"],"required_owner_approvals":[],"available_owner_approvals":[],"available_key_approvals":[],"proposer":"1.2.825548"}}

operation处理流程

/libraries/app/api.cpp

    void network_broadcast_api::broadcast_transaction(const signed_transaction& trx)
    {
       trx.validate();
       _app.chain_database()->push_transaction(trx);
       if( _app.p2p_node() != nullptr )
          _app.p2p_node()->broadcast_transaction(trx);
    }

将交易push到chain_database,如果已连接到其它节点,则广播到其它节点。

    void network_broadcast_api::broadcast_transaction_with_callback(confirmation_callback cb, const signed_transaction& trx)
    {
       trx.validate();
       _callbacks[trx.id()] = cb;
       _app.chain_database()->push_transaction(trx);
       if( _app.p2p_node() != nullptr )
          _app.p2p_node()->broadcast_transaction(trx);
    }

_app.chain_database()返回的是chain:database对象,定义在以下位置:

/libraries/chain/include/graphene/chain/database.hpp

其中push_transaction声明如下:

         processed_transaction push_transaction( const signed_transaction& trx, uint32_t skip = skip_nothing );

函数实现在db_block.cpp中,内部实际调用_push_transaction()函数。

processed_transaction database::_push_transaction( const signed_transaction& trx )
{
   // If this is the first transaction pushed after applying a block, start a new undo session.
   // This allows us to quickly rewind to the clean state of the head block, in case a new block arrives.
   if( !_pending_tx_session.valid() )
      _pending_tx_session = _undo_db.start_undo_session();

   // Create a temporary undo session as a child of _pending_tx_session.
   // The temporary session will be discarded by the destructor if
   // _apply_transaction fails.  If we make it to merge(), we
   // apply the changes.

   auto temp_session = _undo_db.start_undo_session();
   auto processed_trx = _apply_transaction( trx );
   _pending_tx.push_back(processed_trx);

   // notify_changed_objects();
   // The transaction applied successfully. Merge its changes into the pending block session.
   temp_session.merge();

   // notify anyone listening to pending transactions
   notify_on_pending_transaction( trx );
   return processed_trx;
}

这个函数代码不多,主要做了undo、apply、notify,而undo session这块做了交易出错时的恢复工作。

processed_transaction database::_apply_transaction(const signed_transaction& trx);

_apply_transaction()就比较复杂了,如校验交易签名、过期时间判断、执行操作等,代码太多不列出来了,函数最后有段代码如下:

   //Finally process the operations
   processed_transaction ptrx(trx);
   _current_op_in_trx = 0;
   for( const auto& op : ptrx.operations )
   {
      eval_state.operation_results.emplace_back(apply_operation(eval_state, op));
      ++_current_op_in_trx;
   }

看起来交易中能支持多个operations。

在循环中又调用了apply_operation函数,如下:

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) ) }

以上代码中有个_operation_evaluators数组,注册了对应operation id的evaluator方法,所以每个operation有不同的evaluator处理,在db_init.cpp中注册的,如下:

void database::initialize_evaluators()
{
   _operation_evaluators.resize(255);
   register_evaluator<account_create_evaluator>();
   register_evaluator<account_update_evaluator>();
   ...
   register_evaluator<transfer_evaluator>();
   ...
}

如交易就是transfer_evaluator

evaluator一些基类实现在evaluator.hpp、evaluator.cpp中,里面定义了evaluator、generic_evaluator等类。

/libraries/chain/transfer_evaluator.cpp .hpp

   class transfer_evaluator : public evaluator<transfer_evaluator>
   {
      public:
         typedef transfer_operation operation_type;

         void_result do_evaluate( const transfer_operation& o );
         void_result do_apply( const transfer_operation& o );
   };

看一下transfer_evaluator的定义,这个也用到了CRTP,整个bitshares里到处使用了这种方式来提高执行效率。

eval->evaluate调用流程:op_evaluator::evaluate => generic_evaluator::start_evaluate => evaluator::evaluate => transfer_evaluator::do_evaluate

   operation_result generic_evaluator::start_evaluate( transaction_evaluation_state& eval_state, const operation& op, bool apply )
   { try {
      trx_state   = &eval_state;
      //check_required_authorities(op);
      auto result = evaluate( op );

      if( apply ) result = this->apply( op );
      return result;
   } FC_CAPTURE_AND_RETHROW() }

在start_evaluate中看到先执行了evaluate,再执行了apply。

继续看下transfer的apply,如下:

void_result transfer_evaluator::do_apply( const transfer_operation& o )
{ try {
   db().adjust_balance( o.from, -o.amount );
   db().adjust_balance( o.to, o.amount );
   return void_result();
} FC_CAPTURE_AND_RETHROW( (o) ) }

do_apply()中修改了帐号货币数量,这样基本就明白了,evaluate主要是校验,而apply是真正的执行。

operation的实现就简单分析这些,其它operation也是和transfer同样的处理逻辑。

补充

braodcast_with_callback

void network_broadcast_api::broadcast_transaction_with_callback(confirmation_callback cb, const signed_transaction& trx);

broadcast_transaction_with_callback()处理逻辑和broadcast_transaction()是一样,我们看下回调函数定义:

         struct transaction_confirmation
         {
            transaction_id_type   id;
            uint32_t              block_num;
            uint32_t              trx_num;
            processed_transaction trx;
         };

         typedef std::function<void(variant/*transaction_confirmation*/)> confirmation_callback;

回调函数中有交易ID、块号、交易号。

    network_broadcast_api::network_broadcast_api(application& a):_app(a)
    {
       _applied_block_connection = _app.chain_database()->applied_block.connect([this](const signed_block& b){ on_applied_block(b); });
    }

    void network_broadcast_api::on_applied_block( const signed_block& b )
    {
       if( _callbacks.size() )
       {
          /// we need to ensure the database_api is not deleted for the life of the async operation
          auto capture_this = shared_from_this();
          for( uint32_t trx_num = 0; trx_num < b.transactions.size(); ++trx_num )
          {
             const auto& trx = b.transactions[trx_num];
             auto id = trx.id();
             auto itr = _callbacks.find(id);
             if( itr != _callbacks.end() )
             {
                auto block_num = b.block_num();
                auto& callback = _callbacks.find(id)->second;
                fc::async( [capture_this,this,id,block_num,trx_num,trx,callback]() {
                   callback( fc::variant( transaction_confirmation{ id, block_num, trx_num, trx },
                                          GRAPHENE_MAX_NESTED_OBJECTS ) );
                } );
             }
          }
       }
    }

以上代码做了块信号的连接,每个块过来都会查找块中交易,而_callbacks记录了交易id,这样有对应的交易就异步调用callback。

braodcast_with_callback也可以参考network_broadcast_api_tests.cpp的例子。


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

bitshares研究系列【operation的实现】 | Ecency