bitshares研究系列【operation的实现】
前面分几篇文章研究了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 的帖子,期待您能留言交流!
Nice post
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) :
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不错,这篇分析的很详细。可惜我太晚看到不能 resteem 了。
边学习边记录边分享,也在做这方面的工作,还会经常分享的,谢谢大神关注!