Graphene 源码阅读 ~ 数据库篇 ~ 对象模型

我之前向 bitshares-core 提交的第一个 PR 就是关于数据库的, 所以最先了解的就是数据库这块, 所以在写上一篇 源码结构 时我就计划先从数据库开始了. 本来想数据库用一篇文章介绍, 但今晚梳理了一下发现一篇文章不可能写得完.. 所以就有了标题的 “数据库篇 ~ 对象模型”.

类似的, 其它几个模块可见也不是一篇文章能说清的, 所以后面计划还会有共识篇, 网络篇, 钱包篇, 插件篇等几个篇章, 每一篇章可能会包含几篇文章不等, 而且由于看代码时我也不是抓住一个方向一气儿磕到底, 所以这些个篇章的文章可能会是交错出现的.

Ok, 下面就隆重开始数据库篇的第一章节 - 对象模型.

对象模型

Graphene 对象模型基于经典的 C++ 面向对象设计, 同时采用了 C++ 特有的被称作 奇异递归模板模式 的设计模式. 这种设计模式要依赖于 C++ 的模板机制, 特点就是派生类继承于一个模板基类, 同时用它自己作为模板的参数, 借助这种模式能够实现更为强大的多态, 这种模式的写法如下:

// The Curiously Recurring Template Pattern (CRTP)
template<class T>
class Base
{
    // methods within Base can use template to access members of Derived
};
class Derived : public Base<Derived>
{
    // ...
};

Graphene 中的所有对象的基类定义于 <db>/object.hpp 中, 这里可谓是 Graphene 所有对象的本源, “本源” 分成了两个类来定义: object 和 abstract_object, 它们都位于 db 命名空间下. object 类中定义了所有对象都共有的最基本的属性和方法, 其中这些方法全都是纯虚方法, 因为我们被要求不能直接示例 object 对象. abstract_object 继承了 object 方法, 同时提供了对 object 中定义的虚拟方法的实现, 奇异递归模板模式也在 abstract_object 上体现出来, 它的定义如下:

   template<typename DerivedClass>
   class abstract_object : public object
   {
      public:
         virtual unique_ptr<object> clone()const
         {
            return unique_ptr<object>(new DerivedClass( *static_cast<const DerivedClass*>(this) ));
         }

         virtual void    move_from( object& obj )
         {
            static_cast<DerivedClass&>(*this) = std::move( static_cast<DerivedClass&>(obj) );
         }
         virtual variant to_variant()const { return variant( static_cast<const DerivedClass&>(*this) ); }
         virtual vector<char> pack()const  { return fc::raw::pack( static_cast<const DerivedClass&>(*this) ); }
         virtual fc::uint128  hash()const  {
             auto tmp = this->pack();
             return fc::city_hash_crc_128( tmp.data(), tmp.size() );
         }
   };

可以看到 abstract_object 的定义方式和奇异递归模板的写法一致, 而其实现 clone(), move_from(), to_variant(), pack() 以及 hash() 正是 object 定义的 5 个纯虚函数. 这 5 个方法顾名思义, 提供了对象的克隆, 序列化, 取哈希, 类型转换等操作, to_variant() 可能诈看不出来是做什么的, 这个在本篇以后的文章再讨论.

注意 abstract_object 是模板类, 在运行时实际上是会被展开生成许多类的.

对象 ID 类型

object 类中还有三个重要的属性:

static const uint8_t space_id = 0;
static const uint8_t type_id  = 0;
// serialized
object_id_type          id;

在 Graphene 中有类型众多且数量庞大的对象, 所以对每类对象分类标识就有比较必要, 每个对象的标识都有三部分组成: space_id, type_id, 和 sequence_id. space_id 取值一般是 1, type_id 表示对象类型, 比如账户对象, 见证人对象, sequence_id 就是这个类型下的每个对象的序号了. Graphene 中的对象 ID 总共 64 位, space id 占最高 8 位, type id 占中间 8 位, 最低 48 位是 sequence_id. 相信我们都在 bitshares 钱包或者区块链浏览器看到过 1.2.12376 这样的标识, 这里 type_id 是 2, 代表的就是对象类型, 12376 代表这个账户是链上的第几个被创建的账户. 是的, 1.2.12376 就是 @abit A 神的账户.

在定义中 space_id 和 type_id 被定义为静态常量, 这意味着 object 的派生类们在定义时就必须要为这两个属性赋初值, 确定自己的空间和类型. id 则是具体存储对象标识值的地方, 另外注意到 id 的类型叫做 object_id_type, 这个类型定义在 db/object_id.hpp 中, 它提供了对对象标识的一些基本操作, 比如根据标识值获取它的 space 和 type, 还重载了 string 方法, 使得 id 能够输出为我们所看到的 1.2.12376 这种格式.

说到 db/object_id.hpp, 这里还定义了另外一个类型就叫 object_id, 这里面另有文章, 下面会说.

对象 ID 对象

注: 为阅读清晰, 以后都用 <x>/xyz.hpp 来代表 libraries/x/include/graphene/x/xyz.hpp

前面说了 Graphene 中所有的对象都继承自 object 和 abstract_object, 但其实有一个特殊的例外, 那就是 <db>/object_id.hpp 所定义的 object_id 类型, object_id 表示对象的 ID 对象, 这很有意思, 也就是对象的 ID 自己也是对象. object_id 也是模板类, 模板参数其中就有 space_id 和 type_id, 由于模板参数中包含了 space_id 和 type_id, 因此每个 object_id 模板实例中只需要存 sequence_id 就可以了. Graphene 的代码中为每种对象类型都 typedef 了其对应的 object_id 模板实例类型.

但说实话单看 object_id 的定义我并没有觉得这个对象类有什么存在的意义, 我觉得有 object_id_type 类就足够了. object_id 这个模板类让代码变得更复杂了, 但也或许是我没领悟透吧.

示例

我们以 witness_object 为例, 看一看 Graphene 中的对象都是怎么定义的. witness_object 定义于 <chain>/witness_object.hpp 下:

   class witness_object : public abstract_object<witness_object>
   {
      public:
         static const uint8_t space_id = protocol_ids;
         static const uint8_t type_id = witness_object_type;

         account_id_type  witness_account;
         uint64_t         last_aslot = 0;
         public_key_type  signing_key;
         optional< vesting_balance_id_type > pay_vb;
         vote_id_type     vote_id;
         uint64_t         total_votes = 0;
         string           url;
         int64_t          total_missed = 0;
         uint32_t         last_confirmed_block_num = 0;

         witness_object() : vote_id(vote_id_type::witness) {}
   };

可以看到 witness_object 完美吻合奇异递归模板模式, 这样 witness_object 就自动拥有了 clone(), move_from() 等那五个能力. 另外可以看到其 space_id 和 type_id 字段果然是赋了两个固定的初值. object 中的 object_id_type 类型 id 字段没有在这里展示出来, 实际上每一个 witness_object 被创建时, 其 id 字段的 sequence_id 部分都会增 1.

再往下就是 witness_object 自有的一些属性, 比如每个 witness 都有一个对应的账户, 签名用的 key, 得票情况, miss 的块数等. 其中 account_id_type 和 vote_id_type 就是上面说的 object_id 模板类的两个实例.

Ok, 本文就到此吧. 可能写的不是很好, 单纯从底层介绍对象可能有点空, witness_object 的例子也只能说明对象定义方法. 后续降到索引和 db::object_database 部分的文章估计会帮助我们更清晰的理解 Graphene/bitshares 中对象的概念.

后期计划

  1. 在 object.hpp 的最后你可能也注意到了几个 FC_REFLECT 模样的宏, 这会在本篇章后续文章讨论 - 对象的反射与序列化.
  2. 在 witness_object.hpp 中除了 witness_object 之外还出现了 multi_index_container, generic_index, 这也是后续要展开讨论的话题 - 索引模型.

欢迎有兴趣或者对本系列文章写作有建议的朋友们一起在下面交流讨论~ 希望能结交和认识更多对 Graphene/bitshares 感兴趣的朋友们, 更多的人参与到 Graphene/bitshares 的建设中来.

准备写这篇文章时发现上篇文章被 adm 一个 80% 的赞了 $70 多, 真是受宠若惊, 感谢 adm 的支持~

H2
H3
H4
3 columns
2 columns
1 column
5 Comments