# 长安链数据管理 ## 数据库的选型与配置 ### 数据库选型 长安链数据库划分是按照存储数据的功能和特性分为以下几种数据库: * BlockDB 存储区块与交易 * StateDB 存储最新的世界状态 * HistoryDB 存储状态变更历史,账户交易历史和合约调用历史,是可选的 * ResultDB 存储合约执行的读写集,是可选的 * ContractEventDB 存储合约事件日志数据库,是可选的 在数据持久化存储上,长安链支持常用的数据库来存储账本数据,如LevelDB、BadgerDB、TikvDB、MySQL等数据库,业务可选择其中任意一种数据库来部署区块链,每种数据库的简介如下: - LevelDB,默认采用的数据库引擎,LevelDB作为一款嵌入式KV数据库,默认集成在长安链节点中,无需部署,性能也相对关系型数据要更好。 - BadgerDB,作为另一种形式的KV单机数据库的实现,也是嵌入式KV数据库,性能在写入value比较大时比LevelDB更高,但是读性能可能差于LevelDB - TikvDB,作为KVDB的横向扩容版本,需要单独启动tikv服务,底层使用rocksdb,性能更高。[tikv部署流程](./Tikv安装部署.md) - MySQL, (provider为:chainmaker.yml -> storage -> blockdb_config/statedb_config/resultdb/... -> provider) - 采用关系型数据库的方式(provider: sql),支持schema和富查询,性能较KV数据库低,目前关系型数据库与区块链的状态数据并不能很好的结合,导致很少有区块链采用关系型数据库作为状态数据库。原因主要有两点:1.区块链需要对智能合约所读写的状态数据做严格的控制和校验,而SQL语句相对区块链来说过于灵活,难以控制;2.需要提前创建库表和索引,需要针对不同的智能合约创建不同的数据库表结构,不够灵活。目前长安链支持MySQL存储引擎,在系统数据如Block DB上支持区块元信息、交易信息的关系型语义,状态数据库支持kv的方式和智能合约编写SQL语句方式读写状态数据(world state)。 - 采用模拟非关系型数据库的方式(provider: sqlkv),类似KV数据库的方式组织数据存储到mysql中,适合搭配BFDB存储模式使用(用户数据量偏大时可将mysql数据库替换成TDSQL等分布式存储数据库)(推荐使用)。 长安链的存储分为RawDB(非文件存储)和BFDB(文件存储)两种方式 (详见: [数据库存储模式](../tech/数据存储.html#storeMode)) - RawDB:RawDB是指将数据完全存储在关系或非关系型数据库中RawDB的优点是数据存储在数据库中 - BFDB:BFDB是指将区块数据存放在"区块文件存储"中,然后将"区块文件存储"的数据索引信息存放在区块数据库、结果据库,其他业务数据库(状态数据库、历史数据库、事件数据库)与RawDB的数据一样 - 存储方式选型: | 节点数据存储类型 | 别称 | 配置方式 | chainmaker-go支持版本 | blockdb/resultdb/statedb/historydb等的provider | 是否推荐 | |:--------:|:-----:|:---------------------------:|:-----------------:|:--------------------------------------------:|:----:| | RawDB | 非文件存储 | disable_block_file_db:true | \>= v1.2.4 | leveldb/badgerdb/tikvdb/sql | 否 | | BFDB | 文件存储 | disable_block_file_db:false | \>= v2.2.0 | leveldb/badgerdb/tikvdb/sqlkv | 是 | - 注: - "长安链节点" BFDB归档方案不支持采用sql方式(provider: sql)存储的节点数据,用户可以使用sqlkv(provider: sqlkv)方式替代 - 采用sql方式(provider: sql)存储的节点额外支持在链上部署支持sql的合约,但该sql合约性能偏低,不推荐使用 - 表格中的provider为:chainmaker.yml -> storage -> blockdb_config/statedb_config/... -> provider ### 数据库配置 长安链的数据库配置在chainmaker.yml中,具体详见: [9.6.2 配置说明](../tech/数据存储.html#store_config)) ### 数据库选择建议 在数据库选择时,需要根据自身业务类型和数据量以及运营模式而定 - 在区块链数据不到1T的情况下,建议使用LevelDB作为默认的数据库;(provider:leveldb) - 如果状态数据中存储了大量上KB级别的Value,可以将BadgerDB作为状态数据库的底层;(provider:badgerdb) - 如果预期数据量上T甚至更多且具备数据库自行运维能力,则建议采用TiKV作为数据库底层;(provider:tikvdb) - 如果预期数据量上T甚至更多但是不具备数据库自行运维能力,则建议采用TDSQL等常见的分布式云关系数据库作为数据库底层;(provider:sqlkv) - 如果支持的业务复杂,合约需要大量富查询和各个字段的索引查询,汇总等,建议采用MySQL作为状态数据库底层,并且使用SQL语句来编写智能合约。(但该合约性能偏低,不建议使用)(provider:sql) 在选择了不同的数据库作为provider后,其后续的子配置项就是这个数据库的参数配置,比如provider: leveldb,那么后续子配置项就是leveldb_config,其中store_path是最重要的配置参数,其他选型可以采用默认值即可,除非您非常明确这个选项对数据库的影响。 如果出于性能的考虑,可以关闭HistoryDB、ResultDB和ContractEventDB,这些数据库可以帮忙开发者查询更详细的记录,用于区块链浏览器或者数据分析的情况。 ## SQL数据库密码加密(v2.3.10新增) 使用SQL类数据库(provider为`sql`或`sqlkv`,如MySQL、TDSQL)存储账本数据时,数据库连接串(DSN)中的密码默认以明文方式配置在chainmaker.yml中,存在敏感信息泄露风险。长安链支持将DSN中的密码加密后再配置:密文以`ENC(...)`格式写入chainmaker.yml的DSN中,节点启动时自动解密并连接数据库,明文密码仅存在于节点内存中,不会出现在配置文件和日志中(日志输出均经过脱敏处理)。 密码加密采用基于口令的加密算法(PBE,使用PBKDF2派生密钥,迭代100000次),支持以下三种算法套件: | 算法套件(alg) | 缺省hash | 缺省密钥位数 | 说明 | |:---:|:---:|:---:|:---| | sm4-cbc(缺省) | sm3 | 128 | PBKDF2-SM3派生16字节密钥 + SM4-CBC加密 | | aes-cbc | sha256 | 256 | PBKDF2-SHA256派生32字节密钥 + AES-CBC加密 | | aes-gcm | sha256 | 256 | PBKDF2-SHA256派生32字节密钥 + AES-GCM认证加密 | 整体使用流程如下: 1. 使用cmc工具对数据库明文密码进行加密,得到base64编码的密文; 2. 将DSN中的密码部分替换为`ENC(密文)`; 3. 在chainmaker.yml的sqldb_config下配置password_encrypt节点(算法套件与加密口令需与加密时一致); 4. 重启节点,节点启动时自动解密并连接数据库。 ### 使用cmc加密数据库密码 cmc提供`cmc crypto encrypt`命令,用于对明文数据进行基于口令的对称加密,可用其对数据库明文密码加密。 命令格式: ```shell ./cmc crypto encrypt -a <算法> [-m <分组模式>] [--hash <哈希算法>] [-p <加密口令>] [-s <待加密数据>] --out-base64 ``` 参数说明: | 参数 | 说明 | |:---|:---| | -a, --algo | 加密算法:AES(缺省)、SM4 | | -m, --mode | 分组模式:CBC(缺省)、GCM(仅AES支持) | | --hash | PBKDF2派生密钥使用的哈希:AES缺省为SHA256,可选SM3;SM4仅支持SM3(缺省) | | -p, --password | 加密口令,用于派生加密密钥,需与chainmaker.yml中passphrase一致;也可使用--password-file从文件读取口令(与-p互斥) | | -s, --data-string | 待加密数据,本场景中为数据库密码;也可使用-i/--data-file从文件读取(与-s互斥) | | --out-base64 | 以base64编码输出密文。DSN中ENC(...)内为base64密文,推荐使用该选项;输出选项还支持--out-hex、-o/--out-file等 | -p与-s均可省略,省略时cmc将在终端交互式提示输入,终端下输入不回显(屏幕上不显示明文)。相比命令行传参,交互式输入的密码不会留在shell历史记录(history)和进程列表(ps)中,也不受shell特殊字符影响,安全性更好。 使用示例,以缺省套件SM4-CBC加密数据库密码`Passw0rd!`,加密口令为`my-passphrase`: ```shell $ ./cmc crypto encrypt -a SM4 -p my-passphrase -s "Passw0rd!" --out-base64 ``` 交互式输入,省略-p与-s,按提示依次输入加密口令与数据库密码(推荐): ```shell $ ./cmc crypto encrypt -a SM4 --out-base64 Enter password: Enter data: ``` 以AES-GCM套件加密,加密口令与数据库密码均通过文件指定: ```shell $ ./cmc crypto encrypt -a AES -m GCM --password-file ./passphrase.txt -i ./plain-password.txt --out-base64 ``` 命令输出base64编码的密文(每次加密引入随机salt,同一明文每次输出的密文不同),将该密文用于DSN配置。 > **注意**:Linux shell中`$`是特殊字符。口令或密码含`$`时(如`my$pass`),若用双引号包裹,`$pass`会被shell当作变量展开为空,实际传给cmc的口令被截断,与chainmaker.yml中配置的passphrase不一致,导致节点启动解密失败。请用单引号包裹:`-p 'my$pass'`,或用`\$`转义:`-p "my\$pass"`,或省略-p/-s采用交互式输入(不经过shell,不受影响,推荐)。 ### chainmaker.yml配置 #### 配置DSN密文 将DSN中用户名与主机地址之间的密码部分替换为`ENC(密文)`格式。例如明文DSN: ``` root:Passw0rd!@tcp(127.0.0.1:3306)/ ``` 加密后配置为: ``` root:ENC(J9xK3xxxx...base64密文...xxxx)@tcp(127.0.0.1:3306)/ ``` #### 配置password_encrypt节点 在sqldb_config下新增password_encrypt配置,算法套件与口令必须与cmc加密时使用的参数一致,配置示例: ``` yml storage: statedb_config: provider: sqlkv sqldb_config: sqldb_type: mysql dsn: root:ENC(J9xK3xxxx...base64密文...xxxx)@tcp(127.0.0.1:3306)/ password_encrypt: # 密码加密配置,仅当DSN密码为ENC(...)密文时生效 alg: sm4-cbc # 算法套件:sm4-cbc(缺省)/aes-cbc/aes-gcm passphrase: my-passphrase # 加密口令,与cmc加密时使用的口令一致 ``` password_encrypt配置项说明: | 配置项 | 说明 | |:---|:---| | alg | 算法套件代号:sm4-cbc(缺省)/ aes-cbc / aes-gcm | | hash | PBKDF2派生密钥使用的哈希:sm3 / sha256 / sha3-256,缺省随算法:sm4-cbc为sm3,aes-cbc、aes-gcm为sha256 | | alg_bits | PBKDF2派生密钥的位数:128/192/256,缺省随算法:sm4-cbc为128,aes-cbc、aes-gcm为256。使用cmc加密时保持缺省即可(cmc加密AES固定为256位、SM4固定为128位密钥) | | passphrase | 加密口令,DSN含ENC密文时必填。支持两种配置方式:直接配置口令值(默认);配置为`env:环境变量名`,节点启动时从指定环境变量读取口令。口令值本身不要以`env:`开头,否则会被当作环境变量引用导致解密失败 | cmc加密参数与password_encrypt配置的对应关系: | cmc crypto encrypt命令参数 | password_encrypt配置 | |:---|:---| | -a SM4(模式缺省为CBC) | alg: sm4-cbc(hash缺省sm3) | | -a AES(模式缺省为CBC) | alg: aes-cbc(hash缺省sha256) | | -a AES -m GCM | alg: aes-gcm(hash缺省sha256) | | --hash SM3 / --hash SHA256 | hash: sm3 / hash: sha256 | | -p/--password-file指定的口令 | passphrase | #### 口令通过环境变量注入(可选) 为避免口令明文出现在配置文件中,passphrase可配置为`env:环境变量名`,节点启动时从对应环境变量读取口令,适用于K8s Secret envFrom、systemd EnvironmentFile等口令注入场景: ``` yml password_encrypt: alg: sm4-cbc passphrase: env:SQL_PWD_KEY # 从环境变量SQL_PWD_KEY读取加密口令 ``` 若口令含`$`,通过`export`设置该环境变量时同样会被shell截断,需用单引号:`export SQL_PWD_KEY='my$pass'`。 ### 注意事项 - password_encrypt仅当DSN密码为`ENC(...)`密文时生效;DSN密码为明文(未使用ENC包装)时按原有逻辑直连数据库,存量配置无需任何迁移即可升级。 - 该功能适用于所有使用SQL数据库的存储配置,包括blockdb_config、statedb_config、historydb_config、resultdb_config、contract_eventdb_config(provider为sql或sqlkv)下的sqldb_config。 - 若数据库真实密码本身以`ENC(`开头,请修改真实密码,避免被误判为密文导致解密失败。 - 口令字面值不要以`env:`开头,会被误判为环境变量引用;确需使用此类口令时,改用`env:环境变量名`方式间接配置。 - 解密失败(口令错误、算法套件或hash等参数与加密时不一致、密文损坏等)时节点启动将报错退出,不会静默降级为明文连接。 - cmc加密AES时固定派生256位密钥、SM4固定128位密钥,sqldb侧alg_bits需保持缺省值,否则与cmc加密参数不匹配会导致解密失败。 - hash虽可配置为sha3-256,但`cmc crypto encrypt`的`--hash`仅支持SM3与SHA256,无法生成与之匹配的密文;使用cmc加密时请勿配置`hash: sha3-256`。 - Linux shell中口令或密码含`$`时会被截断,需用单引号包裹或`\$`转义,详见[使用cmc加密数据库密码](#使用cmc加密数据库密码)中的说明。 常见的解密失败报错: | 节点启动时的报错 | 原因 | 处理方式 | |:---|:---|:---| | `decrypt dsn password fails: password decrypt error: decrypt password fails: invalid padding, masked dsn: root:****@tcp(...)` | passphrase与生成`ENC(...)`密文时使用的口令不一致 | 用`cmc crypto decrypt`按相同的`-a`/`--hash`参数回验密文能否还原出数据库密码。报错中的DSN已自动脱敏,不会泄露口令 | | `decrypt dsn password fails: password decrypt error: decrypt password fails: AES CBC decryption fails: invalid padding` | alg与生成密文时使用的算法不一致(例如密文由`-a SM4`生成,却配成`alg: aes-cbc`) | 按上文"cmc加密参数与password_encrypt配置的对应关系"调整alg | ## 数据归档&恢复功能 节点在长时间运行后,会积累大量数据,这些数据不常访问,但不能丢弃,因此需要长安链需要提供数据归档功能。具体详见: [9.7 链上数据归档](../tech/数据存储.html#store_archive_restore)) ## 数据重建(rebuild_dbs) 当因为操作不当,恶意篡改等原因,导致状态数据库的数据与区块链历史数据或其他节点不一致,那么就意味着状态数据库损坏,需要利用账本数据进行数据重建。具体详见: [9.8 数据恢复](../tech/数据存储.html#rebuild_dbs))