bitshares研究系列【operation的实现】

in #bitshares6 years ago (edited)

前面分几篇文章研究了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 的帖子,期待您能留言交流!

Sort:  

Thank you! Welcome!

你好!cn区点赞机器人 @cnbuddy 感谢你对cn区作出成长的贡献。假如我的留言打扰到你,请回复“取消”。

好像很厉害的样子,可惜我是外行!

不同行业都是外行,你是中医吗

还不是中医哦,只是个小学徒,还没参加医师考试。

考过了执照可能就不会再这么闲,天天泡在steemit上了。

加油~也许天天泡steemit决定不考了

也许在steemit上赚够了钱不需要考了?

你觉得这可能吗?
反正我觉得这不可能

哈哈哈,可能性不大,但是还是有可能

有你研究折腾完全有可能

一起研究折腾吧

你这配图。。。

技术贴得配点有意思的图,本来就无趣~

Congratulations @chaimyu! You have completed some achievement on Steemit and have been rewarded with new badge(s) :

Award for the number of upvotes received

Click on any badge to view your own Board of Honor on SteemitBoard.

To support your work, I also upvoted your post!
For more information about SteemitBoard, click here

If you no longer want to receive notifications, reply to this comment with the word STOP

Upvote this notification to help all Steemit users. Learn why here!

不错,这篇分析的很详细。可惜我太晚看到不能 resteem 了。

边学习边记录边分享,也在做这方面的工作,还会经常分享的,谢谢大神关注!

Coin Marketplace

STEEM 0.19
TRX 0.15
JST 0.029
BTC 63878.47
ETH 2625.83
USDT 1.00
SBD 2.79