存储模块负责持久化存储链上的区块、交易、状态、历史读写集等账本数据,并对外提供上述数据的查询功能。区块链以区块为单位进行批量的数据提交,一次区块提交会涉及到多项账本数据的提交,比如:交易提交,状态数据修改等,所以存储模块需要维护账本数据的原子性。长安链支持常用的数据库来存储账本数据,如LevelDB、BadgerDB、TikvDB、MySQL等数据库,业务可选择其中任意一种数据库来部署区块链。
账本数据主要分为5类:
1、区块数据,记录区块元信息和交易数据:
交易数据,即序列化后的交易体,为了提供对单笔交易数据的查询,所以对交易数据进行了单独存储。
2、状态数据,记录智能合约中读写的链上状态数据,即世界状态。
3、历史数据,长安链对每笔交易在执行过程中的状态变化历史、合约调用历史、账户发起交易历史都可以进行记录,可用于后续追溯交易、状态数据的变迁过程。
4、合约执行结果读写集数据,长安链对每笔交易在执行过程中的所读写的状态数据集进行了单独保存,方便其他节点进行快速的数据同步。
5、事件数据,合约执行过程中产生的事件日志。
针对上述5类账本数据,长安链分别实现了5个DB类,分别是:Block DB、State DB、History DB、Result DB和Contract Event DB。采用多个数据库之后,就需要维护数据库之间的数据一致性,避免仅有部分数据库提交后,发生程序中断而导致不同数据库间的数据不一致,因此,长安链引入了Block binary log组件来持久化存储区块的原始内容,用于重启过程中的数据恢复,类似于数据库中的预写式日志(wal)的功能。 需要注意的是,历史数据、结果数据并不是每个节点必须保存的,节点可以根据自己的业务需要在配置文件中启用或者关闭历史数据库和结果数据库。
1.存储模块未开启区块文件存储时运行逻辑

首先将序列化后的区块、读写集数据、以及最新的区块高度写入Block binary log,用于异常中断后的数据恢复。为了提高性能,加入一层cache,新区块提交请求在更新完Block binary log之后,再将区块数据写入cache,在更新完log和cache后,提交即可返回,由后台线程异步更新Block DB、State DB、ContractEvent DB、History DB和Result DB。
在Block DB中记录区块元信息与交易信息,其中交易信息以TxID作为主键存储,区块信息以BlockHeight作为主键存储,区块元信息中只记录交易ID列表,同时索引BlockHash到BlockHeight的映射关系。Block DB中额外记录了当前最新的区块高度(LastBlockHeight)作为checkpoint,用以重启后的数据恢复。
在State DB中保存state数据,key为合约名与对象主键的组合:<contractName, ObjectKey>,同时记录最新的区块高度(LastBlockHeight)作为checkpoint。
在History DB中记录交易产生的三种类型的索引:
状态变更历史,以<contractName, ObjectKey,TxId>为索引
合约调用历史,以<contractName, TxId>为索引
账户交易历史,以<accountId,TxId>为索引
在Result DB中记录交易的读写集,读写集以TxID作为key,同时记录最新的区块高度(LastBlockHeight)作为checkpoint。
在ContractEventDB中记录下交易结果的EventLog,并记录最新区块高度作为checkpoint。
存储模块中的Block DB、State DB、History DB等都是封装后的DB对象,其具体实现要基于特定的数据库引擎,比如LevelDB、BadgerDB、TikvDB、MySQL等数据库引擎。为了实现可插拔的数据库引擎,长安链在数据库引擎之上封装了一层接口,并将LevelDB、BadgerDB、TikvDB、MySQL等数据库封装成DB provider。用户可以根据业务需求选择合适的数据库引擎作为长安链的底层存储组件。
chainmaker / store · ChainMaker长安链2.0版本存储基础模块项目https://git.chainmaker.org.cn/chainmaker/store
长安链目前支持4种数据库引擎可供选择,分别是LevelDB、BadgerDB、TikvDB、MySQL。
LevelDB,默认采用的数据库引擎,LevelDB作为一款嵌入式KV数据库,默认集成在长安链节点中,无需部署,性能也相对关系型数据要更好。
BadgerDB,作为另一种形式的KV单机数据库的实现,也是嵌入式KV数据库,性能在写入value比较大时比LevelDB更高,但是读性能可能差于LevelDB
TikvDB,作为KVDB的横向扩容版本,需要单独启动tikv服务,底层使用rocksdb,性能更高。tikv部署流程
MySQL,关系型数据库,支持schema和富查询,性能较KV数据库低,目前关系型数据库与区块链的状态数据并不能很好的结合,导致很少有区块链采用关系型数据库作为状态数据库。原因主要有两点:1.区块链需要对智能合约所读写的状态数据做严格的控制和校验,而SQL语句相对区块链来说过于灵活,难以控制;2.需要提前创建库表和索引,需要针对不同的智能合约创建不同的数据库表结构,不够灵活。目前长安链支持MySQL存储引擎,在系统数据如Block DB上支持区块元信息、交易信息的关系型语义,状态数据库支持kv的方式和智能合约编写SQL语句方式读写状态数据(world state)。
配置文件在节点本地配置文件chainmaker.yml中
- storage:
- store_path: ../data/ledgerData #账本的存储路径, 包括LevelDB、BadgerDB的数据目录,Block binary log的数据目录
- write_buffer_size: 4 #LevelDB的write_buffer_size, 单位为MB,默认为4M
- bloom_filter_bits: 10 #LevelDB的布隆过滤器参数,为每个key分配的额外bit空间,默认为10,如果少于或等于0,则不开启布隆过滤。
- disable_historydb: false #是否禁用历史读写集的存储功能, 默认为false,也就是保存历史读写集。
- disable_block_file_db: false #是否禁用区块文件存储功能, 默认为true,也就是未启用,新节点建议此处设置为false。
- logdb_segment_async: false #区块文件异步落盘与否, 默认为false,也就是默认同步落盘,异步落盘时存在断电后数据损坏风险。
- logdb_segment_size: 128 #区块文件大小,单位MB,默认20MB。
- blockdb_config: #BlockDB 数据库配置
- provider: leveldb #数据库类型,支持LevelDB,BadgerDB,TikvDB,Mysql,这里示例为LevelDB
- leveldb_config: #LevelDB的详细配置
- store_path: ../data/org1/blocks
- write_buffer_size: 4 #LevelDB的write_buffer_size, 单位为MB,默认为4M
- bloom_filter_bits: 10 #LevelDB的布隆过滤器参数,为每个key分配的额外bit空间,默认为10,如果少于或等于0,则不开启布隆过滤。
- block_write_buffer_size:
- statedb_config: #StateDB 数据库配置
- provider: sql #数据库类型,支持LevelDB,BadgerDB,TikvDB,Mysql,这里示例为Mysql
- sqldb_config: #SQL数据库的详细配置
- sqldb_type: mysql #具体的RDBMS为mysql,也可以是sqlite、mssql等
- dsn: root:password@tcp(127.0.0.1:3306)/ #MySQL的数据库连接字符串
- max_idle_conns: 10 #连接池中维持的最大的空闲连接数,默认为10
- max_open_conns: 10 #最大的可用连接数,默认为10
- conn_max_lifetime: 60 #连接维持的最长时间,单位秒,默认为60
- historydb_config: #HistoryDB数据库配置
- provider: badgerdb #数据库类型,支持LevelDB,BadgerDB,TikvDB,Mysql,这里示例为BadgerDB
- badgerdb_config:
- store_path: ../data/org1/history
- compression: 0 # value为0 不压缩,1 Snappy压缩,2 ZSTD压缩,默认为0
- value_threshold: 10240 # 单位为bytes,默认为10240 bytes
- resultdb_config: #ResultDB数据库配置
- provider: tikvdb # 支持LevelDB,BadgerDB,TikvDB,Mysql
- tikvdb_config:
- endpoints: "127.0.0.1:2379" # tikv pd server url,支持多个url, 如: "192.168.1.2:2379,192.168.1.3:2379"
- max_batch_count: 128 # 每次kv batch最大大小 默认128
- grpc_connection_count: 4 # chainmaker连接tikv的连接数, 默认4
- grpc_keep_alive_time: 10 # 保持连接的连接数, 默认10
- grpc_keep_alive_timeout: 3 # 保持连接的超时时间 默认3
- write_batch_size: 128 # 每次提交tikv批次最大大小,默认128
- disable_contract_eventdb: true #是否禁止合约事件存储功能,默认为true,如果设置为false,需要配置mysql
- contract_eventdb_config:
- provider: sql #如果开启contract event db 功能,需要指定provider为sql
- sqldb_config:
- sqldb_type: mysql #contract event db 只支持mysql
- dsn: root:password@tcp(127.0.0.1:3306)/ #mysql的连接信息,包括用户名、密码、ip、port等,示例:root:admin@tcp(127.0.0.1:3306)/
- storage:
- # Default store path
- store_path: ../data/ljh-org1.qianjinlian.com/ledgerData1 # [*]
-
- # Prefix for mysql db name
- # db_prefix: org1_
-
- # Minimum block height not allowed to be archived
- unarchive_block_height: 300000
-
- # Symmetric encryption algorithm for writing data to disk. can be sm4 or aes
- # encryptor: sm4 # [*]
-
- # Disable block file db, default: false
- disable_block_file_db: false
-
- # async write block in file block db to disk, default: false, so default is sync write disk
- logdb_segment_async: false
-
- # file size of .fdb, MB, default: 20
- logdb_segment_size: 128
-
- # bigfilter config
- enable_bigfilter: false #default false
- bigfilter_config:
- redis_hosts_port: "127.0.0.1:6300,127.0.0.1:6301" #redis host:port
- redis_password: abcpass #redis password
- tx_capacity: 1000000000 #support max transaction capacity
- fp_rate: 0.000000001 #false postive rate
-
- # RWC config
- enable_rwc: true #default false
-
- # suggest
- # if block_tx_capacity < 10000,
- # set rolling_window_cache_capacity greater than block_tx_capacity*1.1 and less than block_tx_capacity*2
- # if block_tx_capacity > 10000, set rolling_window_cache_capacity 20000
- rolling_window_cache_capacity: 200
-
- # Symmetric encryption key:16 bytes key
- # If pkcs11 is enabled, it is the keyID
- # encrypt_key: "1234567890123456"
- write_block_type: 0 # 0 common write,1 quick write
- disable_state_cache: false # default false
- state_cache_config:
- life_window: 3000000000000 #key/value ttl time, ns
- clean_window: 1000000000
- max_entry_size: 500
- hard_max_cache_size: 10240 #cache size MB
- # Block db config
- blockdb_config:
- # Event db only support sql
- provider: sql
- # Sql db config
- sqldb_config:
- # Event db only support mysql
- sqldb_type: mysql
- # Mysql connection info, such as: root:admin@tcp(127.0.0.1:3306)/
- dsn: root:root@tcp(192.168.0.201:3306)/
-
- # Example for sql provider
- # Databases type support leveldb, sql, badgerdb, tikvdb
- # provider: sql # [*]
- # If provider is sql, sqldb_config should not be null.
- # sqldb_config:
- # Sql db type, can be mysql, sqlite. sqlite only for test
- # sqldb_type: mysql # # [*]
- # Mysql connection info, the database name is not required. such as: root:admin@tcp(127.0.0.1:3306)/
- # dsn: root:password@tcp(127.0.0.1:3306)/
-
- # Example for badgerdb provider
- # Databases type support leveldb, sql, badgerdb, tikvdb
- # provider: badgerdb
- # If provider is badgerdb, badgerdb_config should not be null.
- # badgerdb_config:
- # BadgerDb store path
- # store_path: ../data/wx-org1.chainmaker.org/history
- # Whether compression is enabled for stored data, default is 0: disabled
- # compression: 0
- # Key and value are stored separately when value is greater than this byte, default is 1024 * 10
- # value_threshold: 256
- # Number of key value pairs written in batch. default is 128
- # write_batch_size: 1024
-
- # Example for tikv provider
- # provider: tikvdb
- # If provider is tikvdb, tikvdb_config should not be null.
- # tikvdb_config:
- # db_prefix: "node1_" #default is ""
- # endpoints: "127.0.0.1:2379" # tikv pd server url,support multi url, example :"192.168.1.2:2379,192.168.1.3:2379"
- # max_batch_count: 128 # max tikv commit batch size, default: 128
- # grpc_connection_count: 16 # chainmaker and tikv connect count, default: 4
- # grpc_keep_alive_time: 10 # keep connnet alive count, default: 10
- # grpc_keep_alive_timeout: 3 # keep connnect alive time, default: 3
- # write_batch_size: 128 # commit tikv bacth size each time, default: 128
- # State db config
- statedb_config:
- # Event db only support sql
- provider: sql
- # Sql db config
- sqldb_config:
- # Event db only support mysql
- sqldb_type: mysql
- # Mysql connection info, such as: root:admin@tcp(127.0.0.1:3306)/
- dsn: root:root@tcp(192.168.0.202:3306)/
-
- # History db config
- historydb_config:
- provider: sql
- disable_key_history: false
- disable_contract_history: true
- disable_account_history: true
- # Sql db config
- sqldb_config:
- # Event db only support mysql
- sqldb_type: mysql
- # Mysql connection info, such as: root:admin@tcp(127.0.0.1:3306)/
- dsn: root:root@tcp(192.168.0.203:3306)/
-
- # Result db config
- resultdb_config:
- # Event db only support sql
- provider: sql
- # Sql db config
- sqldb_config:
- # Event db only support mysql
- sqldb_type: mysql
- # Mysql connection info, such as: root:admin@tcp(127.0.0.1:3306)/
- dsn: root:root@tcp(192.168.0.204:3306)/
-
- # Disable contract event database or not. If it is false, contract_eventdb_config must be mysql
- disable_contract_eventdb: true
- # Contract event db config
- contract_eventdb_config:
- # Event db only support sql
- provider: sql
- # Sql db config
- sqldb_config:
- # Event db only support mysql
- sqldb_type: mysql
- # Mysql connection info, such as: root:admin@tcp(127.0.0.1:3306)/
- dsn: root:root@tcp(192.168.0.205:3306)/
docker run -p 3306:3306 --name mysql -v /mydata/mysql/log:/var/log/mysql -v /mydata/mysql/data:/var/lib/mysql -v /mydata/mysql/conf:/etc/mysql -e MYSQL_ROOT_PASSWORD=root -d mysql:5.7