引子
从上一节中,我们知道在EOS系统中帐户必需遵循以两个准则
- 必须短于13个字符
- 仅能包含以下字符:.12345abcdefghijklmnopqrstuvwxyz
EOS为什么要这样做呢,这样做有什么好处,在EOS源码中又是如何实现的,下面我们从EOS源码中一步步分析,并解答这些疑问。
帐户别名
在EOS源码中,帐户的类型是account_name,但account_name在C++代码中又是什么类型呢,追踪代码发现,EOS好多类型都用了别名机制,在源码文件types.hpp中的133行,其代码所示
using action_name = name; //合约操作名称
using scope_name = name; //作用范围名称
using account_name = name; //帐户名称
using permission_name = name; //权限名称
using table_name = name; //数据库表名称
这些名称都指向同一个name,也就是说帐户等其它名称限制都由name类来完成的,下面我们看一下name类的实现
struct name {
uint64_t value = 0;
bool empty()const { return 0 == value; }
bool good()const { return !empty(); }
name( const char* str ) { set(str); }
name( const string& str ) { set( str.c_str() ); }
void set( const char* str );
template<typename T>
name( T v ):value(v){}
name(){}
explicit operator string()const;
string to_string() const { return string(*this); }
name& operator=( uint64_t v ) {
value = v;
return *this;
}
name& operator=( const string& n ) {
value = name(n).value;
return *this;
}
name& operator=( const char* n ) {
value = name(n).value;
return *this;
}
friend std::ostream& operator << ( std::ostream& out, const name& n ) {
return out << string(n);
}
friend bool operator < ( const name& a, const name& b ) { return a.value < b.value; }
friend bool operator <= ( const name& a, const name& b ) { return a.value <= b.value; }
friend bool operator > ( const name& a, const name& b ) { return a.value > b.value; }
friend bool operator >=( const name& a, const name& b ) { return a.value >= b.value; }
friend bool operator == ( const name& a, const name& b ) { return a.value == b.value; }
friend bool operator == ( const name& a, uint64_t b ) { return a.value == b; }
friend bool operator != ( const name& a, uint64_t b ) { return a.value != b; }
friend bool operator != ( const name& a, const name& b ) { return a.value != b.value; }
operator bool()const { return value; }
operator uint64_t()const { return value; }
operator unsigned __int128()const { return value; }
};
从name类中只有一个属性value且类型为uint64_t,即无符号64位整型,这是如何存储13位字符的呢?它们之间是如何转换的?等一系列疑问又产生了。
在name类重载了所有比较运算符用于名称之间的比较,还写了赋值构造和拷贝构造函数用于name类型的构造与赋值。比较重要的函数是set函数和to_string函数。这两个函数应该是实现了字符到整型及整型到字符串的转化。
设置帐户名称set
void name::set( const char* str ) {
const auto len = strnlen(str, 14);
EOS_ASSERT(len <= 13, name_type_exception, "Name is longer than 13 characters (${name}) ", ("name", string(str)));
value = string_to_name(str);
EOS_ASSERT(to_string() == string(str), name_type_exception,
"Name not properly normalized (name: ${name}, normalized: ${normalized}) ",
("name", string(str))("normalized", to_string()));
}
从代码中可以看帐户的名称长度必须最大长度为13位,大于13位会导致代码断言错误并向系统报告名称超过13个字符的异常。然后调用了函数string_to_name将字符转化为整型,其函数string_to_name如下所示
static constexpr uint64_t string_to_name( const char* str )
{
uint64_t name = 0;
int i = 0;
for ( ; str[i] && i < 12; ++i) {
// NOTE: char_to_symbol() returns char type, and without this explicit
// expansion to uint64 type, the compilation fails at the point of usage
// of string_to_name(), where the usage requires constant (compile time) expression.
name |= (char_to_symbol(str[i]) & 0x1f) << (64 - 5 * (i + 1));
}
// The for-loop encoded up to 60 high bits into uint64 'name' variable,
// if (strlen(str) > 12) then encode str[12] into the low (remaining)
// 4 bits of 'name'
if (i == 12)
name |= char_to_symbol(str[12]) & 0x0F;
return name;
}
static constexpr uint64_t char_to_symbol( char c ) {
if( c >= 'a' && c <= 'z' )
return (c - 'a') + 6;
if( c >= '1' && c <= '5' )
return (c - '1') + 1;
return 0;
}
从代码中可以看出char_to_symbol是将一个字符转换为一个64位的无符号整型,在此判断判断了字符必须为a-z和1-5的字符。并将1-5的字符与数值1-5所对应,而字符a-z字符与数值6-31,对于字符.则对应数值为0.从而将所有帐户合法字符都转化为一个数值。 在string_to_name函数中,采用循环遍历帐户中的所有字符,并调用char_to_symbol函数将帐户中的字符转化所对应的数值,然后只取低5位,左移64- 5 * (i + 1)位,并与name做|操作。最后将所有字符所对应的数值都存储到name类型中。从中可以计算一下帐户总共有32个字符,所对应的数值为0到31,在计算机中采用二进制表示数据,0到31只需要5位就可以存储。一个64位的无符号整型最多可以存储64/5=12,余4位,也就是说最多13个字符且不能以字符z结尾,否则可能会出现问题。当不够13字符时则以字符.补充。
将name转化可见帐户
name::operator string()const {
static const char* charmap = ".12345abcdefghijklmnopqrstuvwxyz";
string str(13,'.');
uint64_t tmp = value;
for( uint32_t i = 0; i <= 12; ++i ) {
char c = charmap[tmp & (i == 0 ? 0x0f : 0x1f)];
str[12-i] = c;
tmp >>= (i == 0 ? 4 : 5);
}
boost::algorithm::trim_right_if( str, []( char c ){ return c == '.'; } );
return str;
}
将name变量转化为字符串,首先将合法字符按其所对应的数值大小排列并赋值给charmap变量,并作为查找表,然后在name变量的属性value中依次取5位(第一次取4位),并按其值在查找表中找出对应的字符,从而还原出对应可见帐户名称,在帐户规则中小于13位字符时高位用字符.来补充,所以帐户最高位不能是字符.,将补充的字符.移除,从而完成帐户还原。
总结
综上所述,这就是EOS帐户名称有所限制的原因,它需要将13个字符装入一个64位的无符号整型,且能将整型转化为所对应的字符帐户的过程。
优点:
1、减少内存使用,帐户13个字符存储需要14个字节,而采用name变量存储只需要8个字节。
2、程序运行速度快,在做帐户查找与比较时,整型比较的效率会大大高于字符串比较。
缺点:
1、减少了帐户名称取名的最大范围,比较不能有特殊字符和67890等字符。
2、对帐户名称的长度有所限制。
链接
星河公链
了解最新更多区块链相关文章
敬请关注星链微信公众号,星链与你共成长