EOS區塊鏈帳戶名稱始未

引子

從上一節中,我們知道在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、對帳戶名稱的長度有所限制。
EOS區塊鏈帳戶名稱始未

鏈接

星河公鏈
瞭解最新更多區塊鏈相關文章
敬請關注星鏈微信公衆號,星鏈與你共成長
EOS區塊鏈帳戶名稱始未

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章