什么是EOSIO的账号
账号是访问EOSIO的基础,相当于一般系统中的用户名账号,它的账号是按照如下规则建立的
static const char* charmap = ".12345abcdefghijklmnopqrstuvwxyz";
EOSIO的账号是按照严格按照上面的字母进行自由组合的一个字符串,最大长度为13,两头不能是’.'字符
在EOSIO中有几个缺省的账号, 是系统使用的
const static name system_account_name {
"eosio"_n };
const static name null_account_name {
"eosio.null"_n };
const static name producers_account_name {
"eosio.prods"_n };
注意:
- 其中eosio是系统超级账号,相当于管理员,它是在创始节点随节点启动创建的,创建使用的公私钥,也是创始节点的公私钥
- 以eosio.为开头创建的账户,其创建者必须是eosio,这是系统保留的私有规则
- 具体的实现逻辑可以看name类
ESOIO的账号作用
了解了什么是EOSIO的账号,那它在整个系统时有何作用,纵观整个系统的各个功能,账号总结起来有如下作用
- 交易的权限鉴定
- 内链Action的权限鉴定
- 智能合约,ABI的载体
- 生产者名称
账户权限的数据结构
// 最基础的账户表,记录账户名,创建时间,还有ABI数据块
class account_object : public chainbase::object<account_object_type, account_object> {
OBJECT_CTOR(account_object,(abi))
id_type id;
account_name name; //< name should not be changed within a chainbase modifier lambda
block_timestamp_type creation_date;
shared_blob abi;
};
// 账户元数据表,记录关于账号易变信息,或者一些统计信息
class account_metadata_object : public chainbase::object<account_metadata_object_type, account_metadata_object>
{
OBJECT_CTOR(account_metadata_object);
enum class flags_fields : uint32_t {
privileged = 1
};
id_type id;
account_name name; //< name should not be changed within a chainbase modifier lambda
uint64_t recv_sequence = 0;
uint64_t auth_sequence = 0;
uint64_t code_sequence = 0;
uint64_t abi_sequence = 0;
digest_type code_hash;
time_point last_code_update;
uint32_t flags = 0;
uint8_t vm_type = 0;
uint8_t vm_version = 0;
};
// 合约数据表,保存用户的部署的合约
class code_object : public chainbase::object<code_object_type, code_object> {
OBJECT_CTOR(code_object, (code))
id_type id;
digest_type code_hash; //< code_hash should not be changed within a chainbase modifier lambda
shared_blob code;
uint64_t code_ref_count;
uint32_t first_block_used;
uint8_t vm_type = 0; //< vm_type should not be changed within a chainbase modifier lambda
uint8_t vm_version = 0; //< vm_version should not be changed within a chainbase modifier lambda
};
// 权限基础表
class permission_object : public chainbase::object<permission_object_type, permission_object> {
OBJECT_CTOR(permission_object, (auth) )
id_type id;
permission_usage_object::id_type usage_id;
id_type parent; ///< parent permission
account_name owner; ///< the account this permission belongs to (should not be changed within a chainbase modifier lambda)
permission_name name; ///< human-readable name for the permission (should not be changed within a chainbase modifier lambda)
time_point last_updated; ///< the last time this authority was updated
shared_authority auth; ///< authority required to execute this permission
};
// 权限链接表,记录和action进行绑定关系
class permission_link_object : public chainbase::object<permission_link_object_type, permission_link_object> {
OBJECT_CTOR(permission_link_object)
id_type id;
/// The account which is defining its permission requirements
account_name account;
/// The contract which account requires @ref required_permission to invoke
account_name code; /// TODO: rename to scope
/// The message type which account requires @ref required_permission to invoke
/// May be empty; if so, it sets a default @ref required_permission for all messages to @ref code
action_name message_type;
/// The permission level which @ref account requires for the specified message types
/// all of the above fields should not be changed within a chainbase modifier lambda
permission_name required_permission;
};
/****************************************************************************
在EOSIO中,账户的权限是作为整个数据块进行存储的
在authority结构中,主要记录了一下核心数据
threshold:阈值,所有权限之权重值之和, 最后的比较值
keys:记录公钥与权重数值
accounts:记录管理账户与权重值,新创建的账户一般没有这个数值
waits:延迟执行相关权限
****************************************************************************/
struct permission_level {
account_name actor;
permission_name permission;
};
struct permission_level_weight {
permission_level permission;
weight_type weight;
};
struct key_weight {
public_key_type key;
weight_type weight;
};
struct authority {
authority( public_key_type k, uint32_t delay_sec = 0 )
:threshold(1),keys({
{
k,1}})
{
if( delay_sec > 0 ) {
threshold = 2;
waits.push_back(wait_weight{
delay_sec, 1});
}
}
authority( permission_level p, uint32_t delay_sec = 0 )
:threshold(1),accounts({
{
p,1}})
{
if( delay_sec > 0 ) {
threshold = 2;
waits.push_back(wait_weight{
delay_sec, 1});
}
}
authority( uint32_t t, vector<key_weight> k, vector<permission_level_weight> p = {
}, vector<wait_weight> w = {
} )
:threshold(t),keys(move(k)),accounts(move(p)),waits(move(w)){
}
authority(){
}
uint32_t threshold = 0;
vector<key_weight> keys;
vector<permission_level_weight> accounts;
vector<wait_weight> waits;
};
从上面的代码结构,可以直接的出以下结论
- 账户表值记录了最核心的账户名
- 账户和权限做了深度绑定,整个权限数据作为整体数据块存储
- 同一个权限可以对应多个key_weight, permission_level_weight,wait_weight,可以是权限更加灵活,扩展性更好
- 权限直接作用于单一账号,控制的力度更细
- 权限数据块整个存在一起,存取快速,但是同时也带来了操作麻烦,查询也不方便
- code,abi分开存储,因为abi的查询频次要远远大于code,这个后面详细说
账户权限的操作
EOSIO中,账户权限的操作都是在系统合约eosio_contract来完成的,eosio_contract合约内置部署于eosio账户上,它是eosio中最基础,也是最重要的合约,直接编译部署在节点中,不能更新。
eosio_contract的action列表如下
-
newaccount: 创建账户,创建账户时默认建立owner,active两个权限
动作附带参数如下:struct newaccount { account_name creator; account_name name; authority owner; authority active; };
-
updateauth:权限更新
struct updateauth { account_name account; permission_name permission; permission_name parent; authority auth; };
-
deleteauth:删除账户权限
struct deleteauth { account_name account; permission_name permission; };
-
linkauth:权限与合约动作的绑定
struct linkauth { account_name account; account_name code; action_name type; permission_name requirement; };
-
unlinkauth:接触合约与动作的绑定关系
struct unlinkauth { account_name account; account_name code; action_name type; };
-
setcode:部署合约
struct setcode { account_name account; uint8_t vmtype = 0; uint8_t vmversion = 0; bytes code; };
-
setabi:部署ABI
struct setabi { account_name account; bytes abi; };
-
canceldelay:取消延迟
struct canceldelay { permission_level canceling_auth; transaction_id_type trx_id; };
关于cleos中权限账户的命令行
前面我们说过eosio提供给外部访问的接口都是http接口,cleos只是对这些接口的封装,一般在我们的项目中,直接使用的是http接口,但是在系统部署的初期阶段,我们还是免不了会使用cleos命令行,当然你自己开发一个命令行另说,在笔者的项目中,我们曾开发了java版本的命令行工具,还有钱包,这样做主要是为了后台及在android使用,非常的方便。
具体的命令使用规则大家可参考这里,EOSIO命令行大全
总结
相对来说,在EOSIO中,关于权限的代码比较统一集中,功能相对独立,可以封装城独立的模块,也可以建立一个独立的服务,只负责CA相关的业务逻辑。
但是,同时我们又观察到在公链中,权限的设计和一般的系统中权限又大大的不同,那么我们接着会问
- 这样设计的权限又有什么好处?方便操作与访问么?如果不好,可以从哪些方面做出优化?
- 在权限的数据结构中,account,key是个什么关系,是一对一的关系么?
- 在交易中,权限是怎么鉴定的,进而引申在合约中又是怎么鉴定的?
- 我们观察到,权限相关的表的定义,为什么继承chainbase::object,他到底是咋样存储的?
后记
账户权限这一块,是EOSIO中相对独立,但是又特别绕口难以理解的功能模块,特别是它引入的一些概念,如阈值,权重,多签,link绑定,code权限必须要牢记,才可以理解它进行权限验证的过程和原理。
所以理解了账户权限的相关数据结构,我们才能更好的去理解它的鉴权过程。