主页 > imtoken安卓版 > EOSIO源码分析-Chainbase运行原理分析

EOSIO源码分析-Chainbase运行原理分析

imtoken安卓版 2023-10-15 05:07:53

链库简介

Chainbase是eosio开发的高性能内存映射数据库,其核心是利用内存文件映射技术实现的。 最后,文件的内容可以自动映射到内存中。 可以说实现了修改、访问、存储的自动映射。 使用的相关头文件如下

#include 
#include 
#include 
#include 

整个chainbase的实现和定义都浓缩在chainbase.hpp和chainbase.cpp文件中,使用灵活的模板类实现,具有很高的扩展性。

chainbase核心类数据库:整个chainbase数据库的接入点,核心接口如下abstract_index:单表的操作接口,我们创建的单对象表就在这个类中

abstract_index接口对应数据库的增删改查。 代码如下

class abstract_index
   {
      public:
         abstract_index( void* i ):_idx_ptr(i){}
         virtual ~abstract_index(){}
         virtual void     set_revision( uint64_t revision ) = 0;
         virtual unique_ptr<abstract_session> start_undo_session( bool enabled ) = 0;
         virtual int64_t revision()const = 0;
         virtual void    undo()const = 0;
         virtual void    squash()const = 0;
         virtual void    commit( int64_t revision )const = 0;
         virtual void    undo_all()const = 0;
         virtual void remove_object( int64_t id ) = 0;
   };

undo_state:回滚状态,记录回滚操作所需的相关置信度

template< typename value_type >
class undo_state
{
   public:
      typedef typename value_type::id_type                      id_type;
      typedef allocator< std::pair<const id_type, value_type> > id_value_allocator_type;
      typedef allocator< id_type >                              id_allocator_type;
      template<typename T>
      undo_state( allocator<T> al )
      :old_values( id_value_allocator_type( al.get_segment_manager() ) ),
       removed_values( id_value_allocator_type( al.get_segment_manager() ) ),
       new_ids( id_allocator_type( al.get_segment_manager() ) ){}
      typedef boost::interprocess::map< id_type, value_type, std::less<id_type>, id_value_allocator_type >  id_value_type_map;
      typedef boost::interprocess::set< id_type, std::less<id_type>, id_allocator_type >                    id_type_set;
      id_value_type_map            old_values;			//对象发生修改时,记录的旧值,回滚时用以恢复旧值
      id_value_type_map            removed_values;		//对象被删除时,记录改值,回滚时重新添加该值
      id_type_set                  new_ids;				//添加的新对象,回滚时需要删除改新对象
      id_type                      old_next_id = 0;		//记录创建回滚节点是的上一次块号	
      int64_t                      revision = 0;		//当前回滚块号,在按照块号回滚石,就是使用这个数值进行判断的
};

generic_index:表操作的实现类

在这个类中,核心是在实现数据操作的同时记录修改数据的操作,并利用这些操作记录实现回滚

Chainbase在EOSIO中的应用

由于eosio使用了chainbase,在整个eosio中,哪些数据是系统使用chainbase记录的,相关表的代码定义如下

// 记录了账户,资源,合约,已经生产相关的数据
// 注意:table_id_multi_index记录创建了那些合约表
using controller_index_set = index_set<
   account_index,
   account_metadata_index,
   account_ram_correction_index,
   global_property_multi_index,
   protocol_state_multi_index,
   dynamic_global_property_multi_index,
   block_summary_multi_index,
   transaction_multi_index,
   generated_transaction_multi_index,
   table_id_multi_index,
   code_index,
   database_header_multi_index
>;
// 合约状态数据表相关
// key_value_index是核心
using contract_database_index_set = index_set<
   key_value_index,
   index64_index,
   index128_index,
   index256_index,
   index_double_index,
   index_long_double_index
>;
// 账户权限相关表
using authorization_index_set = index_set<
   permission_index,
   permission_usage_index,
   permission_link_index
>;

以上是系统在eosio中创建的所有表,在创建时,我们只列出其中一个作为例子,代码如下

// 以账户权限表为例
void authorization_manager::add_indices() {
   authorization_index_set::add_indices(_db);
}
// 继续进入代码如下
// 注意: 最终调用代码db.add_index函数,回顾上面的类容
template<typename Index>
   class index_set<Index> {
   public:
      static void add_indices( chainbase::database& db ) {
         db.add_index<Index>();
      }
   };
   template<typename FirstIndex, typename ...RemainingIndices>
   class index_set<FirstIndex, RemainingIndices...> {
   public:
      static void add_indices( chainbase::database& db ) {
         index_set<FirstIndex>::add_indices(db);
         index_set<RemainingIndices...>::add_indices(db);
      }
   };

通过跟踪我们会发现,在系统初始化的时候,会分别创建对应的表,最后会在chainbase文件中创建表。

EOSIO 中的数据存储

从以上章节我们可以分析出eosio的数据存储可以归纳为两类,一类是预定义的对象,比如账户、权限等数据比特币源码分析,一类是合约状态数据,这类数据系统没有知道了,那么在eosio中如何处理这两类数据

通用对象存储

我们还是以账户数据为例,账户数据表定义如下

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;
};
using account_id_type = account_object::id_type;
struct by_name;
using account_index = chainbase::shared_multi_index_container<
   account_object,
   indexed_by<
      ordered_unique<tag<by_id>, member<account_object, account_object::id_type, &account_object::id>>,
      ordered_unique<tag<by_name>, member<account_object, account_name, &account_object::name>>
   >
>;
// 最终表创建时,调用如以下格式
db.add_index<account_index>();

从上面的代码中,我们可以看到一些结果:

template<typename MultiIndexType>
void add_index() {
   const uint16_t type_id = generic_index<MultiIndexType>::value_type::type_id;
   typedef generic_index<MultiIndexType>          index_type;
   typedef typename index_type::allocator_type    index_alloc;
   std::string type_name = boost::core::demangle( typeid( typename index_type::value_type ).name() );
   // 判断类型是否合法,表是否存在
   if( !( _index_map.size() <= type_id || _index_map[ type_id ] == nullptr ) ) {
      BOOST_THROW_EXCEPTION( std::logic_error( type_name + "::type_id is already in use" ) );
   }
   // 创建该表的内存segment,建立内存与文件的映射关系
   index_type* idx_ptr = nullptr;
   if( _read_only )
      idx_ptr = _db_file.get_segment_manager()->find_no_lock< index_type >( type_name.c_str() ).first;
   else
      idx_ptr = _db_file.get_segment_manager()->find< index_type >( type_name.c_str() ).first;
   bool first_time_adding = false;
   if( !idx_ptr ) {
      if( _read_only ) {
         BOOST_THROW_EXCEPTION( std::runtime_error( "unable to find index for " + type_name + " in read only database" ) );
      }
      first_time_adding = true;
      idx_ptr = _db_file.get_segment_manager()->construct< index_type >( type_name.c_str() )( index_alloc( _db_file.get_segment_manager() ) );
    }
    // 将创建出来的新表,加入_index_list对象
   auto new_index = new index<index_type>( *idx_ptr );
   _index_map[ type_id ].reset( new_index );
   _index_list.push_back( new_index );
}

那么程序是如何访问这个对象表的呢?账户权限代码如下

// 添加account_object对象
const auto& new_account = db.create<account_object>([&](auto& a) {
   a.name = create.name;
   a.creation_date = context.control.pending_block_time();
});
// 添加permission_object
const auto& perm = _db.create<permission_object>([&](auto& p) {
   p.usage_id     = perm_usage.id;
   p.parent       = parent;
   p.owner        = account;
   p.name         = name;
   p.last_updated = creation_time;
   p.auth         = std::move(auth);
});
// 修改permission_object
_db.modify( permission, [&](permission_object& po) {
   po.auth = auth;
   po.last_updated = _control.pending_block_time();
});
// 删除permission_object
_db.remove( permission );

合约数据的存储

用户的合约数据是用户自定义数据,是不确定的数据定义,那么eosio是怎么解决的呢? 我们先看结构体定义

/**
 * 表对象定义,记录用户在合约中创建了多少个表
 */
class table_id_object : public chainbase::object<table_id_object_type, table_id_object> {
   OBJECT_CTOR(table_id_object)
   id_type        id;		//表ID
   account_name   code;  	//合约账户
   scope_name     scope; 	//表类型
   table_name     table; 	//表名
   account_name   payer;	//创建者
   uint32_t       count = 0; /// the number of elements in the table
};
struct by_code_scope_table;
// 表对象表
using table_id_multi_index = chainbase::shared_multi_index_container<
   table_id_object,
   indexed_by<
      ordered_unique<tag<by_id>,
         member<table_id_object, table_id_object::id_type, &table_id_object::id>
      >,
      ordered_unique<tag<by_code_scope_table>,
         composite_key< table_id_object,
            member<table_id_object, account_name, &table_id_object::code>,
            member<table_id_object, scope_name,   &table_id_object::scope>,
            member<table_id_object, table_name,   &table_id_object::table>
         >
      >
   >
>;
using table_id = table_id_object::id_type;
struct by_scope_primary;
struct by_scope_secondary;
struct by_scope_tertiary;
/**
 * 合约记录数据状态对象
 */
struct key_value_object : public chainbase::object<key_value_object_type, key_value_object> {
   OBJECT_CTOR(key_value_object, (value))
   typedef uint64_t key_type;
   static const int number_of_keys = 1;
   id_type               id;			//记录ID
   table_id              t_id; 			//表ID,表明对象存在于那张表里面,对应table_id_object表
   uint64_t              primary_key; 	//记录主键
   account_name          payer;			//谁操作的
   shared_blob           value;			//数据内容,注意这里数据类型为shared_blob, 通过代码我们知道shared_blob为一段连续的二进制内存数据,所以我们确定合约的状态数据系统会将其序列化为shared_blob来存储,查询时反序列化给我们看
};
// 合约记录数据对象表
using key_value_index = chainbase::shared_multi_index_container<
   key_value_object,
   indexed_by<
      ordered_unique<tag<by_id>, member<key_value_object, key_value_object::id_type, &key_value_object::id>>,
      ordered_unique<tag<by_scope_primary>,
         composite_key< key_value_object,
            member<key_value_object, table_id, &key_value_object::t_id>,
            member<key_value_object, uint64_t, &key_value_object::primary_key>
         >,
         composite_key_compare< std::less<table_id>, std::less<uint64_t> >
      >
   >
>;

看完合约状态数据表的定义,我们可以得出以下结论

对于合约状态数据,系统也定义了index64_object类的定义。 这类数据定义了状态表的第二个索引,方便我们后面的查找。 此处不再赘述

Chainbase回滚机制

要想了解chainbase的回滚机制,就得看它在创建、修改、删除时做了哪些操作。 同时想一想我们在第一章介绍的结构体undo_state,它是如何处理回滚的,接下来我们以插入新数据为例来分析一下chainbase的回滚机制

插入目标代码如下

template<typename ObjectType, typename Constructor>
const ObjectType& create( Constructor&& con )
{
    CHAINBASE_REQUIRE_WRITE_LOCK("create", ObjectType);
    typedef typename get_index_type<ObjectType>::type index_type;
	// 最后调用函数 emplace
    return get_mutable_index<index_type>().emplace( std::forward<Constructor>(con) );
}
template<typename Constructor>
const value_type& emplace( Constructor&& c ) {
   auto new_id = _next_id;
   
   // 构造新对象
   auto constructor = [&]( value_type& v ) {
      v.id = new_id;
      c( v );
   };
   // 将对象放入容器
   auto insert_result = _indices.emplace( constructor, _indices.get_allocator() );
   // 如果失败,则抛出异常
   if( !insert_result.second ) {
      BOOST_THROW_EXCEPTION( std::logic_error("could not insert object, most likely a uniqueness constraint was violated") );
   }
   // 记录ID+1
   ++_next_id;
   // 数据插入已经完成,接下来继续看on_create函数干了啥
   on_create( *insert_result.first );
   return *insert_result.first;
}
void on_create( const value_type& v ) {
  // 如果不能修改,直接返回
  if( !enabled() ) return;
  // _stack: boost::interprocess::deque< undo_state_type, allocator > _stack;
  // 可以看到_stack记录了一个操作命令表,类型为 undo_state_type
  auto& head = _stack.back();
  // 最后我们看到,chainbase将新添加对象的ID记录下来了
  head.new_ids.insert( v.id );
}

至此,整个创建流程就完成了,我们看到在保存对象的同时,还记录了一个操作命令序列对象

那么它的撤消是如何做到的呢? 接下来继续看代码

// 下面的代码清晰的展示出,在进行回滚时,chainbase都做了哪些事情
void undo() {
   if( !enabled() ) return;
   // 获取当前操作记录的头节点
   const auto& head = _stack.back();
   // 对于新添加的对象要删除
   for( auto id : head.new_ids )
   {
      _indices.erase( _indices.find( id ) );
   }
   // ID恢复
   _next_id = head.old_next_id;
   // 对于修改的对象,进行恢复
   for( auto& item : head.old_values ) {
      auto ok = _indices.modify( _indices.find( item.second.id ), [&]( value_type& v ) {
         v = std::move( item.second );
      });
      if( !ok ) std::abort(); // uniqueness violation
   }
   // 对于已经删除掉的对象,重新添加回来
   for( auto& item : head.removed_values ) {
      bool ok = _indices.emplace( std::move( item.second ) ).second;
      if( !ok ) std::abort(); // uniqueness violation
   }
   _stack.pop_back();
   --_revision;
}

明白了,是不是很简单,每一步都要记录自己的操作,回滚的时候进行对应的相反操作

整个EOSIO系统上层的回滚分为两类

总结

在这篇文章中,我们详细分析了chainbase数据库比特币源码分析,它的结构、运行,以及它的回滚机制(类似git)。 我们在总结问题的时候,也会有以下的疑问

等等,随着我们对代码越来越熟悉,我们会有更多的问题,继续