前面几篇文章,我们全面地介绍了EOS的账户和权限,以及多重签名及其用法。今天呢,我们开始分析合约代码,今天分析的正是eosio.msig
合约。
代码在eos源码目录的contracts/eosio.msig/
文件夹下面。
头文件
我们先看下头文件eosio.msig.hpp
。可以看到有五个方法的声明:
void propose();
void approve( account_name proposer, name proposal_name, permission_level level );
void unapprove( account_name proposer, name proposal_name, permission_level level );
void cancel( account_name proposer, name proposal_name, account_name canceler );
void exec( account_name proposer, name proposal_name, account_name executer );
分别对应于我们上篇文章我们降到的multisig
的五个命令:
cleos multisig propose
cleos multisig approve
cleos multisig unapprove
cleos multisig cancel
cleos multisig exec
再来看下eosio.msig
合约用到的table类型,先看这个proposals
类型:
struct proposal {
name proposal_name;
vector<char> packed_transaction;
auto primary_key()const { return proposal_name.value; }
};
typedef eosio::multi_index<N(proposal),proposal> proposals;
这个记录(也就是table中的一行(row)记录)类型是:proposal
,它包含两个字段:
proposal_name: 提案的名字,这个是
name
类型,也就是说同eos的账户名字类似,长度只能是12位。
packed_transaction: 这个就是提案要执行的交易内容。
另外有点需要注意的是:
primary_key(): 这个方法是必须的,这里返回的是
proposal_name
所对应的uint64_t
整数。
proposal
类型没有加//@abi table
标志,也就是说,这个table是无法被外部访问的,比如用cleos get table
命令无法获取它的数据,只有本eosio.msig
代码可以访问。
另外一个table类型:
struct approvals_info {
name proposal_name;
vector<permission_level> requested_approvals;
vector<permission_level> provided_approvals;
auto primary_key()const { return proposal_name.value; }
};
typedef eosio::multi_index<N(approvals),approvals_info> approvals;
这里也有三个字段:
proposal_name: 提案的名字,与
struct proposal
中一样
requested_approvals: 需要签署的权限列表
provided_approvals: 目前已经签署的权限列表
有两点需要注意:
N(approvals): 这里使用的表的名字是'approvals',不是approvals_info
同样,这里也没有//@abi table
注释,所以也无法从此合约外部访问 。
eosio.msig.cpp
文件
abi声明
看下eosio.msig.cpp
最下方的action声明:
EOSIO_ABI( eosio::multisig, (propose)(approve)(unapprove)(cancel)(exec) )
你看到了,这说明eosio.msig
提供了propose, appove, unapprove, cancel, exec
这5个供外部调用的action接口。
multisig::propose
方法
这个方法没有参数,之所以这样做,注释中有这样的解释:
propose function manually parses input data (instead of taking parsed arguments from dispatcher)
because parsing data in the dispatcher uses too much CPU in case if proposed transaction is big
If we use dispatcher the function signature should be:
void multisig::propose( account_name proposer,
name proposal_name,
vector<permission_level> requested,
transaction trx)
意思是说,这样在方法里面解析数据,而不是让dispatcher
去解析数据,可以提高效率,降低cpu占用率。我们来对比下采用dispatcher去解析参数的代码,下面dispatcher的源码:
template<typename T, typename Q, typename... Args>
bool execute_action( T* obj, void (Q::*func)(Args...) ) {
size_t size = action_data_size();
//using malloc/free here potentially is not exception-safe, although WASM doesn't support exceptions
constexpr size_t max_stack_buffer_size = 512;
void* buffer = nullptr;
if( size > 0 ) {
buffer = max_stack_buffer_size < size ? malloc(size) : alloca(size);
read_action_data( buffer, size );
}
auto args = unpack<std::tuple<std::decay_t<Args>...>>( (char*)buffer, size );
if ( max_stack_buffer_size < size ) {
free(buffer);
}
auto f2 = [&]( auto... a ){
(obj->*func)( a... );
};
boost::mp11::tuple_apply( f2, args );
return true;
}
在看本方法中解析的部分:
constexpr size_t max_stack_buffer_size = 512;
size_t size = action_data_size();
char* buffer = (char*)( max_stack_buffer_size < size ? malloc(size) : alloca(size) );
read_action_data( buffer, size );
account_name proposer;
name proposal_name;
vector<permission_level> requested;
transaction_header trx_header;
datastream<const char*> ds( buffer, size );
ds >> proposer >> proposal_name >> requested;
这段代码是特意为解析propose
action参数数据写的,因为知道都是些什么参数,自然比通用的参数解析要高效一些。
继续往下看multisig::propose
的代码(留意我加的中文注释):
size_t trx_pos = ds.tellp();
ds >> trx_header;
//检查调用者是否有所指定的`proposer`的权限,大部分action都会做类似的检查
require_auth( proposer );
eosio_assert( trx_header.expiration >= eosio::time_point_sec(now()), "transaction expired" );
//eosio_assert( trx_header.actions.size() > 0, "transaction must have at least one action" );
//创建proptable,用于存储提案的相关信息
proposals proptable( _self, proposer );
eosio_assert( proptable.find( proposal_name ) == proptable.end(), "proposal with the same name exists" );
bytes packed_requested = pack(requested);
//检查请求的权限是否可以满足
auto res = ::check_transaction_authorization( buffer+trx_pos, size-trx_pos,
(const char*)0, 0,
packed_requested.data(), packed_requested.size()
);
eosio_assert( res > 0, "transaction authorization failed" );
下面的代码也比较容易看懂:
//存储提案的名字和提案中交易的信息
proptable.emplace( proposer, [&]( auto& prop ) {
prop.proposal_name = proposal_name;
prop.packed_transaction = bytes( buffer+trx_pos, buffer+size );
});
//保存被请求的签署权限信息,也以提案的名字作为主键
approvals apptable( _self, proposer );
apptable.emplace( proposer, [&]( auto& a ) {
a.proposal_name = proposal_name;
a.requested_approvals = std::move(requested);
});
你可能会有疑惑,上篇文章中,不是说提案是由提案发起人和提案的名字一起唯一标志的吗,这里怎么只有提案名字?
主要创建proptable
和apptable
的这两条语句:
proposals proptable( _self, proposer );
approvals apptable( _self, proposer );
注意这两个table
的构造函数的参数了吗?都是_self, proposer
。是什么意思呢?这个构造函数的原型是这样的:
public inline multi_index(uint64_t code, uint64_t scope)
解释下这两个形参:
code: 代表拥有此表的账号,上面代码中,传入的实参是
_self
也就是本合约账号。
scope: 是范围标志,相当于在拥有者账号下,把表划分为不同的范围,这个范围标志可以是任意定的,这里以propser作为范围标志,这样,proposerA
范围下的表与proposerB
范围下的表是两个不同的表,也就是说proposerA
发起了一个提案,名为T,proposerB
发起了一个提案,名字也是T,那么这两个T将会使用不同的表,不会产生冲突。
现在你明白为什么提案发起人和提案名字一起唯一标志一个提案了吧。
今天就是这样了,其他的四个action处理器,我们下次再分析。
简介:不羁,一名程序员;专研EOS技术,玩转EOS智能合约开发。
微信公众号:know_it_well
知识星球地址:https://t.zsxq.com/QvbuzFM
网友评论