
# 1.模块介绍

和传统的区块链实现相同，chain33中使用高性能、高可靠的KV DB来进行数据的存储，并且设计上提供的DB接口也都是针对KV的特性定义的。  
在chain33系统中，目前存在4个数据库实例，如下：  
>fzm@fzm001:~/chain33 $ ls datadir  
addrbook blockchain.db mavltree wallet    

其中：
>
**addrbook**  
实现的能力比较单一，主要是存储P2P的节点以及相关的状态信息。  
**wallet**  
存储本地账号信息。  
**blockchain.db**  
存储区块头、区块体以及区块相关的附加信息，还存储交易本地执行返回的结果信息。  
**store**  
存储交易执行的结果以及区块状态哈希信息。  

对于chain33的存储模块，有以下几个要点：  
（1）chain33的底层存储及操作通过灵活配置可支持多种实现方式。  
    底层根据配置可以支持多种类型的KV DB实现，比如goleveldb、gobadgerdb、gomemdb、gossdb等。  
    上述addrbook、wallet、blockchain.db、store这4个数据库实例的底层存储操作都是通过配置的具体的KV DB来实现的，比如默认配置的goleveldb。  
（2）实际应用中，Excutor执行器通过StateDB和LocalDB两个抽象数据库概念，分别用来对blockchain.db和store的存储进行消息查询等操作。  
（3）Store模块数据的存储格式，也采用可配置、可插拔的方式，默认支持mavl tree的存储格式。  
    chain33也支持用户自行扩展新的存储格式，比如单纯的KVDB存储格式、基于MVCC的KVDB存储格式、MPT存储格式等。  
    如（1）中描述，最终对数据存储的底层操作还是通过配置的具体的KV DB来实现的，比如goleveldb。
  

# 2.逻辑架构及上下文
## 2.1 存储模块上下文

![存储模块上下文](./resource/store-context.png)  
 
P2P、Wallet、Blockchain、Store几个模块均涉及存储，底层通过DB接口向KV DB实例写入及读出数据。  
BlockChain及Store模块也提供数据查询接口，具体通过StateDB、LocalDB对象使用消息来进行操作。  
BlockChain模块在生成区块时，可以通过消息向Store模块写入状态信息。  
Wallet、Consensus、Client等模块可以通过执行器，由StateDB、LocalDB对象分别向Blockchain、Store使用消息发起数据查询。  
Client模块也可以通过消息直接向Store发起数据查询。  

## 2.2 底层存储KV DB的逻辑架构

![DB接口](./resource/KVDB-interface.png)  

底层数据存储支持的kv db都需要适配上述接口，以goleveldb为例：

![goleveldb](./resource/goleveldb.png)
可以看到goleveldb实现了DB接口、Iterator接口、Batch接口，就可以作为存储模块的底层数据库实现来在chain33系统中使用。
gobadgerdb、gomemdb、gossdb等kv db实现也是类似的，需要实现DB接口、Iterator接口、Batch接口以适配上层应用的适用。

## 2.3 StateDB和LocalDB的逻辑架构  
  
![StateDB和LocalDB的逻辑架构](./resource/statedb-localdb.png)

![chain33-statedb](./resource/chain33-statedb.png)  
可见，这两个抽象数据库的功能非常简单，容易导致误解的是两个DB的使用场景和存储内容，所以下面使用表格方式，列出了两个DB之间的区别。  
![state-local-diff](./resource/state-local-diff.png)

## 2.4 Store的逻辑架构  

![Store的逻辑架构](./resource/store.png)  
Sotre层决定了状态数据在底层KV DB中将以何种格式进行组织。  
系统默认支持mavl tree格式的实现，通过加载插件还可支持单纯的KV DB的实现、基于MVCC的KV DB的实现、MPT的实现。
开发者如果要扩展开发自己的数据组织格式，需要实现SubStore接口，并注册到系统中，做好对应配置即可。

# 3.处理逻辑

## 3.1 底层KV DB的主要接口

数据接口定义在多个interface中，下面的介绍关注方法即可。

### 3.1.1 基本数据接口

    Get(key []byte) ([]byte, error)
    BatchGet(keys [][]byte) (values [][]byte, err error)
    Set(key []byte, value []byte) (err error)
    SetSync([]byte, []byte) error
    Delete([]byte) error
    DeleteSync([]byte) error    
    Close()
这些就是基本的数据的读、写、删除接口，在加上关闭数据库的接口。纯粹的Key-Value操作，直接和具体数据库的接口对接即可。有些数据库可能不支持异步写操作，这时只能都按照同步实现（这个对性能影响不大）。


### 3.1.2 范围查找接口

    List(prefix, key []byte, count, direction int32) ([][]byte, error)
    PrefixCount(prefix []byte) int64
这两个接口需要实现所谓的前缀查找，其实在KV的实现中，并不是纯粹的前缀查找，而是范围（Range）查找，因为匹配的数据KEY是按照根据prefix推算出来的范围进行查找的，下面举一个例子就知道了（这个查找效果和*后缀模糊匹配是同样的）。


例一： prefix=0xab9876abcd77ff


这时的查找范围是： 0xab9876abcd 77ff xxx  ~  0xab9876abcd 78ff xxx


例二：prefix=0xab9876abcd77ab


这时的查找范围是： 0xab9876abcd77 ab xxx  ~  0xab9876abcd77 ac xxx

其中的count是限定查找的个数，因为可能匹配的数据量很大，只返回前面的count个；

direction是匹配方向，正向的化就是从开始到结束，反向就是从结束到开始；


特别注意： 当count为0时（或PrefixCount接口），意思是返回所有匹配的数据，但是在具体数据库实现时，如果数据量特别大，可能会导致数据库挂死、网络超时、内存溢出等各种问题。
比较保险的做法是，内部查找时做一下处理，每次只取固定条数的记录（比如1万条），然后分多次查找，最终汇总返回结果。



### 3.1.3 批量数据接口

    type Batch interface {
        Set(key, value []byte)
        Delete(key []byte)
        Write() error
    }
可以认为这是一个事务接口，可以进行多次Set和Delete操作，最终一次Write提交操作，要么全部成功要么全部失败。

在数据库实现时，特别是不支持原生事务的数据库时，可能要考虑折中实现，下面以ssdb的实现为例说明：


newBatch时，开辟两个缓存对象，分别用来存储修改和删除操作缓存队列；
Set操作时，检查Delete队列是否有相应Key，如有则从Delete队列删除，Set操作入Set队列；
Delete操作时，检查Set队列中是否有相应Key，如有则从Set队列删除，Delete操作入Delete队列；
Write操作时，向Set队列中插入所有Delete队列中的Key，Value设置为空，然后调批量Set接口，如失败，则返回，如成功，则调批量删除接口，然后返回成功；


之所以这么操作，有两点考虑：

1、常规的事务操作是保证内部的操作顺序的，所以在Set和Delete操作时，需要检查对方的队列，防止先Delete后Set出现的问题；  
2、事务提交时，为了防止写成功，但删除失败的中间状态，所以在写操作的同时，将要删除的数据置空；



### 3.1.4 迭代器接口

    type IteratorSeeker interface {
        Rewind() bool
        Seek(key []byte) bool
        Next() bool
    }
    type Iterator interface {
        IteratorSeeker
        Valid() bool
        Key() []byte
        Value() []byte
        ValueCopy() []byte
        Error() error
        Close()
    }
chain33中包装的迭代器，类似C中的指针或SQL中的游标的能力，Iterator中的接口很好实现，主要是IteratorSeeker中的三个方法，对有些数据库来说，实现起来存在一定的难度。下面还是以ssdb为例，说明下迭代器的实现逻辑。

迭代器的创建接口如下：

    //迭代prefix 范围的所有key value, 支持正反顺序迭代
    Iterator(prefix []byte, reserver bool) Iterator
在创建迭代器时，包装了前缀查找的能力，在ssdb的实现逻辑如下：


1、使用prefix计算出匹配的开始和结束KEY；  
2、使用生的keys或rkeys命令，查找满足此prefix前缀的所有KEY（使用1024进行分页），然后将这些信息包装进Iterator对象；  
3、Next方法执行时，将游标+1，从缓存数组中获取对应的KEY，然后调用DB的Get接口获取数据；    
3.1、如果游标+1==1024，则以当前缓存数组中的最后一个元素KEY为开始，继续获取下一页KEY；  
4、Seek方法执行时，会连续调用分页获取KEYS的逻辑，然后和key匹配，直到满足，或没有数据；  

### 3.1.5 其它特定接口

    // 模拟事务接口
    Begin()
    Rollback()
    Commit()
上面三个方法是模拟事务操作，目前只在StateDB中实现，仅支持单线程内存事务，是在支持TxGroup概念的时候引入的。新增一种数据库实现时，不需要考虑对这三个接口的支持。而且在自己的代码逻辑中，也不要调用这几个接口。


## 3.2 StateDB和LocalDB的相关主要接口  
### 3.2.1执行器中，和数据相关的有下面几个接口：
    SetStateDB(dbm.KV)
    SetLocalDB(dbm.KVDB)
    Exec(tx *types.Transaction, index int) (*types.Receipt, error)
    ExecLocal(tx *types.Transaction, receipt *types.ReceiptData, index int) (*types.LocalDBSet, error)
    ExecDelLocal(tx *types.Transaction, receipt *types.ReceiptData, index int) (*types.LocalDBSet, error)

#### Exec 
交易的具体执行逻辑，不管执行了什么逻辑，最终都会返回一个*types.Receipt对象，这个对象中包含了两部分内容，KV和Logs，其中KV将会被写入StateDB（<font color=#FF0000>最终写入store </font>），而Logs将会作为入参，在调用ExecLocal时传入；

默认的Exec方法，不生成任何数据；


#### ExecLocal


交易的本地执行逻辑，这是一个附加的逻辑，两次执行结果不同，不会导致区块执行失败，这里的主要逻辑一般是使用Exec生成的信息，再生成一些附加信息，方便其它地方使用；

默认的ExecLocal方法，生成交易哈希对应的交易详情数据；

<font color=#FF0000>相关数据最终会被blockchain写入blockchain.db。</font>

#### ExecDelLocal  
此方法和ExecLocal对应，是在处理分叉时调用，如果一个区块已经被执行，而后另一条链成为主链，那么已经执行的区块将会被回退，这时会调用ExecDelLocal逻辑。它应该将ExecLocal写入的数据进行回滚。  

### 3.2.2 StateDB的主要接口：
     //通过消息EventStoreGet从Store（对应第一章的store）中获得状态数据
     Get(key []byte) ([]byte, error)  

### 3.2.3 LocalDB的主要接口：    
     //通过消息EventLocalGet从BlockStore数据库（对应第一章的blockchain.db）中获得区块数据
     Get(key []byte) ([]byte, error)  

     //通过消息EventLocalList从BlockStore数据库（对应第一章的blockchain.db）中查询数据列表
     List(prefix, key []byte, count, direction int32) ([][]byte, error)  

     //通过消息EventLocalPrefixCount从BlockStore数据库（对应第一章的blockchain.db）中查询指定前缀的key的数量
     PrefixCount(prefix []byte) (count int64)


## 3.3 SubStore的主要接口  

    type SubStore interface {
    //向对应StateHash写入KV信息
	Set(datas *types.StoreSet, sync bool) ([]byte, error)
    
    //获得对应StateHash的KV信息
	Get(datas *types.StoreGet) [][]byte

    //Blockchain模块生成区块时，设置对应StateHash的KVSet到内存。
	MemSet(datas *types.StoreSet, sync bool) ([]byte, error)

    //Blockchain模块生成区块时，将对应StateHash的内存KVSet写入到存储。
	Commit(hash *types.ReqHash) ([]byte, error)

    //Blockchain模块生成区块失败时，将对应StateHash的内存KVSet数据清除。
	Rollback(req *types.ReqHash) ([]byte, error)

    //异常时，回滚已写入区块的StateHash对应的KV信息。
	Del(req *types.StoreDel) ([]byte, error)

    //对应于StateHash，遍历满足条件的Key、value，并使用fn函数进行处理。
	IterateRangeByStateHash(statehash []byte, start []byte, end []byte, ascending bool, fn func(key, value []byte) bool)

    //预留的事件处理接口
	ProcEvent(msg queue.Message)
    }

    
