简介: 昨天我们分析了eosio.msig
合约的头文件,以及propose
方法,今天继续分析其他方法。
代码在eos源码目录的contracts/eosio.msig/
文件夹下面。
multisig::approve
方法:
对比一下之前文章中,我们用carl
和bob
举例子所使用的命令:
cleos multisig approve bob payuser '{"actor":"bob", "permission":"active"}' -p bob@active
源码:
void multisig::approve( account_name proposer, name proposal_name, permission_level level ) {
require_auth( level );
approvals apptable( _self, proposer );
auto& apps = apptable.get( proposal_name, "proposal not found" );
auto itr = std::find( apps.requested_approvals.begin(), apps.requested_approvals.end(), level );
eosio_assert( itr != apps.requested_approvals.end(), "approval is not on the list of requested approvals" );
apptable.modify( apps, proposer, [&]( auto& a ) {
a.provided_approvals.push_back( level );
a.requested_approvals.erase( itr );
});
}
approve
方法,因为参数比较简单,没有像上一篇文章中分析的propose
方法那样,在方法内部自己解析参数。
在上面命令的情况下,可以看出,这里传入的proposer
就是bob, proposal_name
是payuser
,level
就是{"actor":"bob", "permission":"active"}
。
方法内部,第一条语句:
require_auth( level );
意思是,检查当前触发此action的账户是否有level
所指定的权限,如果没有,则中止并回滚当前事务。
approvals apptable( _self, proposer );
auto& apps = apptable.get( proposal_name, "proposal not found" );
这里创建了一个table,然后利用get方法,从table中取出提案。你可能会疑惑:“为什么刚创建的table就能有数据?”
其实table相当于给了我们一组操作持久化数据的接口,底层是数据库支持的。我们这里的table不是数据的集合本身,可以被认为是一组操作数据库的接口;这里说的创建table,其实就是初始化这样一组接口。_self, proposer
作为初始化参数,告诉这组接口,我们后续的操作是针对于哪个存储表的。
我们之前没有对表的操作接口做详细讲解。这里我们看到了get
接口,它有两个参数,第一个参数是主键,第二个参数是找对到对应的记录时,打印出来的错误信息。如果找不到,当前执行的事务中止并回滚。如果成功,则返回这条记录。还记得approvals表记录的类型吗?
struct approvals_info {
name proposal_name;
vector<permission_level> requested_approvals;
vector<permission_level> provided_approvals;
auto primary_key()const { return proposal_name.value; }
};
继续看下面的代码:
auto itr = std::find( apps.requested_approvals.begin(), apps.requested_approvals.end(), level );
eosio_assert( itr != apps.requested_approvals.end(), "approval is not on the list of requested approvals" );
apptable.modify( apps, proposer, [&]( auto& a ) {
a.provided_approvals.push_back( level );
a.requested_approvals.erase( itr );
});
从本提案的请求的签署权限列表
中,查找与当前签署的level
权限一致的权限,并把它从请求签署权限列表
(requested_approvals
)中删除,然后加入到已签署权限列表
(provided_approvals
)中。
这里面用到了C++标准模板库(STL)中的vector
数据结构和std::find
方法,这里就不多介绍。本系列只介绍EOS相关的知识。如果对C++方面还不熟,请随时查阅C++的相关书籍或者网上搜索相关资料学习。
multisig::unapprove方法
把下面的源码,对比一下这个命令:
cleos multisig unapprove bob payuser '{"actor":"carl", "permission":"active"}' -p bob@active
源码:
void multisig::unapprove( account_name proposer, name proposal_name, permission_level level ) {
require_auth( level );
approvals apptable( _self, proposer );
auto& apps = apptable.get( proposal_name, "proposal not found" );
auto itr = std::find( apps.provided_approvals.begin(), apps.provided_approvals.end(), level );
eosio_assert( itr != apps.provided_approvals.end(), "no approval previously granted" );
apptable.modify( apps, proposer, [&]( auto& a ) {
a.requested_approvals.push_back(level);
a.provided_approvals.erase(itr);
});
}
对比一下approve
源码,你是不是发现非常相似,不同的地方在于,这个unapprove
是把level
权限从已签署列表
(provided_approvals
)中删除,然后添加到请求签署列表
(requested_approvals
)中。
multisig::cancel
这个方法也比较简单,分别从proposals
和approvals
table中删除相关的提案信息即可。具体参看源码:
void multisig::cancel( account_name proposer, name proposal_name, account_name canceler ) {
require_auth( canceler );
proposals proptable( _self, proposer );
auto& prop = proptable.get( proposal_name, "proposal not found" );
if( canceler != proposer ) {
eosio_assert( unpack<transaction_header>( prop.packed_transaction ).expiration < eosio::time_point_sec(now()), "cannot cancel until expiration" );
}
approvals apptable( _self, proposer );
auto& apps = apptable.get( proposal_name, "proposal not found" );
proptable.erase(prop);
apptable.erase(apps);
}
需要注意的地方只有一点:
中间有一条语句判断,如果取消的人与
proposer
不是同一个人,则中止此事务,也就是不做取消操作
multisig::exec
看下exec的命令形式:
cleos multisig exec bob payuser -p user@active
源码:
void multisig::exec( account_name proposer, name proposal_name, account_name executer ) {
require_auth( executer );
proposals proptable( _self, proposer );
auto& prop = proptable.get( proposal_name, "proposal not found" );
approvals apptable( _self, proposer );
auto& apps = apptable.get( proposal_name, "proposal not found" );
transaction_header trx_header;
datastream<const char*> ds( prop.packed_transaction.data(), prop.packed_transaction.size() );
ds >> trx_header;
eosio_assert( trx_header.expiration >= eosio::time_point_sec(now()), "transaction expired" );
bytes packed_provided_approvals = pack(apps.provided_approvals);
auto res = ::check_transaction_authorization( prop.packed_transaction.data(), prop.packed_transaction.size(),
(const char*)0, 0,
packed_provided_approvals.data(), packed_provided_approvals.size()
);
eosio_assert( res > 0, "transaction authorization failed" );
send_deferred( (uint128_t(proposer) << 64) | proposal_name, executer, prop.packed_transaction.data(), prop.packed_transaction.size() );
proptable.erase(prop);
apptable.erase(apps);
}
有如下几点需要注意一下:
- 只检查了当前的action的权限中否有当前的
executer
,但是没有限制executer
的身份。也就是说,任何一个人,只要用了自己的签名就可以发起exec
的action。- 对已签署许可是否满足所需许可的检查,是在
::check_transaction_authorization
里面的检查的,这个方法已经不止一次用到。- 提案的交易事务,是以
deferred action
来执行的,我们以后把它翻译成延迟的action
或者异步的action
吧。关于inline action
和deferred action
的区别,我们还没有介绍,以后找个专题来介绍吧。
今天就到这里吧,以后想详细解释下require_auth
,以及inline action 和 deferred action
。
简介:不羁,一名程序员;专研EOS技术,玩转EOS智能合约开发。
微信公众号:know_it_well
知识星球地址:https://t.zsxq.com/QvbuzFM
网友评论