From 6985792ffe77882774adcd3f1b6d309a5ec19887 Mon Sep 17 00:00:00 2001 From: lucasli <410567249@qq.com> Date: Tue, 4 Jul 2023 10:11:35 +0800 Subject: [PATCH 1/4] add chapter for tx struct (#1713) --- 3.x/zh_CN/docs/sdk/c_sdk/api.md | 473 ++++++++++++++++---------------- 1 file changed, 242 insertions(+), 231 deletions(-) diff --git a/3.x/zh_CN/docs/sdk/c_sdk/api.md b/3.x/zh_CN/docs/sdk/c_sdk/api.md index bbee23b0d..73a5f8ba3 100644 --- a/3.x/zh_CN/docs/sdk/c_sdk/api.md +++ b/3.x/zh_CN/docs/sdk/c_sdk/api.md @@ -14,7 +14,8 @@ - [工具类](../c_sdk/api.html#id4) - [KeyPair](../c_sdk/api.html#keypair) - [ABI编解码](../c_sdk/api.html#abi) - - [交易构造](../c_sdk/api.html#id5) + - [交易构造(不带类型)](../c_sdk/api.html#id5) + - [交易构造(带类型)](../c_sdk/api.html#id6) ## 1. 基础操作 @@ -944,43 +945,7 @@ - 注意: - 返回的字符串需要调用`bcos_sdk_c_free`释放,以免造成内存泄露 -### 6.3 构造签名交易 -- **c-sdk `3.3.0-tx-struct` 特性分支,增加了对交易结构体的支持**。 -即返回值、入参支持交易结构体,结构体如下: -```c -// transaction bytes -struct bcos_sdk_c_bytes -{ - uint8_t* buffer; - uint32_t length; -}; - -// transaction data -struct bcos_sdk_c_transaction_data -{ - int32_t version; - int64_t block_limit; - char* chain_id; - char* group_id; - char* nonce; - char* to; - char* abi; - struct bcos_sdk_c_bytes* input; -}; - -// transaction -struct bcos_sdk_c_transaction -{ - struct bcos_sdk_c_transaction_data* transaction_data; - struct bcos_sdk_c_bytes* data_hash; - struct bcos_sdk_c_bytes* signature; - struct bcos_sdk_c_bytes* sender; - int64_t import_time; - int32_t attribute; - char* extra_data; -}; -``` - +### 6.3 交易构造(不带类型) - `bcos_sdk_get_group_wasm_and_crypto` - 原型: - `void bcos_sdk_get_group_wasm_and_crypto(void* sdk, const char* group_id, int* wasm, int* crypto_type)` @@ -1011,7 +976,9 @@ struct bcos_sdk_c_transaction - `bcos_sdk_create_transaction_data` - 原型: - - `void* bcos_sdk_create_transaction_data(const char* group_id, const char* chain_id, const char* to, const char* data, const char* abi, int64_t block_limit)` + ```cpp + void* bcos_sdk_create_transaction_data(const char* group_id, const char* chain_id, const char* to, const char* data, const char* abi, int64_t block_limit) + ``` - 功能: - 创建`TransactionData`对象,该对象是未签名的交易对象 - 参数: @@ -1027,41 +994,85 @@ struct bcos_sdk_c_transaction - 注意: - `TransactionData`对象需要调用`bcos_sdk_destroy_transaction_data`接口释放,以免造成内存泄露 -- `bcos_sdk_create_transaction_data_struct_with_hex_input` +- `bcos_sdk_calc_transaction_data_hash` - 原型: - - `struct bcos_sdk_c_transaction_data* bcos_sdk_create_transaction_data_struct_with_hex_input(const char* group_id, const char* chain_id, const char* to, const char* input, const char* abi, int64_t block_limit)` + - `const char* bcos_sdk_calc_transaction_data_hash(int crypto_type, void* transaction_data)` - 功能: - - 创建`bcos_sdk_c_transaction_data`交易结构体,该对象结构体是未签名的交易对象 + - 计算`TransactionData`对象哈希 - 参数: - - `group_id`: 群组ID - - `chain_id`: 链ID,可以调用`bcos_sdk_get_group_chain_id`接口获取群组的链ID - - `to`: 调用的合约地址,部署合约时设置为空字符串"" - - `input`: ABI编码后的参数,十六进制c风格字符串,是hex字符串 - - `abi`: 合约的ABI,JSON字符串,可选参数,部署合约时可以将合约的ABI传入,默认传入空字符串"" - - `block_limit`: 区块限制,可以调用`bcos_rpc_get_block_limit`接口获取 + - crypto_type: 类型, ECDSA: BCOS_C_SDK_ECDSA_TYPE(0), SM: BCOS_C_SDK_SM_TYPE(1) + - `transaction_data`: `TransactionData`对象指针 - 返回: - - `bcos_sdk_c_transaction_data`交易结构体指针 + - `TransactionData`对象哈希 - 失败返回`NULL`,使用`bcos_sdk_get_last_error`、 `bcos_sdk_get_last_error_msg`获取错误码和错误描述信息 - 注意: - - `bcos_sdk_c_transaction_data`交易结构体,需要调用`bcos_sdk_destroy_transaction_data_struct`接口释放,以免造成内存泄露 + - **`TransactionData`对象的哈希,也是交易的哈希** + - 返回的字符串需要调用`bcos_sdk_c_free`释放,以免造成内存泄露 -- `bcos_sdk_create_transaction_data_struct_with_bytes` - - 原型:`struct bcos_sdk_c_transaction_data* bcos_sdk_create_transaction_data_struct_with_bytes(const char* group_id, const char* chain_id, const char* to, const unsigned char* bytes_input, uint32_t bytes_input_length, const char* abi, int64_t block_limit)` +- `bcos_sdk_sign_transaction_data_hash` + - 原型: + - `const char* bcos_sdk_sign_transaction_data_hash(void* keypair, const char* transcation_hash)` - 功能: - - 创建`bcos_sdk_c_transaction_data`交易结构体,该对象结构体是未签名的交易对象 + - 交易哈希签名 - 参数: - - `group_id`: 群组ID - - `chain_id`: 链ID,可以调用`bcos_sdk_get_group_chain_id`接口获取群组的链ID - - `to`: 调用的合约地址,部署合约时设置为空字符串"" - - `bytes_input`: ABI编码后的参数,byte的字节数组 - - `bytes_input_length`: byte字节数组的长度 - - `abi`: 合约的ABI,JSON字符串,可选参数,部署合约时可以将合约的ABI传入,默认传入空字符串"" - - `block_limit`: 区块限制,可以调用`bcos_rpc_get_block_limit`接口获取 + - keypair:`KeyPair`对象,参考[`KeyPair`签名对象](../c_sdk/api.html#keypair) + - transcation_hash: 交易哈希,由`bcos_sdk_calc_transaction_data_hash`接口生成 - 返回: - - `bcos_sdk_c_transaction_data`交易结构体指针 - - 失败返回`NULL`,使用`bcos_sdk_get_last_error`、 `bcos_sdk_get_last_error_msg`获取错误码和错误描述信息 + - 交易签名,字符串类型 + - 失败返回`NULL`,调用`bcos_sdk_get_last_error`、 `bcos_sdk_get_last_error_msg`获取错误码和错误描述信息 - 注意: - - `bcos_sdk_c_transaction_data`交易结构体,需要调用`bcos_sdk_destroy_transaction_data_struct`接口释放,以免造成内存泄露 + - 返回的字符串需要调用`bcos_sdk_c_free`释放,以免造成内存泄露 + +- `bcos_sdk_create_signed_transaction_with_signed_data` + - 原型: + + ```cpp + const char* bcos_sdk_create_signed_transaction_with_signed_data(void* transaction_data, const char* signed_transaction_data, const char* transaction_data_hash, int32_t attribute) + ``` + + - 功能: + - 创建签名的交易 + - 参数: + - transaction_data: `TransactionData`对象 + - signed_transaction_data: 交易哈希的签名,十六进制c风格字符串,`bcos_sdk_sign_transaction_data_hash`接口生成 + - transaction_data_hash: 交易哈希,十六进制c风格字符串,`bcos_sdk_calc_transaction_data_hash`接口生成 + - attribute: 交易额外属性,待拓展,默认填0即可 + - 返回: + - 签名的交易,十六进制c风格字符串 + - 失败返回`NULL`,调用`bcos_sdk_get_last_error`、 `bcos_sdk_get_last_error_msg`获取错误码和错误描述信息 + - 注意: + - 返回的字符串需要调用`bcos_sdk_c_free`释放,以免造成内存泄露 + +- `bcos_sdk_create_signed_transaction` + - 原型: + + ```cpp + void bcos_sdk_create_signed_transaction(void* key_pair, const char* group_id, const char* chain_id, const char* to, const char* data, const char* abi, int64_t block_limit, int32_t attribute, char** tx_hash, char** signed_tx) + ``` + + - 功能: + - 创建签名的交易 + - 参数: + - key_pair: `KeyPair`对象,参考[`KeyPair`签名对象](../c_sdk/api.html#keypair) + - group_id: 群组ID + - chain_id: 链ID,可以调用`bcos_sdk_get_group_chain_id`接口获取群组的链ID + - to: 调用的合约地址,部署合约时设置为空字符串"" + - data: ABI编码后的参数,参考[ABI编解码](../c_sdk/api.html#abi) + - abi: 合约的ABI,可选参数,部署合约时可以将合约的ABI传入,默认空字符串"" + - block_limit: 区块限制,可以调用`bcos_rpc_get_block_limit`接口获取 + - attribute: 交易额外属性,待拓展,默认填0即可 + - tx_hash: 返回值,交易哈希,十六进制c风格字符串 + - signed_tx: 返回值,签名的交易,十六进制c风格字符串 + - 返回: + - 调用`bcos_sdk_get_last_error`接口判断是否成功,0表示成功,其他值表示错误码 + - 注意: + - 返回的`tx_hash`、`signed_tx`需要调用`bcos_sdk_c_free`释放,以免造成内存泄露 + - **说明**: + - `bcos_sdk_create_signed_transaction`相当于下面几个接口功能的组合,创建交易、交易哈希、交易签名流程需要分开处理时,使用下面几个接口: + - `bcos_sdk_create_transaction_data`: 创建`TransactionData` + - `bcos_sdk_calc_transaction_data_hash`: 计算交易哈希 + - `bcos_sdk_sign_transaction_data_hash`: 交易哈希签名 + - `bcos_sdk_create_signed_transaction_with_signed_data`: 创建签名的交易 - `bcos_sdk_destroy_transaction_data` - 原型: @@ -1073,78 +1084,143 @@ struct bcos_sdk_c_transaction - 返回: - 无 -- `bcos_sdk_destroy_transaction_data_struct` +- `bcos_sdk_create_transaction_builder_service` - 原型: - - `void bcos_sdk_destroy_transaction_data_struct(struct bcos_sdk_c_transaction_data* transaction_data)` + - `void* bcos_sdk_create_transaction_builder_service(void* sdk, const char* group_id)` - 功能: - - 释放`bcos_sdk_c_transaction_data`交易结构体 + - 创建`TransactionBuilderService`对象,简化构造签名交易的姿势,可以对比`bcos_sdk_create_transaction_data_with_tx_builder_service`与`bcos_sdk_create_transaction_data`接口的差异 - 参数: - - `transaction_data`: `bcos_sdk_c_transaction_data`交易结构体指针 + - sdk: sdk对象指针 + - group_id: 群组ID - 返回: - - 无 + - `TransactionBuilderService`对象指针 + - 失败返回`NULL`,调用`bcos_sdk_get_last_error`、 `bcos_sdk_get_last_error_msg`获取错误码和错误描述信息 + - 注意: + - `TransactionBuilderService`对象需要使用`bcos_sdk_destroy_transaction_builder_service`销毁,以免造成内存泄露 -- `bcos_sdk_encode_transaction_data_struct` +- `bcos_sdk_destroy_transaction_builder_service` - 原型: - - `const char* bcos_sdk_encode_transaction_data_struct(struct bcos_sdk_c_transaction_data* transaction_data)` + - `bcos_sdk_destroy_transaction_builder_service(void* service)` - 功能: - - 将`bcos_sdk_c_transaction_data`交易结构体编码为hex字符串 + - 销毁`TransactionBuilderService`对象 - 参数: - - `transaction_data`: `bcos_sdk_c_transaction_data`交易结构体指针 + - `TransactionBuilderService`对象指针 - 返回: - - `transaction_data`交易结构体编码后的hex字符串 - - 注意: - - 返回的字符串需要调用`bcos_sdk_c_free`释放,以免造成内存泄露 - -- `bcos_sdk_encode_transaction_data_struct_to_json` + - 无 +- `bcos_sdk_create_transaction_data_with_tx_builder_service` - 原型: - - `const char* bcos_sdk_encode_transaction_data_struct_to_json(struct bcos_sdk_c_transaction_data* transaction_data)` + - `void* bcos_sdk_create_transaction_data_with_tx_builder_service(void* tx_builder_service, const char* to, const char* data, const char* abi)` - 功能: - - 将`bcos_sdk_c_transaction_data`交易结构体编码为json字符串 + - 创建`TransactionData`对象 - 参数: - - `transaction_data`: `bcos_sdk_c_transaction_data`交易结构体指针 + - tx_builder_service: `TransactionBuilderService`对象指针 + - to: 调用的合约地址,部署合约时设置为空字符串"" + - data: ABI编码后的参数,参考[ABI编解码](../c_sdk/api.html#abi) + - abi: 合约的ABI,可选参数,部署合约时可以将合约的ABI传入,默认空字符串"" - 返回: - - `transaction_data`交易结构体编码后的json字符串 + - `TransactionData`对象指针 + - 失败返回`NULL`使用`bcos_sdk_get_last_error`、 `bcos_sdk_get_last_error_msg`获取错误码和错误描述信息 - 注意: - - 返回的字符串需要调用`bcos_sdk_c_free`释放,以免造成内存泄露 - -- `bcos_sdk_decode_transaction_data_struct` + - 创建的`TransactionData`对象需要由`bcos_sdk_destroy_transaction_data`接口释放,以免造成内存泄露 + +- `bcos_sdk_create_signed_transaction_with_tx_builder_service` - 原型: - - `struct bcos_sdk_c_transaction_data* bcos_sdk_decode_transaction_data_struct(const char* transaction_data_hex_str)` + + ```cpp + void bcos_sdk_create_signed_transaction_with_tx_builder_service(void*tx_builder_service, void* key_pair, const char*to, const char* data, const char* abi, int32_t attribute, char** tx_hash, char** signed_tx) + ``` + - 功能: - - 将编码后的hex字符串解码为`bcos_sdk_c_transaction_data`交易结构体 + - 创建签名的交易 - 参数: - - `transaction_data_hex_str`: 编码后的hex字符串 - - 返回: - - `bcos_sdk_c_transaction_data`交易结构体指针 + - tx_builder_service: `TransactionBuilderService`对象指针 + - key_pair: `KeyPair`对象,参考[`KeyPair`签名对象](../c_sdk/api.html#keypair) + - to: 调用的合约地址,部署合约时设置为空字符串"" + - data: ABI编码后的参数,参考[ABI编解码](../c_sdk/api.html#abi) + - abi: 合约的ABI,可选参数,部署合约时可以将合约的ABI传入,默认空字符串"" + - attribute: 交易额外属性,待拓展,默认填0即可 + - tx_hash: 返回值,交易哈希,十六进制c风格字符串 + - signed_tx: 返回值,签名的交易,十六进制c风格字符串 - 注意: - - `bcos_sdk_c_transaction_data`交易结构体,需要调用`bcos_sdk_destroy_transaction_data_struct`接口释放,以免造成内存泄露 + - 返回的`tx_hash`、`signed_tx`需要调用`bcos_sdk_c_free`释放,以免造成内存泄露 -- `bcos_sdk_decode_transaction_data_struct_with_json` +### 6.4 交易构造(带类型) +- **c-sdk `3.3.0-tx-struct` 特性分支,增加了对交易结构体的支持**。 +即返回值、入参支持交易结构体,结构体如下: +```c +// transaction bytes +struct bcos_sdk_c_bytes +{ + uint8_t* buffer; + uint32_t length; +}; + +// transaction data +struct bcos_sdk_c_transaction_data +{ + int32_t version; + int64_t block_limit; + char* chain_id; + char* group_id; + char* nonce; + char* to; + char* abi; + struct bcos_sdk_c_bytes* input; +}; + +// transaction +struct bcos_sdk_c_transaction +{ + struct bcos_sdk_c_transaction_data* transaction_data; + struct bcos_sdk_c_bytes* data_hash; + struct bcos_sdk_c_bytes* signature; + struct bcos_sdk_c_bytes* sender; + int64_t import_time; + int32_t attribute; + char* extra_data; +}; +``` + +- `bcos_sdk_create_transaction_data_struct_with_hex_input` - 原型: - - `struct bcos_sdk_c_transaction_data* bcos_sdk_decode_transaction_data_struct_with_json(const char* transaction_data_json_str)` + ```cpp + struct bcos_sdk_c_transaction_data* bcos_sdk_create_transaction_data_struct_with_hex_input(const char* group_id, const char* chain_id, const char* to, const char* input, const char* abi, int64_t block_limit) + ``` - 功能: - - 将编码后的json字符串解码为`bcos_sdk_c_transaction_data`交易结构体 + - 创建`bcos_sdk_c_transaction_data`交易结构体,该对象结构体是未签名的交易对象 - 参数: - - `transaction_data_json_str`: 编码后的json字符串 + - `group_id`: 群组ID + - `chain_id`: 链ID,可以调用`bcos_sdk_get_group_chain_id`接口获取群组的链ID + - `to`: 调用的合约地址,部署合约时设置为空字符串"" + - `input`: ABI编码后的参数,十六进制c风格字符串,是hex字符串 + - `abi`: 合约的ABI,JSON字符串,可选参数,部署合约时可以将合约的ABI传入,默认传入空字符串"" + - `block_limit`: 区块限制,可以调用`bcos_rpc_get_block_limit`接口获取 - 返回: - `bcos_sdk_c_transaction_data`交易结构体指针 + - 失败返回`NULL`,使用`bcos_sdk_get_last_error`、 `bcos_sdk_get_last_error_msg`获取错误码和错误描述信息 - 注意: - `bcos_sdk_c_transaction_data`交易结构体,需要调用`bcos_sdk_destroy_transaction_data_struct`接口释放,以免造成内存泄露 -- `bcos_sdk_calc_transaction_data_hash` +- `bcos_sdk_create_transaction_data_struct_with_bytes` - 原型: - - `const char* bcos_sdk_calc_transaction_data_hash(int crypto_type, void* transaction_data)` + ```cpp + struct bcos_sdk_c_transaction_data* bcos_sdk_create_transaction_data_struct_with_bytes(const char* group_id, const char* chain_id, const char* to, const unsigned char* bytes_input, uint32_t bytes_input_length, const char* abi, int64_t block_limit) + ``` - 功能: - - 计算`TransactionData`对象哈希 + - 创建`bcos_sdk_c_transaction_data`交易结构体,该对象结构体是未签名的交易对象 - 参数: - - crypto_type: 类型, ECDSA: BCOS_C_SDK_ECDSA_TYPE(0), SM: BCOS_C_SDK_SM_TYPE(1) - - `transaction_data`: `TransactionData`对象指针 + - `group_id`: 群组ID + - `chain_id`: 链ID,可以调用`bcos_sdk_get_group_chain_id`接口获取群组的链ID + - `to`: 调用的合约地址,部署合约时设置为空字符串"" + - `bytes_input`: ABI编码后的参数,byte的字节数组 + - `bytes_input_length`: byte字节数组的长度 + - `abi`: 合约的ABI,JSON字符串,可选参数,部署合约时可以将合约的ABI传入,默认传入空字符串"" + - `block_limit`: 区块限制,可以调用`bcos_rpc_get_block_limit`接口获取 - 返回: - - `TransactionData`对象哈希 + - `bcos_sdk_c_transaction_data`交易结构体指针 - 失败返回`NULL`,使用`bcos_sdk_get_last_error`、 `bcos_sdk_get_last_error_msg`获取错误码和错误描述信息 - 注意: - - **`TransactionData`对象的哈希,也是交易的哈希** - - 返回的字符串需要调用`bcos_sdk_c_free`释放,以免造成内存泄露 + - `bcos_sdk_c_transaction_data`交易结构体,需要调用`bcos_sdk_destroy_transaction_data_struct`接口释放,以免造成内存泄露 - `bcos_sdk_calc_transaction_data_struct_hash` - 原型: @@ -1161,49 +1237,38 @@ struct bcos_sdk_c_transaction - **`bcos_sdk_c_transaction_data`交易结构体的哈希,也是交易的哈希** - 返回的字符串需要调用`bcos_sdk_c_free`释放,以免造成内存泄露 -- `bcos_sdk_sign_transaction_data_hash` - - 原型: - - `const char* bcos_sdk_sign_transaction_data_hash(void* keypair, const char* transcation_hash)` - - 功能: - - 交易哈希签名 - - 参数: - - keypair:`KeyPair`对象,参考[`KeyPair`签名对象](../c_sdk/api.html#keypair) - - transcation_hash: 交易哈希,由`bcos_sdk_calc_transaction_data_hash`接口生成 - - 返回: - - 交易签名,字符串类型 - - 失败返回`NULL`,调用`bcos_sdk_get_last_error`、 `bcos_sdk_get_last_error_msg`获取错误码和错误描述信息 - - 注意: - - 返回的字符串需要调用`bcos_sdk_c_free`释放,以免造成内存泄露 - -- `bcos_sdk_create_signed_transaction_with_signed_data` +- `bcos_sdk_create_transaction_struct` - 原型: - ```shell - const char* bcos_sdk_create_signed_transaction_with_signed_data(void* transaction_data, const char* signed_transaction_data, const char* transaction_data_hash, int32_t attribute) + ```cpp + struct bcos_sdk_c_transaction* bcos_sdk_create_transaction_struct(struct bcos_sdk_c_transaction_data* transaction_data, const char* signature, const char* transaction_data_hash, int32_t attribute, const char* extra_data) ``` - 功能: - - 创建签名的交易 + - 创建签名的交易结构体 - 参数: - - transaction_data: `TransactionData`对象 - - signed_transaction_data: 交易哈希的签名,十六进制c风格字符串,`bcos_sdk_sign_transaction_data_hash`接口生成 - - transaction_data_hash: 交易哈希,十六进制c风格字符串,`bcos_sdk_calc_transaction_data_hash`接口生成 + - transaction_data: `bcos_sdk_c_transaction_data`交易结构体 + - signature: 交易结构体哈希的签名,十六进制c风格字符串,`bcos_sdk_sign_transaction_data_hash`接口生成 + - transaction_data_hash: 交易结构体哈希,十六进制c风格字符串,`bcos_sdk_calc_transaction_data_struct_hash`接口生成 - attribute: 交易额外属性,待拓展,默认填0即可 + - extra_data: 交易额外数据,填""空字符串即可 - 返回: - - 签名的交易,十六进制c风格字符串 + - `bcos_sdk_c_transaction`签名的交易结构体指针 - 失败返回`NULL`,调用`bcos_sdk_get_last_error`、 `bcos_sdk_get_last_error_msg`获取错误码和错误描述信息 - 注意: - - 返回的字符串需要调用`bcos_sdk_c_free`释放,以免造成内存泄露 + - `bcos_sdk_c_transaction`签名的交易结构体,需要调用`bcos_sdk_destroy_transaction_struct`接口释放,以免造成内存泄露 -- `bcos_sdk_create_transaction_struct` +- `bcos_sdk_create_encoded_transaction` - 原型: - ```shell - struct bcos_sdk_c_transaction* bcos_sdk_create_transaction_struct(struct bcos_sdk_c_transaction_data* transaction_data, const char* signature, const char* transaction_data_hash, int32_t attribute, const char* extra_data) + ```cpp + const char* bcos_sdk_create_encoded_transaction( + struct bcos_sdk_c_transaction_data* transaction_data, const char* signature, + const char* transaction_data_hash, int32_t attribute, const char* extra_data) ``` - 功能: - - 创建签名的交易结构体 + - 创建签名的交易字符串 - 参数: - transaction_data: `bcos_sdk_c_transaction_data`交易结构体 - signature: 交易结构体哈希的签名,十六进制c风格字符串,`bcos_sdk_sign_transaction_data_hash`接口生成 @@ -1211,74 +1276,68 @@ struct bcos_sdk_c_transaction - attribute: 交易额外属性,待拓展,默认填0即可 - extra_data: 交易额外数据,填""空字符串即可 - 返回: - - `bcos_sdk_c_transaction`签名的交易结构体指针 + - 签名的交易字符串 - 失败返回`NULL`,调用`bcos_sdk_get_last_error`、 `bcos_sdk_get_last_error_msg`获取错误码和错误描述信息 - 注意: - - `bcos_sdk_c_transaction`签名的交易结构体,需要调用`bcos_sdk_destroy_transaction_struct`接口释放,以免造成内存泄露 + - 返回的签名交易字符串,需要调用`bcos_sdk_c_free`释放,以免造成内存泄露 -- `bcos_sdk_destroy_transaction_struct` +- `bcos_sdk_encode_transaction_data_struct` - 原型: - - `void bcos_sdk_destroy_transaction_struct(struct bcos_sdk_c_transaction* transaction)` + - `const char* bcos_sdk_encode_transaction_data_struct(struct bcos_sdk_c_transaction_data* transaction_data)` - 功能: - - 释放`bcos_sdk_c_transaction`签名的交易结构体 + - 将`bcos_sdk_c_transaction_data`交易结构体编码为hex字符串 - 参数: - - `transaction_data`: `bcos_sdk_c_transaction`签名的交易结构体指针 + - `transaction_data`: `bcos_sdk_c_transaction_data`交易结构体指针 - 返回: - - 无 + - `transaction_data`交易结构体编码后的hex字符串 + - 注意: + - 返回的字符串需要调用`bcos_sdk_c_free`释放,以免造成内存泄露 -- `bcos_sdk_create_signed_transaction` +- `bcos_sdk_encode_transaction_data_struct_to_json` - 原型: - - ```shell - void bcos_sdk_create_signed_transaction(void* key_pair, const char* group_id, const char* chain_id, const char* to, const char* data, const char* abi, int64_t block_limit, int32_t attribute, char** tx_hash, char** signed_tx) - ``` - + - `const char* bcos_sdk_encode_transaction_data_struct_to_json(struct bcos_sdk_c_transaction_data* transaction_data)` - 功能: - - 创建签名的交易 + - 将`bcos_sdk_c_transaction_data`交易结构体编码为json字符串 - 参数: - - key_pair: `KeyPair`对象,参考[`KeyPair`签名对象](../c_sdk/api.html#keypair) - - group_id: 群组ID - - chain_id: 链ID,可以调用`bcos_sdk_get_group_chain_id`接口获取群组的链ID - - to: 调用的合约地址,部署合约时设置为空字符串"" - - data: ABI编码后的参数,参考[ABI编解码](../c_sdk/api.html#abi) - - abi: 合约的ABI,可选参数,部署合约时可以将合约的ABI传入,默认空字符串"" - - block_limit: 区块限制,可以调用`bcos_rpc_get_block_limit`接口获取 - - attribute: 交易额外属性,待拓展,默认填0即可 - - tx_hash: 返回值,交易哈希,十六进制c风格字符串 - - signed_tx: 返回值,签名的交易,十六进制c风格字符串 + - `transaction_data`: `bcos_sdk_c_transaction_data`交易结构体指针 - 返回: - - 调用`bcos_sdk_get_last_error`接口判断是否成功,0表示成功,其他值表示错误码 + - `transaction_data`交易结构体编码后的json字符串 - 注意: - - 返回的`tx_hash`、`signed_tx`需要调用`bcos_sdk_c_free`释放,以免造成内存泄露 - - **说明**: - - `bcos_sdk_create_signed_transaction`相当于下面几个接口功能的组合,创建交易、交易哈希、交易签名流程需要分开处理时,使用下面几个接口: - - `bcos_sdk_create_transaction_data`: 创建`TransactionData` - - `bcos_sdk_calc_transaction_data_hash`: 计算交易哈希 - - `bcos_sdk_sign_transaction_data_hash`: 交易哈希签名 - - `bcos_sdk_create_signed_transaction_with_signed_data`: 创建签名的交易 - -- `bcos_sdk_create_encoded_transaction` + - 返回的字符串需要调用`bcos_sdk_c_free`释放,以免造成内存泄露 + +- `bcos_sdk_decode_transaction_data_struct` - 原型: + - `struct bcos_sdk_c_transaction_data* bcos_sdk_decode_transaction_data_struct(const char* transaction_data_hex_str)` + - 功能: + - 将编码后的hex字符串解码为`bcos_sdk_c_transaction_data`交易结构体 + - 参数: + - `transaction_data_hex_str`: 编码后的hex字符串 + - 返回: + - `bcos_sdk_c_transaction_data`交易结构体指针 + - 注意: + - `bcos_sdk_c_transaction_data`交易结构体,需要调用`bcos_sdk_destroy_transaction_data_struct`接口释放,以免造成内存泄露 - ```shell - const char* bcos_sdk_create_encoded_transaction( - struct bcos_sdk_c_transaction_data* transaction_data, const char* signature, - const char* transaction_data_hash, int32_t attribute, const char* extra_data) - ``` - +- `bcos_sdk_decode_transaction_data_struct_with_json` + - 原型: + - `struct bcos_sdk_c_transaction_data* bcos_sdk_decode_transaction_data_struct_with_json(const char* transaction_data_json_str)` - 功能: - - 创建签名的交易字符串 + - 将编码后的json字符串解码为`bcos_sdk_c_transaction_data`交易结构体 - 参数: - - transaction_data: `bcos_sdk_c_transaction_data`交易结构体 - - signature: 交易结构体哈希的签名,十六进制c风格字符串,`bcos_sdk_sign_transaction_data_hash`接口生成 - - transaction_data_hash: 交易结构体哈希,十六进制c风格字符串,`bcos_sdk_calc_transaction_data_struct_hash`接口生成 - - attribute: 交易额外属性,待拓展,默认填0即可 - - extra_data: 交易额外数据,填""空字符串即可 + - `transaction_data_json_str`: 编码后的json字符串 - 返回: - - 签名的交易字符串 - - 失败返回`NULL`,调用`bcos_sdk_get_last_error`、 `bcos_sdk_get_last_error_msg`获取错误码和错误描述信息 + - `bcos_sdk_c_transaction_data`交易结构体指针 - 注意: - - 返回的签名交易字符串,需要调用`bcos_sdk_c_free`释放,以免造成内存泄露 + - `bcos_sdk_c_transaction_data`交易结构体,需要调用`bcos_sdk_destroy_transaction_data_struct`接口释放,以免造成内存泄露 + +- `bcos_sdk_destroy_transaction_data_struct` + - 原型: + - `void bcos_sdk_destroy_transaction_data_struct(struct bcos_sdk_c_transaction_data* transaction_data)` + - 功能: + - 释放`bcos_sdk_c_transaction_data`交易结构体 + - 参数: + - `transaction_data`: `bcos_sdk_c_transaction_data`交易结构体指针 + - 返回: + - 无 - `bcos_sdk_encode_transaction_struct` - 原型: @@ -1328,60 +1387,12 @@ struct bcos_sdk_c_transaction - 注意: - `bcos_sdk_c_transaction`签名的交易结构体,需要调用`bcos_sdk_destroy_transaction_struct`接口释放,以免造成内存泄露 -- `bcos_sdk_create_transaction_builder_service` - - 原型: - - `void* bcos_sdk_create_transaction_builder_service(void* sdk, const char* group_id)` - - 功能: - - 创建`TransactionBuilderService`对象,简化构造签名交易的姿势,可以对比`bcos_sdk_create_transaction_data_with_tx_builder_service`与`bcos_sdk_create_transaction_data`接口的差异 - - 参数: - - sdk: sdk对象指针 - - group_id: 群组ID - - 返回: - - `TransactionBuilderService`对象指针 - - 失败返回`NULL`,调用`bcos_sdk_get_last_error`、 `bcos_sdk_get_last_error_msg`获取错误码和错误描述信息 - - 注意: - - `TransactionBuilderService`对象需要使用`bcos_sdk_destroy_transaction_builder_service`销毁,以免造成内存泄露 -- `bcos_sdk_destroy_transaction_builder_service` - - 原型: - - `bcos_sdk_destroy_transaction_builder_service(void* service)` - - 功能: - - 销毁`TransactionBuilderService`对象 - - 参数: - - `TransactionBuilderService`对象指针 - - 返回: - - 无 -- `bcos_sdk_create_transaction_data_with_tx_builder_service` +- `bcos_sdk_destroy_transaction_struct` - 原型: - - `void* bcos_sdk_create_transaction_data_with_tx_builder_service(void* tx_builder_service, const char* to, const char* data, const char* abi)` + - `void bcos_sdk_destroy_transaction_struct(struct bcos_sdk_c_transaction* transaction)` - 功能: - - 创建`TransactionData`对象 + - 释放`bcos_sdk_c_transaction`签名的交易结构体 - 参数: - - tx_builder_service: `TransactionBuilderService`对象指针 - - to: 调用的合约地址,部署合约时设置为空字符串"" - - data: ABI编码后的参数,参考[ABI编解码](../c_sdk/api.html#abi) - - abi: 合约的ABI,可选参数,部署合约时可以将合约的ABI传入,默认空字符串"" + - `transaction_data`: `bcos_sdk_c_transaction`签名的交易结构体指针 - 返回: - - `TransactionData`对象指针 - - 失败返回`NULL`使用`bcos_sdk_get_last_error`、 `bcos_sdk_get_last_error_msg`获取错误码和错误描述信息 - - 注意: - - 创建的`TransactionData`对象需要由`bcos_sdk_destroy_transaction_data`接口释放,以免造成内存泄露 -- `bcos_sdk_create_signed_transaction_with_tx_builder_service` - - 原型: - - ```shell - void bcos_sdk_create_signed_transaction_with_tx_builder_service(void*tx_builder_service, void* key_pair, const char*to, const char* data, const char* abi, int32_t attribute, char** tx_hash, char** signed_tx) - ``` - - - 功能: - - 创建签名的交易 - - 参数: - - tx_builder_service: `TransactionBuilderService`对象指针 - - key_pair: `KeyPair`对象,参考[`KeyPair`签名对象](../c_sdk/api.html#keypair) - - to: 调用的合约地址,部署合约时设置为空字符串"" - - data: ABI编码后的参数,参考[ABI编解码](../c_sdk/api.html#abi) - - abi: 合约的ABI,可选参数,部署合约时可以将合约的ABI传入,默认空字符串"" - - attribute: 交易额外属性,待拓展,默认填0即可 - - tx_hash: 返回值,交易哈希,十六进制c风格字符串 - - signed_tx: 返回值,签名的交易,十六进制c风格字符串 - - 注意: - - 返回的`tx_hash`、`signed_tx`需要调用`bcos_sdk_c_free`释放,以免造成内存泄露 + - 无 \ No newline at end of file From 7bcc81f0bccd2e09473f19690555c06bd1ecc2ad Mon Sep 17 00:00:00 2001 From: jimmyshi <417711026@qq.com> Date: Tue, 4 Jul 2023 09:28:39 +0800 Subject: [PATCH 2/4] Add 3.2.2 description (#1714) --- 2.x/docs/compatibility.md | 4 +- .../docs/introduction/change_log/3_2_2.md | 86 +++++++++++++++++++ .../docs/introduction/change_log/index.rst | 1 + 3 files changed, 89 insertions(+), 2 deletions(-) create mode 100644 3.x/zh_CN/docs/introduction/change_log/3_2_2.md diff --git a/2.x/docs/compatibility.md b/2.x/docs/compatibility.md index d38d1eb31..a048da347 100644 --- a/2.x/docs/compatibility.md +++ b/2.x/docs/compatibility.md @@ -278,11 +278,11 @@ FISCO-BCOS 3.0.0 及之后的版本之间相互兼容。FISCO-BCOS 3.0.0-rc的 | WeBankBlockchain-Governance-Authority | 权限治理组件 | V3 | [文档](https://governance-doc.readthedocs.io/zh_CN/v3.0.0/docs/WeBankBlockchain-Governance-Acct/index.html) | [github](https://github.com/WeBankBlockchain/Governance-Account/tree/V3) | [gitee](https://gitee.com/WeBankBlockchain/Governance-Account/tree/V3/) | | | WeBankBlockchain-Governance-Account | 账户治理组件 | V3 | [文档](https://governance-doc.readthedocs.io/zh_CN/v3.0.0/docs/WeBankBlockchain-Governance-Auth/index.html) | [github](https://github.com/WeBankBlockchain/Governance-Authority/tree/V3) | [gitee](https://gitee.com/WeBankBlockchain/Governance-Authority/tree/V3/) | | -#### 3.2.7. FISCO-BCOS v3.2.1 +#### 3.2.7. FISCO-BCOS v3.2.x | 项目 | 功能简介 | 版本 | 文档 | github | gitee | 备注 | | ------------------------------------- | -------------------------- | ------------------------- | ------------------------------------------------------------ | ------------------------------------------------------------ | ------------------------------------------------------------ | ---------------------------- | -| FISCO-BCOS | 区块链底层平台 | v3.2.1 | [文档](https://fisco-bcos-doc.readthedocs.io/zh_CN/latest/) | [github](https://github.com/FISCO-BCOS/FISCO-BCOS/tree/v3.2.0) | [gitee](https://gitee.com/FISCO-BCOS/FISCO-BCOS/tree/v3.2.0) | | +| FISCO-BCOS | 区块链底层平台 | v3.2.x | [文档](https://fisco-bcos-doc.readthedocs.io/zh_CN/latest/) | [github](https://github.com/FISCO-BCOS/FISCO-BCOS/tree/v3.2.0) | [gitee](https://gitee.com/FISCO-BCOS/FISCO-BCOS/tree/v3.2.0) | | | Solidity | solidity智能合约 | v0.8.11 | [文档](https://docs.soliditylang.org/en/v0.8.11) | | | **最高支持solidity v0.8.11** | | JavaSDK | Java语言SDK | v3.2.x | [文档](https://fisco-bcos-doc.readthedocs.io/zh_CN/latest/docs/develop/sdk/java_sdk/index.html) | [github](https://github.com/FISCO-BCOS/java-sdk/tree/v3.2.0) | [gitee](https://gitee.com/FISCO-BCOS/java-sdk/tree/v3.2.0) | | | GoSDK | Go语言SDK | 暂不支持
后续版本规划 | | | | | diff --git a/3.x/zh_CN/docs/introduction/change_log/3_2_2.md b/3.x/zh_CN/docs/introduction/change_log/3_2_2.md new file mode 100644 index 000000000..03ece5482 --- /dev/null +++ b/3.x/zh_CN/docs/introduction/change_log/3_2_2.md @@ -0,0 +1,86 @@ +# v3.2.2 + +#### 修复 + +* 新增交易同步模式开关,支持3.1.x灰度升级至3.2.2版本([#3678](https://github.com/FISCO-BCOS/FISCO-BCOS/pull/3678)) + +#### 修复 + +* 修复交易同步在极端场景下P2P比RPC写交易池快的问题([#3683](https://github.com/FISCO-BCOS/FISCO-BCOS/pull/3683)) +* 调整scheduler模块中的日志等级([#3707](https://github.com/FISCO-BCOS/FISCO-BCOS/pull/3707)) + +#### 升级描述 + +* 升级节点可执行程序 + + 效果:修复bug,并带来稳定性、性能的提升 + + 操作:停止节点服务,升级节点可执行程序为当前版本,重启节点服务 + + 注意事项:推荐逐步替换可执行程序进行灰度升级 + + 支持升级的版本:v3.0.0+ + +* 升级链数据版本 + + 效果:可使用当前版本的最新特性 + + 操作:先完成升级所有节点可执行程序,再参考[文档](https://fisco-bcos-doc.readthedocs.io/zh_CN/latest/docs/introduction/change_log/3_2_1.html#id5)发送交易升级链数据版本至 v3.2.2 + + 注意事项:务必备份原节点的所有账本数据,若操作失误造成升级失败,可通过原数据回滚到升级前的状态 + 支持升级的版本:v3.0.0+ + +* 组件兼容性 + + 请查阅[完整描述](https://fisco-bcos-documentation.readthedocs.io/zh_CN/latest/docs/compatibility.html#fisco-bcos-v3-2-2) + +| | 推荐版本 | 最低版本 | 说明 | +| ---------- | ----------- | ------------------------ | ---------------------------------- | +| WeBASE | 3.0.2 | 3.0.2 | | +| WeIdentity | v3.0.0-rc.1 | v3.0.0-rc.1 | | +| Console | 3.2.0 | 3.0.0 | | +| Java SDK | 3.2.2 | 3.0.0 | | +| CPP SDK | 3.2.2 | 3.0.0 | | +| Solidity | 0.8.11 | 最低 0.4.25,最高 0.8.11 | 需根据合约版本下载编译器(控制台) | +| WBC-Liquid | 1.0.0-rc3 | 1.0.0-rc3 | | + +#### 升级方法 + +该操作仅支持将3.x版本升级为本版本,不支持3.0-rc或2.x的升级。 + +##### 查询数据兼容版本号(compatibility_version) + +用[控制台](https://fisco-bcos-doc.readthedocs.io/zh_CN/latest/docs/operation_and_maintenance/console/console_commands.html#getsystemconfigbykey)进行查询,如当前返回的版本为3.0.0 + +``` +[group0]: /apps> getSystemConfigByKey compatibility_version +3.0.0 +``` + +##### 替换节点二进制 + +需将**所有节点**的二进制逐步替换为当前版本。为了不影响业务,替换过程能够以灰度方式进行,逐个替换并重启节点。替换过程中,当前的链仍然会以旧的数据兼容版本号的逻辑继续执行。当所有节点二进制替换完成并重启后,需用控制台修改数据兼容版本号为当前版本。 + +##### 设置数据兼容版本号(compatibility_version) + +用[控制台](https://fisco-bcos-doc.readthedocs.io/zh_CN/latest/docs/operation_and_maintenance/console/console_commands.html#setsystemconfigbykey)设置数据兼容版本号,如当前版本为3.2.0。 + +``` +[group0]: /apps> setSystemConfigByKey compatibility_version 3.2.0 +{ + "code":0, + "msg":"success" +} + +注:若开启权限治理功能,需要使用 setSysConfigProposal 命令 +``` + +设置成功,再次查询,得到当前版本已升级为3.2.0 + +``` +[group0]: /apps> getSystemConfigByKey compatibility_version +3.2.0 +``` + +当前链已经完成升级,至此,**链开始以新的逻辑继续运行**,并支持了新的特性。 + diff --git a/3.x/zh_CN/docs/introduction/change_log/index.rst b/3.x/zh_CN/docs/introduction/change_log/index.rst index aac356290..0948295c0 100644 --- a/3.x/zh_CN/docs/introduction/change_log/index.rst +++ b/3.x/zh_CN/docs/introduction/change_log/index.rst @@ -84,6 +84,7 @@ v3.2.x :hidden: :maxdepth: 0 + 3_2_2.md 3_2_1.md 3_2_0.md From f7978228e9e5a9adfc70aecec6be22daacc366ca Mon Sep 17 00:00:00 2001 From: Kyon <32325790+kyonRay@users.noreply.github.com> Date: Wed, 5 Jul 2023 14:44:37 +0800 Subject: [PATCH 3/4] (develop): add contract_safty_practice. (#1715) --- .../docs/develop/contract_safty_practice.md | 815 ++++++++++++++++++ 3.x/zh_CN/docs/develop/index.md | 3 +- 3.x/zh_CN/index.rst | 1 + 3 files changed, 818 insertions(+), 1 deletion(-) create mode 100644 3.x/zh_CN/docs/develop/contract_safty_practice.md diff --git a/3.x/zh_CN/docs/develop/contract_safty_practice.md b/3.x/zh_CN/docs/develop/contract_safty_practice.md new file mode 100644 index 000000000..4d390c0ea --- /dev/null +++ b/3.x/zh_CN/docs/develop/contract_safty_practice.md @@ -0,0 +1,815 @@ +# 8. 智能合约安全实践 + +智能合约安全是指在设计、编码、部署、运行和维护智能合约的全生命周期中,采取措施确保合约的安全性和可靠性,防止恶意攻击、漏洞利用或错误操作导致的资产损失或系统崩溃。 + +本文从设计模式出发详细讲述智能合约在各个阶段应该使用的策略、推荐实践方式以及安全保障措施。 + +## 1. 智能合约设计模式 + +[智能合约编写之 Solidity的设计模式](https://fisco-bcos-documentation.readthedocs.io/zh_CN/latest/docs/articles/3_features/35_contract/solidity_design_patterns.html) + +作者:储雨知|FISCO BCOS 核心开发者 + +随着区块链技术发展,越来越多的企业与个人开始将区块链与自身业务相结合。区块链所具有的独特优势,例如,数据公开透明、不可篡改,可以为业务带来便利。但与此同时,也存在一些隐患。数据的公开透明,意味着任何人都可以读取;不可篡改,意味着信息一旦上链就无法删除,甚至合约代码都无法被更改。除此之外,合约的公开性、回调机制,每一个特点都可被利用,作为攻击手法,稍有不慎,轻则合约形同虚设,重则要面临企业机密泄露的风险。所以,在业务合约上链前,需要预先对合约的安全性、可维护性等方面作充分考虑。幸运的是,通过近些年Solidity语言的大量实践,开发者们不断提炼和总结,已经形成了一些"设计模式",来指导应对日常开发常见的问题。 + +2019年,IEEE收录了维也纳大学一篇题为《Design Patterns For Smart Contracts In the Ethereum Ecosystem》的论文。这篇论文分析了那些火热的Solidity开源项目,结合以往的研究成果,整理出了18种设计模式。这些设计模式涵盖了安全性、可维护性、生命周期管理、鉴权等多个方面。 + +| 类型 | 模式 | +|--------------------|---------------------------------------------------------------------------------------------------------------------------| +| Security | 1. Checks-Effects-Interaction
2. Emergency Stop
3. Speed Bump
4. Rate Limit
5. Mutex
6. Balance Limit | +| Maintenance | 7. Data Segregation
8. Satellite
9. Contract Register
10. Contract Relay | +| Lifecycle | 11. Mortal
12. Automatic Deprecation | +| Authorization | 13. Ownership
14. Access Restriction | +| Action And Control | 15. Pull Payment
16. Commit And Reveal
17. State Machine
18. Oracle | + +接下来,本文将从这18种设计模式中选择最为通用常见的进行介绍,这些设计模式在实际开发经历中得到了大量检验。 + +### 1.1 Checks-Effects-Interaction - 保证状态完整,再做外部调用 + +该模式是编码风格约束,可有效避免重放攻击。通常情况下,一个函数可能包含三个部分: + +- Checks:参数验证 +- Effects:修改合约状态 +- Interaction:外部交互 + +这个模式要求合约按照Checks-Effects-Interaction的顺序来组织代码。它的好处在于进行外部调用之前,Checks-Effects已完成合约自身状态所有相关工作,使得状态完整、逻辑自洽,这样外部调用就无法利用不完整的状态进行攻击了。回顾前文的AddService合约,并没有遵循这个规则,在自身状态没有更新完的情况下去调用了外部代码,外部代码自然可以横插一刀,让_adders[msg.sender]=true永久不被调用,从而使require语句失效。我们以checks-effects-interaction的角度审阅原来的代码: + +```solidity + //Checks + require(_adders[msg.sender] == false, "You have added already"); + //Effects + _count++; + //Interaction + AdderInterface adder = AdderInterface(msg.sender); + adder.notify(); + //Effects + _adders[msg.sender] = true; +``` + +只要稍微调整顺序,满足Checks-Effects-Interaction模式,悲剧就得以避免: + +```solidity + //Checks + require(_adders[msg.sender] == false, "You have added already"); + //Effects + _count++; + _adders[msg.sender] = true; + //Interaction + AdderInterface adder = AdderInterface(msg.sender); + adder.notify(); +``` + +由于_adders映射已经修改完毕,当恶意攻击者想递归地调用addByOne,require这道防线就会起到作用,将恶意调用拦截在外。虽然该模式并非解决重入攻击的唯一方式,但依然推荐开发者遵循。 + +### 1.2 Mutex - 禁止递归 + +Mutex模式也是解决重入攻击的有效方式。它通过提供一个简单的修饰符来防止函数被递归调用: + +```solidity +contract Mutex { + bool locked; + modifier noReentrancy() { + //防止递归 + require(!locked, "Reentrancy detected"); + locked = true; + _; + locked = false; + } + + //调用该函数将会抛出Reentrancy detected错误 + function some() public noReentrancy{ + some(); + } +} +``` + +在这个例子中,调用some函数前会先运行noReentrancy修饰符,将locked变量赋值为true。如果此时又递归地调用了some,修饰符的逻辑会再次激活,由于此时的locked属性已为true,修饰符的第一行代码会抛出错误。 + +### 1.3 Data segregation - 数据与逻辑相分离 + +了解该设计模式之前,先看看下面这个合约代码: + +```solidity +contract Computer{ + + uint private _data; + + function setData(uint data) public { + _data = data; + } + + function compute() public view returns(uint){ + return _data * 10; + } +} +``` + +此合约包含两个能力,一个是存储数据(setData函数),另一个是运用数据进行计算(compute函数)。如果合约部署一段时间后,发现compute写错了,比如不应是乘以10,而要乘以20,就会引出前文如何升级合约的问题。这时,可以部署一个新合约,并尝试将已有数据迁移到新的合约上,但这是一个很重的操作,一方面要编写迁移工具的代码,另一方面原先的数据完全作废,空占着宝贵的节点存储资源。 + +所以,预先在编程时进行模块化十分必要。如果我们将"数据"看成不变的事物,将"逻辑"看成可能改变的事物,就可以完美避开上述问题。Data Segregation(意为数据分离)模式很好地实现了这一想法。该模式要求一个业务合约和一个数据合约:数据合约只管数据存取,这部分是稳定的;而业务合约则通过数据合约来完成逻辑操作。 + +结合前面的例子,我们将数据读写操作专门转移到一个合约DataRepository中: + +```solidity +contract DataRepository{ + + uint private _data; + + function setData(uint data) public { + _data = data; + } + + function getData() public view returns(uint){ + return _data; + } +} +``` + +计算功能被单独放入一个业务合约中: + +```solidity +contract Computer{ + DataRepository private _dataRepository; + constructor(address addr){ + _dataRepository =DataRepository(addr); + } + + //业务代码 + function compute() public view returns(uint){ + return _dataRepository.getData() * 10; + } +} +``` + +这样,只要数据合约是稳定的,业务合约的升级就很轻量化了。比如,当我要把Computer换成ComputerV2时,原先的数据依然可以被复用。 + +### 1.4 Satellite - 分解合约功能 + +一个复杂的合约通常由许多功能构成,如果这些功能全部耦合在一个合约中,当某一个功能需要更新时,就不得不去部署整个合约,正常的功能都会受到波及。Satellite模式运用单一职责原则解决上述问题,提倡将合约子功能放到子合约里,每个子合约(也称为卫星合约)只对应一个功能。当某个子功能需要修改,只要创建新的子合约,并将其地址更新到主合约里即可,其余功能不受影响。 + +举个简单的例子,下面这个合约的setVariable功能是将输入数据进行计算(compute函数),并将计算结果存入合约状态_variable: + +```solidity +contract Base { + uint public _variable; + + function setVariable(uint data) public { + _variable = compute(data); + } + + //计算 + function compute(uint a) internal returns(uint){ + return a * 10; + } +} +``` + +如果部署后,发现compute函数写错,希望乘以的系数是20,就要重新部署整个合约。但如果一开始按照Satellite模式操作,则只需部署相应的子合约。 + +首先,我们先将compute函数剥离到一个单独的卫星合约中去: + +```solidity +contract Satellite { + function compute(uint a) public returns(uint){ + return a * 10; + } +} +``` + +然后,主合约依赖该子合约完成setVariable: + +```solidity +contract Base { + uint public _variable; + + function setVariable(uint data) public { + _variable = _satellite.compute(data); + } + + Satellite _satellite; + //更新子合约(卫星合约) + function updateSatellite(address addr) public { + _satellite = Satellite(addr); + } +} +``` + +这样,当我们需要修改compute函数时,只需部署这样一个新合约,并将它的地址传入到Base.updateSatellite即可: + +```solidity +contract Satellite2{ + function compute(uint a) public returns(uint){ + return a * 20; + } +} +``` + +### 1.5 Contract Registry - 跟踪最新合约 + +在Satellite模式中,如果一个主合约依赖子合约,在子合约升级时,主合约需要更新对子合约的地址引用,这通过updateXXX来完成,例如前文的updateSatellite函数。这类接口属于维护性接口,与实际业务无关,过多暴露此类接口会影响主合约美观,让调用者的体验大打折扣。Contract Registry设计模式优雅地解决了这个问题。在该设计模式下,会有一个专门的合约Registry跟踪子合约的每次升级情况,主合约可通过查询此Registyr合约取得最新的子合约地址。卫星合约重新部署后,新地址通过Registry.update函数来更新。 + +```solidity +contract Registry{ + + address _current; + address[] _previous; + + //子合约升级了,就通过update函数更新地址 + function update(address newAddress) public{ + if(newAddress != _current){ + _previous.push(_current); + _current = newAddress; + } + } + + function getCurrent() public view returns(address){ + return _current; + } +} +``` + +主合约依赖于Registry获取最新的卫星合约地址。 + +```solidity +contract Base { + uint public _variable; + + function setVariable(uint data) public { + Satellite satellite = Satellite(_registry.getCurrent()); + _variable = satellite.compute(data); + } + + Registry private _registry = //...; +} +``` + +### 1.6 Contract Relay - 代理调用最新合约 + +该设计模式所解决问题与Contract Registry一样,即主合约无需暴露维护性接口就可调用最新子合约。该模式下,存在一个代理合约,和子合约享有相同接口,负责将主合约的调用请求传递给真正的子合约。卫星合约重新部署后,新地址通过SatelliteProxy.update函数来更新。 + +```solidity +contract SatelliteProxy{ + address _current; + function compute(uint a) public returns(uint){ + Satellite satellite = Satellite(_current); + return satellite.compute(a); + } + + //子合约升级了,就通过update函数更新地址 + function update(address newAddress) public{ + if(newAddress != _current){ + _current = newAddress; + } + } +} + + +contract Satellite { + function compute(uint a) public returns(uint){ + return a * 10; + } +} +``` + +主合约依赖于SatelliteProxy: + +```solidity +contract Base { + uint public _variable; + + function setVariable(uint data) public { + _variable = _proxy.compute(data); + } + SatelliteProxy private _proxy = //...; +} +``` + +### 1.7 Mortal - 允许合约自毁 + +字节码中有一个selfdestruct指令,用于销毁合约。所以只需要暴露出自毁接口即可: + +```solidity +contract Mortal{ + + //自毁 + function destroy() public{ + selfdestruct(msg.sender); + } +} +``` + +### 1.8 Automatic Deprecation - 允许合约自动停止服务 + +如果你希望一个合约在指定期限后停止服务,而不需要人工介入,可以使用Automatic Deprecation模式。 + +``` solidity +contract AutoDeprecated{ + + uint private _deadline; + + function setDeadline(uint time) public { + _deadline = time; + } + + modifier notExpired(){ + require(now <= _deadline); + _; + } + + function service() public notExpired { + //some code + } +} +``` + +当用户调用service,notExpired修饰符会先进行日期检测,这样,一旦过了特定时间,调用就会因过期而被拦截在notExpired层。 + +### 1.9 Ownership检查 + +前文中有许多管理性接口,这些接口如果任何人都可调用,会造成严重后果,例如上文中的自毁函数,假设任何人都能访问,其严重性不言而喻。所以,一套保证只有特定账户能够访问的权限控制设计模式显得尤为重要。 + +对于权限的管控,可以采用Ownership模式。该模式保证了只有合约的拥有者才能调用某些函数。首先需要有一个Owned合约: + +```solidity +contract Owned{ + + address public _owner; + + constructor() { + _owner = msg.sender; + } + + modifier onlyOwner(){ + require(_owner == msg.sender); + _; + } +} +``` + +如果一个业务合约,希望某个函数只由拥有者调用,该怎么办呢?如下: + +```solidity +contract Biz is Owned{ + function manage() public onlyOwner{ + } +} +``` + +这样,当调用manage函数时,onlyOwner修饰符就会先运行并检测调用者是否与合约拥有者一致,从而将无授权的调用拦截在外。 + +### 1.10 延迟秘密泄露 + +这类模式一般针对具体场景使用,这节将主要介绍基于隐私的编码模式和与链外数据交互的设计模式。 + +链上数据都是公开透明的,一旦某些隐私数据上链,任何人都可看到,并且再也无法撤回。Commit And Reveal模式允许用户将要保护的数据转换为不可识别数据,比如一串哈希值,直到某个时刻再揭示哈希值的含义,展露真正的原值。以投票场景举例,假设需要在所有参与者都完成投票后再揭示投票内容,以防这期间参与者受票数影响。我们可以看看,在这个场景下所用到的具体代码: + +```solidity +contract CommitReveal { + + struct Commit { + string choice; + string secret; + uint status; + } + + mapping(address => mapping(bytes32 => Commit)) public userCommits; + event LogCommit(bytes32, address); + event LogReveal(bytes32, address, string, string); + + function commit(bytes32 commit) public { + Commit storage userCommit = userCommits[msg.sender][commit]; + require(userCommit.status == 0); + userCommit.status = 1; // committed + emit LogCommit(commit, msg.sender); + } + + function reveal(string choice, string secret, bytes32 commit) public { + Commit storage userCommit = userCommits[msg.sender][commit]; + require(userCommit.status == 1); + require(commit == keccak256(choice, secret)); + userCommit.choice = choice; + userCommit.secret = secret; + userCommit.status = 2; + emit LogReveal(commit, msg.sender, choice, secret); + } +} +``` + +## 2. 智能合约编程攻略 + +[智能合约编写之Solidity的编程攻略](https://fisco-bcos-documentation.readthedocs.io/zh_CN/latest/docs/articles/3_features/35_contract/solidity_design_programming_strategy.html) + +作者:毛嘉宇|FISCO BCOS 核心开发者 + +**“如无必要,勿增实体”。** + +- 需要分布式协作的重要数据才上链,非必需数据不上链; +- 敏感数据脱敏或加密后上链(视数据保密程度选择符合隐私保护安全等级要求的加密算法); +- 链上验证,链下授权。 + +在使用区块链时,开发者不需要将所有业务和数据都放到链上。相反,“好钢用在刀刃上”,智能合约更适合被用在分布式协作的业务场景中。 + +### 2.1 精简函数变量 + +如果在智能合约中定义了复杂的逻辑,特别是合约内定义了复杂的函数入参、变量和返回值,就会在编译的时候碰到以下错误: + +```shell +Compiler error: Stack too deep, try removing local variables. +``` + +这也是社区中的高频技术问题之一。造成这个问题的原因就是EVM所设计用于最大的栈深度为16。所有的计算都在一个栈内执行,对栈的访问只限于其顶端,限制方式为:允许拷贝最顶端16个元素中的一个到栈顶,或者将栈顶元素和下面16个元素中的一个交换。所有其他操作都只能取最顶的几个元素,运算后,把结果压入栈顶。当然可以把栈上的元素放到存储或内存中。但无法只访问栈上指定深度的那个元素,除非先从栈顶移除其他元素。如果一个合约中,入参、返回值、内部变量的大小超过了16个,显然就超出了栈的最大深度。因此,我们可以使用结构体或数组来封装入参或返回值,达到减少栈顶元素使用的目的,从而避免此错误。例如以下代码,通过使用bytes数组来封装了原本16个bytes变量。 + +```solidity +function doBiz(bytes[] paras) public { + require(paras.length >= 16); + // do something +} +``` + +### 2.2 保证参数和行为符合预期 + +在编写智能合约时,一定要注意对合约参数和行为的检查,尤其是那些对外部开放的合约函数。Solidity提供了require、revert、assert等关键字来进行异常的检测和处理。一旦检测并发现错误,整个函数调用会被回滚,所有状态修改都会被回退,就像从未调用过函数一样。以下分别使用了三个关键字,实现了相同的语义。 + +```solidity +require(_data == data, "require data is valid"); + +if(_data != data) { revert("require data is valid"); } + +assert(_data == data); +``` + +不过,这三个关键字一般适用于不同的使用场景: + +- require:最常用的检测关键字,用来验证输入参数和调用函数结果是否合法。 +- revert:适用在某个分支判断的场景下。 +- assert: 检查结果是否正确、合法,一般用于函数结尾。 + +在一个合约的函数中,可以使用函数修饰器来抽象部分参数和条件的检查。在函数体内,可以对运行状态使用if-else等判断语句进行检查,对异常的分支使用revert回退。在函数运行结束前,可以使用assert对执行结果或中间状态进行断言检查。在实践中,推荐使用require关键字,并将条件检查移到函数修饰器中去;这样可以让函数的职责更为单一,更专注到业务逻辑中。同时,函数修饰器等条件代码也更容易被复用,合约也会更加安全、层次化。 + +以一个水果店库存管理系统为例,设计一个水果超市的合约。这个合约只包含了对店内所有水果品类和库存数量的管理,setFruitStock函数提供了对应水果库存设置的函数。在这个合约中,我们需要检查传入的参数,即水果名称不能为空。 + +```solidity +pragma solidity ^0.4.25; + +contract FruitStore { + mapping(bytes => uint) _fruitStock; + modifier validFruitName(bytes fruitName) { + require(fruitName.length > 0, "fruite name is invalid!"); + _; + } + function setFruitStock(bytes fruitName, uint stock) validFruitName(fruitName) external { + _fruitStock[fruitName] = stock; + } +} +``` + +如上所述,我们添加了函数执行前的参数检查的函数修饰器。同理,通过使用函数执行前和函数执行后检查的函数修饰器,可以保证智能合约更加安全、清晰。智能合约的编写需要设置严格的前置和后置函数检查,来保证其安全性。 + +### 2.3 严控函数的执行权限 + +如果说智能合约的参数和行为检测提供了静态的合约安全措施,那么合约权限控制的模式则提供了动态访问行为的控制。由于智能合约是发布到区块链上,所有数据和函数对所有参与者都是公开透明的,任一节点参与者都可发起交易,无法保证合约的隐私。因此,合约发布者必须对函数设计严格的访问限制机制。Solidity提供了函数可见性修饰符、修饰器等语法,灵活地使用这些语法,可帮助构建起合法授权、受控调用的智能合约系统。还是以刚才的水果合约为例。现在getStock提供了查询具体水果库存数量的函数。 + +```solidity +pragma solidity ^0.4.25; + +contract FruitStore { + mapping(bytes => uint) _fruitStock; + modifier validFruitName(bytes fruitName) { + require(fruitName.length > 0, "fruite name is invalid!"); + _; + } + function getStock(bytes fruit) external view returns(uint) { + return _fruitStock[fruit]; + } + function setFruitStock(bytes fruitName, uint stock) validFruitName(fruitName) external { + _fruitStock[fruitName] = stock; + } +} +``` + +水果店老板将这个合约发布到了链上。但是,发布之后,setFruitStock函数可被任何其他联盟链的参与者调用。虽然联盟链的参与者是实名认证且可事后追责;但一旦有恶意攻击者对水果店发起攻击,调用setFruitStock函数就能任意修改水果库存,甚至将所有水果库存清零,这将对水果店正常经营管理产生严重后果。因此,设置某些预防和授权的措施很必要:对于修改库存的函数setFruitStock,可在函数执行前对调用者进行鉴权。类似的,这些检查可能会被多个修改数据的函数复用,使用一个onlyOwner的修饰器就可以抽象此检查。owner字段代表了合约的所有者,会在合约构造函数中被初始化。使用public修饰getter查询函数,就可以通过_owner()函数查询合约的所有者。 + +```solidity +contract FruitStore { + address public _owner; + mapping(bytes => uint) _fruitStock; + + constructor() public { + _owner = msg.sender; + } + + modifier validFruitName(bytes fruitName) { + require(fruitName.length > 0, "fruite name is invalid!"); + _; + } + // 鉴权函数修饰器 + modifier onlyOwner() { + require(msg.sender == _owner, "Auth: only owner is authorized."); + _; + } + function getStock(bytes fruit) external view returns(uint) { + return _fruitStock[fruit]; + } + // 添加了onlyOwner修饰器 + function setFruitStock(bytes fruitName, uint stock) + onlyOwner validFruitName(fruitName) external { + _fruitStock[fruitName] = stock; + } +} +``` + +这样一来,我们可以将相应的函数调用权限检查封装到修饰器中,智能合约会自动发起对调用者身份验证检查,并且只允许合约部署者来调用setFruitStock函数,以此保证合约函数向指定调用者开放。 + +### 2.4 抽象通用的业务逻辑 + +分析上述FruitStore合约,我们发现合约里似乎混入了奇怪的东西。参考单一职责的编程原则,水果店库存管理合约多了上述函数功能检查的逻辑,使合约无法将所有代码专注在自身业务逻辑中。对此,我们可以抽象出可复用的功能,利用Solidity的继承机制继承最终抽象的合约。基于上述FruitStore合约,可抽象出一个BasicAuth合约,此合约包含之前onlyOwner的修饰器和相关功能接口。 + +```solidity +contract BasicAuth { + address public _owner; + + constructor() public { + _owner = msg.sender; + } + + function setOwner(address owner) + public + onlyOwner +{ + _owner = owner; + } + + modifier onlyOwner() { + require(msg.sender == _owner, "BasicAuth: only owner is authorized."); + _; + } +} +``` + +FruitStore可以复用这个修饰器,并将合约代码收敛到自身业务逻辑中。 + +```solidity +import "./BasicAuth.sol"; + +contract FruitStore is BasicAuth { + mapping(bytes => uint) _fruitStock; + + function setFruitStock(bytes fruitName, uint stock) + onlyOwner validFruitName(fruitName) external { + _fruitStock[fruitName] = stock; + } +} +``` + +这样一来,FruitStore的逻辑被大大简化,合约代码更精简、聚焦和清晰。 + +### 2.5 预防私钥的丢失 + +在区块链中调用合约函数的方式有两种:内部调用和外部调用。出于隐私保护和权限控制,业务合约会定义一个合约所有者。假设用户A部署了FruitStore合约,那上述合约owner就是部署者A的外部账户地址。这个地址由外部账户的私钥计算生成。但是,在现实世界中,私钥泄露、丢失的现象比比皆是。一个商用区块链DAPP需要严肃考虑私钥的替换和重置等问题。这个问题最为简单直观的解决方法是添加一个备用私钥。这个备用私钥可支持权限合约修改owner的操作,代码如下: + +```solidity +ontract BasicAuth { + address public _owner; + address public _bakOwner; + + constructor(address bakOwner) public { + _owner = msg.sender; + _bakOwner = bakOwner; + } + + function setOwner(address owner) + public + canSetOwner +{ + _owner = owner; + } + + function setBakOwner(address owner) + public + canSetOwner +{ + _bakOwner = owner; + } + + // ... + + modifier isAuthorized() { + require(msg.sender == _owner || msg.sender == _bakOwner, "BasicAuth: only owner or back owner is authorized."); + _; + } +} +``` + +这样,当发现私钥丢失或泄露时,我们可以使用备用外部账户调用setOwner重置账号,恢复、保障业务正常运行。 + +### 2.6 合理预留事件 + +迄今为止,我们已实现强大灵活的权限管理机制,只有预先授权的外部账户才能修改合约owner属性。不过,仅通过上述合约代码,我们无法记录和查询修改、调用函数的历史记录和明细信息。而这样的需求在实际业务场景中比比皆是。比如,FruitStore水果店需要通过查询历史库存修改记录,计算出不同季节的畅销与滞销水果。 + +一种方法是依托链下维护独立的台账机制。不过,这种方法存在很多问题:保持链下台账和链上记录一致的成本开销非常高;同时,智能合约面向链上所有参与者开放,一旦其他参与者调用了合约函数,相关交易信息就存在不能同步的风险。针对此类场景,Solidity提供了event语法。event不仅具备可供SDK监听回调的机制,还能用较低的gas成本将事件参数等信息完整记录、保存到区块中。FISCO BCOS社区中,也有WEBASE-Collect-Bee这样的工具,在事后实现区块历史事件信息的完整导出。 + +[WEBASE-Collect-Bee工具参考](https://webasedoc.readthedocs.io/zh_CN/latest/docs/WeBASE-Collect-Bee/index.html) + +基于上述权限管理合约,我们可以定义相应的修改权限事件,其他事件以此类推。 + +```solidity +event LogSetAuthority (Authority indexed authority, address indexed from); +} +``` + +接下来,可以调用相应的事件: + +```solidity +function setAuthority(Authority authority) + public + auth +{ + _authority = authority; + emit LogSetAuthority(authority, msg.sender); + } +``` + +当setAuthority函数被调用时,会同时触发LogSetAuthority,将事件中定义的Authority合约地址以及调用者地址记录到区块链交易回执中。当通过控制台调用setAuthority方法时,对应事件LogSetAuthority也会被打印出来。基于WEBASE-Collect-Bee,我们可以导出所有该函数的历史信息到数据库中。也可基于WEBASE-Collect-Bee进行二次开发,实现复杂的数据查询、大数据分析和数据可视化等功能。 + +### 2.7 遵循安全编程规范 + +每一门语言都有其相应的编码规范,我们需要尽可能严格地遵循Solidity官方编程风格指南,使代码更利于阅读、理解和维护,有效地减少合约的bug数量。[Solidity官方编程风格指南参考](https://solidity.readthedocs.io/en/latest/style-guide.html)。除了编程规范,业界也总结了很多安全编程指南,例如重入漏洞、数据结构溢出、随机数误区、构造函数失控、为初始化的存储指针等等。重视和防范此类风险,采用业界推荐的安全编程规范至关重要,例如[Solidity官方安全编程指南](https://solidity.readthedocs.io/en/latest/security-considerations.html)。同时,在合约发布上线后,还需要注意关注、订阅Solidity社区内安全组织或机构发布的各类安全漏洞、攻击手法,一旦出现问题,及时做到亡羊补牢。 + +对于重要的智能合约,有必要引入审计。现有的审计包括了人工审计、机器审计等方法,通过代码分析、规则验证、语义验证和形式化验证等方法保证合约安全性。虽然本文通篇都在强调,模块化和重用被严格审查并广泛验证的智能合约是最佳的实践策略。但在实际开发过程,这种假设过于理想化,每个项目或多或少都会引入新的代码,甚至从零开始。不过,我们仍然可以视代码的复用程度进行审计分级,显式地标注出引用的代码,将审计和检查的重点放在新代码上,以节省审计成本。 + +### 2.8 使用SmartDev应用插件 + +SmartDev包含了一套开放、轻量的开发组件集,覆盖智能合约的开发、调试、应用开发等环节,包括智能合约库(SmartDev-Contract)、智能合约编译插件(SmartDev-SCGP)和应用开发脚手架(SmartDev-Scaffold)。开发者可根据自己的情况自由选择相应的开发工具,提升开发效率。 + +详情可以看:[SmartDev应用开发组件](./smartdev_index.md) + +## 3. 智能合约部署权限控制 + +部署合约的权限控制将由治理委员会统一控制,治理委员会将以投票表决的形式控制部署权限。治理委员会对某个部署权限的提案通过后,将会主动调用固定地址0x1005预编译合约的部署权限写接口,这些写接口也限定只能治理委员会合约调用。 + +部署权限记录在BFS目录/apps下,这代表着允许在/apps目录下的写权限。 + +治理委员可以通过控制台进行部署合约权限控制等操作,详情请看 [设置部署权限类型提案](../operation_and_maintenance/console/console_commands.html#setdeployauthtypeproposal) 、[开启部署权限提案](../operation_and_maintenance/console/console_commands.html#opendeployauthproposal) 、[关闭部署权限提案](../operation_and_maintenance/console/console_commands.html#closedeployauthproposal) + +在检查部署权限时将会对交易发起地址tx.origin进行校验,若没有权限则会返回错误码 -5000。即,会对用户部署合约、用户通过合约部署合约都进行校验。 + +## 4. 智能合约执行权限控制 + +合约管理员可以对固定地址0x1005的预编译合约发起交易,对合约接口的访问ACL进行读写。 + +在执行合约接口的访问ACL的写操作时,将会确定交易发起人msg.sender是否为合约权限表记录的合约管理员,若不是则会拒绝。 + +合约管理员可以通过控制台对合约接口访问ACL的写操作等操作,详情请看:[合约管理员专用命令](../operation_and_maintenance/console/console_commands.html#setmethodauth) + +在检查合约调用权限时将会对交易发起地址tx.origin和消息发送者msg.sender进行校验,若没有权限则会返回错误码 -5000。即,会对用户调用合约、用户通过合约调用合约、合约调用合约都进行校验。 + +## 5. 智能合约运维 + +智能合约在运维时主要关注智能合约的数据状态、智能合约升级、智能合约冻结、智能合约销毁。 + +### 5.1 智能合约升级 + +在Solidity中,一旦合约部署发布后,其代码就无法被修改,只能通过发布新合约去改动代码。假如数据存储在老合约,就会出现所谓的“孤儿数据”问题,新合约将丢失之前运行的历史业务数据。这种情况,开发者可以考虑将老合约数据迁移到新合约中,但此操作至少存在两个问题: + +1. 迁移数据会加重区块链的负担,产生资源浪费和消耗,甚至引入安全问题; +2. 牵一发而动全身,会引入额外的迁移数据逻辑,增加合约复杂度。 + +一种更合理的方式是抽象一层独立的合约存储层。这个存储层只提供合约读写的最基本方法,而不包含任何业务逻辑。在这种模式中,存在三种合约角色: + +- 数据合约:在合约中保存数据,并提供数据的操作接口。 +- 管理合约:设置控制权限,保证只有控制合约才有权限修改数据合约。 +- 控制合约:真正需要对数据发起操作的合约。 + +具体的代码示例如下: + +**数据合约** + +```solidity +contract FruitStore is BasicAuth { + address _latestVersion; + mapping(bytes => uint) _fruitStock; + + modifier onlyLatestVersion() { + require(msg.sender == _latestVersion); + _; + } + + function upgradeVersion(address newVersion) public { + require(msg.sender == _owner); + _latestVersion = newVersion; + } + + function setFruitStock(bytes fruit, uint stock) onlyLatestVersion external { + _fruitStock[fruit] = stock; + } +} +``` + +**管理合约** + +```solidity +contract Admin is BasicAuth { + function upgradeContract(FruitStore fruitStore, address newController) isAuthorized external { + fruitStore.upgradeVersion(newController); + } +} +``` + +**控制合约** + +```solidity +contract FruitStoreController is BasicAuth { + function upgradeStock(bytes fruit, uint stock) isAuthorized external { + fruitStore.setFruitStock(fruit, stock); + } +} +``` + +一旦函数的控制逻辑需要变更,开发者只需修改FruitStoreController控制合约逻辑,部署一个新合约,然后使用管理合约Admin修改新的合约地址参数就可轻松完成合约升级。这种方法可消除合约升级中因业务控制逻辑改变而导致的数据迁移隐患。但天下没有免费的午餐,这种操作需要在可扩展性和复杂性之间需要做基本的权衡。首先,数据和逻辑的分离降低了运行性能。其次,进一步封装增加了程序复杂度。最后,越是复杂的合约越会增加潜在攻击面,简单的合约比复杂的合约更安全。 + +**通用数据结构——数据升级** + +到目前为止,还存在一个问题,假如数据合约中的数据结构本身需要升级怎么办? + +例如,在FruitStore中,原本只保存了库存信息,现在由于水果销售店生意发展壮大,一共开了十家分店,需要记录每家分店、每种水果的库存和售出信息。在这种情况下,一种解决方案是采用外部关联管理方式:创建一个新的ChainStore合约,在这个合约中创建一个mapping,建立分店名和FruitStore的关系。 + +此外,不同分店需要创建一个FruitStore的合约。为了记录新增的售出信息等数据,我们还需要新建一个合约来管理。假如在FruitStore中可预设一些不同类型的reserved字段,可帮助规避新建售出信息合约的开销,仍然复用FruitStore合约。但这种方式在最开始会增加存储开销。一种更好的思路是抽象一层更为底层和通用的存储结构。代码如下: + +```solidity +contract commonDB is BasicAuth { + mapping(bytes => uint) _uintMapping; + + function getUint(bytes key) external view returns(uint) { + return _uintMapping[key]; + } + + function setUint(bytes key, uint value) isAuthorized onlyLatestVersion external { + _uintMapping[key] = value; + } + +} +``` + +类似的,我们可加入所有数据类型变量,帮助commonDB应对和满足不同的数据类型存储需求。相应的控制合约可修改如下: + +```solidity +contract FruitStoreControllerV2 is BasicAuth { + function upgradeStock(bytes32 storeName, bytes32 fruit, uint stock) + isAuthorized external { + commonDB.setUint(sha256(storeName, fruit), stock); + uint result = commonDB.getUint(sha256(storeName, fruit)); + } +} +``` + +使用以上存储的设计模式,可显著提升合约数据存储灵活性,保证合约可升级。众所周知,Solidity既不支持数据库,使用代码作为存储entity,也无法提供更改schema的灵活性。但是,通过这种KV设计,可以使存储本身获得强大的可扩展性。总之,**没有一个策略是完美的,优秀的架构师善于权衡**。智能合约设计者需要充分了解各种方案的利弊,并基于实际情况选择合适的设计方案。 + +**使用CRUD或者KV存储合约数据** + +需要存储的数据都使用CRUD数据接口进行存储,CRUD的数据是通过节点共识并持久存储在链上的。详情请参考[使用CRUD预编译合约开发应用](../contract_develop/c++_contract/use_crud_precompiled.md),[使用KV存储预编译合约开发应用](../contract_develop/c++_contract/use_kv_precompiled.md) + +### 5.2 智能合约冻结、解冻 + +在合约出现数据异常或者出现大量访问异常时,合约管理员可以对该智能合约进行冻结操作,防止其他用户继续访问该合约。 + +合约管理员可以对固定地址0x1005的预编译合约发起交易,对合约的状态进行读写。 + +在执行合约状态的写操作时,将会确定交易发起人msg.sender是否为合约权限表记录的合约管理员,若不是则会拒绝。 + +```eval_rst +.. important:: + 兼容性说明:合约生命周期管理废止操作只能在节点版本3.2以上进行。 +``` + +合约管理员也可以通过控制台对合约进行冻结等操作,详情请看:[冻结合约命令](../operation_and_maintenance/console/console_commands.html#freezecontract)、[解冻合约命令](../operation_and_maintenance/console/console_commands.html#unfreezecontract) + +### 5.3 智能合约废止 + +当合约不再使用,且数据不再供访问,用户可以使用预留的selfdestruct进行合约销毁,合约管理员也可以使用合约废止功能主动将合约状态设定为废止。 + +**selfdestruct** + +字节码中有一个selfdestruct指令,用于销毁合约。所以只需要暴露出自毁接口即可。**注意:** 该过程不可逆,请酌情考虑后果。 + +```solidity +contract Mortal{ + + //自毁 + function destroy() public{ + selfdestruct(msg.sender); + } +} +``` + +**合约废止** + +```eval_rst +.. important:: + 兼容性说明:合约生命周期管理废止操作只能在节点版本3.2以上进行。 +``` + +**注意:** 该过程不可逆,请酌情考虑后果。 + +在执行合约状态的写操作时,将会确定交易发起人msg.sender是否为合约权限表记录的合约管理员,若不是则会拒绝。 + +合约管理员也可以通过控制台对合约进行冻结等操作,详情请看:[冻结合约命令](../operation_and_maintenance/console/console_commands.html#freezecontract) \ No newline at end of file diff --git a/3.x/zh_CN/docs/develop/index.md b/3.x/zh_CN/docs/develop/index.md index 57cf08308..7c8296a4e 100644 --- a/3.x/zh_CN/docs/develop/index.md +++ b/3.x/zh_CN/docs/develop/index.md @@ -10,4 +10,5 @@ 4. 控制台部署调用合约:此部分介绍应用开发者如何下载配置控制台,并指导开发者如何通过控制台部署与调用合约。 5. SmartDev应用开发组件:SmartDev开发组件为区块链应用开发者提供了全面的智能合约库,对于常用的功能,不必再重复造轮子,只需按需引用,就可以引入相应功能,为合约开发的效率和安全保驾护航。此部分旨在指导区块链应用开发者熟悉掌握SmartDev组件。 6. 使用AMOP功能:FISCO BCSO提供AMOP功能(Advanced Messages Onchain Protocol),通过此部分介绍,指导用户利用AMOP协议与其它机构互传消息,通过指定接口接受系统推送消息。 -7. 使用群环签名与同态加密:FISCO BCOS以预编译合约的形式集成了同态加密、群/环签名验证功能,提供了多种隐私保护手段,开发者通可过此部分文档熟悉同态加密与群环签名算法,并了解如何使用此部分功能。 \ No newline at end of file +7. 使用群环签名与同态加密:FISCO BCOS以预编译合约的形式集成了同态加密、群/环签名验证功能,提供了多种隐私保护手段,开发者通可过此部分文档熟悉同态加密与群环签名算法,并了解如何使用此部分功能。 +8. 智能合约安全实践:介绍智能合约在编码、部署、运行、维护各个阶段的最佳实践以及应该使用的安全措施。 diff --git a/3.x/zh_CN/index.rst b/3.x/zh_CN/index.rst index 47257502d..a42e70300 100644 --- a/3.x/zh_CN/index.rst +++ b/3.x/zh_CN/index.rst @@ -310,6 +310,7 @@ FISCO BCOS开源社区致力打造开放多元的开源联盟链生态,至今 docs/develop/smartdev_index.md docs/develop/amop.md docs/develop/privacy.md + docs/develop/contract_safty_practice.md .. toctree:: :hidden: From a8028e347a5345721714eb479485705df84f3435 Mon Sep 17 00:00:00 2001 From: yongmi Date: Sat, 7 Oct 2023 18:25:31 +0800 Subject: [PATCH 4/4] Add document for expanding nodes gateway and group in pro chain --- .../tutorial/pro/expand_pro_withoutTars.md | 735 ++++++++++++++++++ 1 file changed, 735 insertions(+) create mode 100644 3.x/zh_CN/docs/tutorial/pro/expand_pro_withoutTars.md diff --git a/3.x/zh_CN/docs/tutorial/pro/expand_pro_withoutTars.md b/3.x/zh_CN/docs/tutorial/pro/expand_pro_withoutTars.md new file mode 100644 index 000000000..a1f50f41b --- /dev/null +++ b/3.x/zh_CN/docs/tutorial/pro/expand_pro_withoutTars.md @@ -0,0 +1,735 @@ +# 一键扩容无tars pro链 + +## 1. 脚本功能介绍 + +`build_chain.sh`脚本用于快速生成一条链中节点的配置文件,以下为用于无tars版pro/max 脚本选项功能介绍: + +**`C`选项[Optional]** + +脚本的命令,支持 `deploy` ,默认为`deploy`: + +- deploy: 用于部署新节点。 +- expand_node: 用于扩容节点 +- expand_group: 用于扩容群组 +- expand_service: 用于扩容RPC/Gateway服务 + +**`V`选项[Optional]** + +指定链版本(air、pro、max),默认为air。 + +**`c`选项[Optional]** + +用于指定服务的配置文件路径,此路径须包括config.toml,默认为./BcosBuilder/pro/config.toml。 + +**`o`选项[Optional]** + +指定生成的节点产物所在的目录,默认目录为 `./expand` 。 + +## 2、扩容node service + +### 2.1设置区块链节点扩容配置 + +具体步骤如下: + +``` +1、复制部署时的config.toml,仅保留想要扩容节点的agency配置,将其他agency的配置文件删除; +2、[tars]中的tars_pkg_dir指定服务的二进制位置; +3、[group] genesis_config_path,指定已有节点的创世块配置文件路径; +4、[[agency]] 中的[agency.group]选项,修改node_name、tars_listen_port; + +注意,tars_listen_port需要比之前部署的最后一个端口相差大于5,例如之前部署的最后一个节点的tars_listen_port=40423,那么此次的tars_listen_port最小为40428。 +``` + +扩容配置`config.toml`如下(例如扩容机构A节点node1): + +``` +[tars] +tars_pkg_dir = "binary/" + +[chain] +chain_id="chain0" +# the rpc-service enable sm-ssl or not, default disable sm-ssl +rpc_sm_ssl=false +# the gateway-service enable sm-ssl or not, default disable sm-ssm +gateway_sm_ssl=false +# the existed rpc service ca path, will generate new ca if not configured +#rpc_ca_cert_path="" +# the existed gateway service ca path, will generate new ca if not configured +#gateway_ca_cert_path=" + +[[group]] +group_id="group0" +# the genesis configuration path of the group, will generate new genesis configuration if not configured +genesis_config_path = "./group0/chain0/group0/config.genesis" +# VM type, now only support evm/wasm +vm_type="evm" +# use sm-crypto or not +sm_crypto=false +# enable auth-check or not +auth_check=false +init_auth_address="1" + +# the genesis config +# the number of blocks generated by each leader +leader_period = 1 +# the max number of transactions of a block +block_tx_count_limit = 1000 +# consensus algorithm now support PBFT(consensus_type=pbft) +consensus_type = "pbft" +# transaction gas limit +gas_limit = "3000000000" +# compatible version, can be dynamically upgraded through setSystemConfig +compatibility_version="3.4.0" + +[[agency]] +name = "agencyA" +# enable data disk encryption for rpc/gateway or not, default is false +enable_storage_security = false +# url of the key center, in format of ip:port, please refer to https://github.com/FISCO-BCOS/key-manager for details +# key_center_url = +# cipher_data_key = + + [agency.rpc] + deploy_ip=["172.31.184.189"] + # rpc listen ip + listen_ip="0.0.0.0" + # rpc listen port + listen_port=20000 + thread_count=4 + # rpc tars server listen ip + tars_listen_ip="0.0.0.0" + # rpc tars server listen port + tars_listen_port=40407 + + [agency.gateway] + deploy_ip=["172.31.184.189"] + # gateway listen ip + listen_ip="0.0.0.0" + # gateway listen port + listen_port=30000 + # gateway connected peers, should be all of the gateway peers info + peers=["172.31.184.189:30000", "172.31.184.246:30000", "172.31.184.13:30000", "172.31.184.213:30000"] + # gateway tars server listen ip + tars_listen_ip="0.0.0.0" + # gateway tars server listen port + tars_listen_port=40401 + + [[agency.group]] + group_id = "group0" + [[agency.group.node]] + # node name, Notice: node_name in the same agency and group must be unique + node_name = "node1" + deploy_ip = "172.31.184.189" + # node tars server listen ip + tars_listen_ip="0.0.0.0" + # node tars server listen port, Notice: the tars server of the node will cost five ports, then the port tars_listen_port ~ tars_listen_port + 4 should be in free + tars_listen_port=40428 + # enable data disk encryption for bcos node or not, default is false + enable_storage_security = false + # url of the key center, in format of ip:port, please refer to https://github.com/FISCO-BCOS/key-manager for details + # key_center_url = + # cipher_data_key = + monitor_listen_port = "3902" + # monitor log path example:"/home/fisco/tars/framework/app_log/" + monitor_log_path = "" +``` + +### 2.2 部署生成节点 + +```shell + +# 扩容node service +bash build_chain.sh -C expand_node -V pro -o expand_node -c ./config.toml + +``` + +执行上述命令后,扩容过程中生成的产物具体如下 + +``` +$ tree expand_node/172.31.184.227/ +expand_node/172.31.184.227/ +├── group0_node_44428 +│ ├── BcosNodeService +│ ├── conf +│ │ ├── config.genesis +│ │ ├── config.ini +│ │ ├── node.nodeid +│ │ ├── node.pem +│ │ ├── tars.conf +│ │ └── tars_proxy.ini +│ ├── start.sh +│ └── stop.sh +├── start_all.sh +└── stop_all.sh +``` + + + +### 2.3 将新扩容节点加入到群组 + +```eval_rst +.. note:: + 扩容新节点时,先将节点添加为观察节点,当扩容节点的块高与链上已有节点最高块高一致时候,才可将其加入为共识节点。 +``` + +**步骤1:获取扩容节点的NodeID** + +新节点扩容成功后,可通过控制台的`getGroupPeers`命令查看新增的节点列表: + +```shell +[group0]: /apps> getGroupPeers +peer0: 2300e14703675333d947154d8f01bb9d9f814c23a89980872404f368b301d45be2d3acebe306fa6d5a2859fd67f6ba2515eba0b263c940a5f67977ca440f806d +peer1: 582a3bc29ca8088ce63124c7b13d8627f931579bc3d896a983209a3b302be3f69cbd653f4bcd8ea3cdbf34d008960ee263644011af5826a7297c732b4ef4a1be +peer2: 58d2f067916dd514e570561e12b42cd07e42729317ea95217065c423055252a534dd7ab9490f97febf61868b4271a3b29776cd4d23b1b9f93e006d257c01cf81 +peer3: 6ca18750f51e9f748e653aa88d4f275659aecfc7c0062a105863b9415e75f0189e8c931f0d13e1254bb776c29da7e90f8316467e5bbf50663a1866c0c6fca46c +peer4: f760b1ef241faa35f2cc68cdb370bfc6b27f9b94b4c79a3d24d4fee41021d87d858a486b611c8ec8213c666faf9e6eec41115cb289bb0249d233e8866f22a94a + +[group0]: /apps> getSealerList +[ + Sealer{ + nodeID='2300e14703675333d947154d8f01bb9d9f814c23a89980872404f368b301d45be2d3acebe306fa6d5a2859fd67f6ba2515eba0b263c940a5f67977ca440f806d', + weight=1 + }, + Sealer{ + nodeID='582a3bc29ca8088ce63124c7b13d8627f931579bc3d896a983209a3b302be3f69cbd653f4bcd8ea3cdbf34d008960ee263644011af5826a7297c732b4ef4a1be', + weight=1 + }, + Sealer{ + nodeID='6ca18750f51e9f748e653aa88d4f275659aecfc7c0062a105863b9415e75f0189e8c931f0d13e1254bb776c29da7e90f8316467e5bbf50663a1866c0c6fca46c', + weight=1 + }, + Sealer{ + nodeID='f760b1ef241faa35f2cc68cdb370bfc6b27f9b94b4c79a3d24d4fee41021d87d858a486b611c8ec8213c666faf9e6eec41115cb289bb0249d233e8866f22a94a', + weight=1 + } +] +[group0]: /> getObserverList +[] +``` + +从控制台输出可看出,nodeID为58d2f067916dd514e570561e12b42cd07e42729317ea95217065c423055252a534dd7ab9490f97febf61868b4271a3b29776cd4d23b1b9f93e006d257c01cf81 的节点不在群组内,使用控制台`addObserver`命令将其加入到观察节点如下: + + +**步骤2: 将扩容节点加入为观察节点** + +```shell +[group0]: /apps> addObserver 58d2f067916dd514e570561e12b42cd07e42729317ea95217065c423055252a534dd7ab9490f97febf61868b4271a3b29776cd4d23b1b9f93e006d257c01cf81 +{ + "code":0, + "msg":"Success" +} + +[group0]: /apps> getObserverList +[ 58d2f067916dd514e570561e12b42cd07e42729317ea95217065c423055252a534dd7ab9490f97febf61868b4271a3b29776cd4d23b1b9f93e006d257c01cf81 +] +``` + +**步骤3:扩容节点同步到最高块后,将扩容节点加入为共识节点** + +```shell +[group0]: /apps> addSealer 58d2f067916dd514e570561e12b42cd07e42729317ea95217065c423055252a534dd7ab9490f97febf61868b4271a3b29776cd4d23b1b9f93e006d257c01cf81 1 +{ + "code":0, + "msg":"Success" +} + +[group0]: /apps> getSealerList +[ + Sealer{ + nodeID='2300e14703675333d947154d8f01bb9d9f814c23a89980872404f368b301d45be2d3acebe306fa6d5a2859fd67f6ba2515eba0b263c940a5f67977ca440f806d', + weight=1 + }, + Sealer{ + nodeID='582a3bc29ca8088ce63124c7b13d8627f931579bc3d896a983209a3b302be3f69cbd653f4bcd8ea3cdbf34d008960ee263644011af5826a7297c732b4ef4a1be', + weight=1 + }, + Sealer{ + nodeID='6ca18750f51e9f748e653aa88d4f275659aecfc7c0062a105863b9415e75f0189e8c931f0d13e1254bb776c29da7e90f8316467e5bbf50663a1866c0c6fca46c', + weight=1 + }, + Sealer{ + nodeID='f760b1ef241faa35f2cc68cdb370bfc6b27f9b94b4c79a3d24d4fee41021d87d858a486b611c8ec8213c666faf9e6eec41115cb289bb0249d233e8866f22a94a', + weight=1 + }, + Sealer{ + nodeID='58d2f067916dd514e570561e12b42cd07e42729317ea95217065c423055252a534dd7ab9490f97febf61868b4271a3b29776cd4d23b1b9f93e006d257c01cf81', + weight=1 + } +] +``` + +## 3、扩容RPC/Gateway service + +### 3.1 设置RPC/Gateway服务扩容配置 + +主要修改: + +``` +1、指定[chain] rpc_ca_cert_path、gateway_ca_cert_path中的ca路径; +2、[tars]中的tars_pkg_dir指定服务的二进制位置; +3、设置[[agency]]中的机构 name +4、设置[agency.rpc]的deploy_ip、listen_port、tars_listen_port 服务ip 和相应的端口; +5、设置[agency.gateway]的deploy_ip、listen_port、tars_listen_port服务ip 和相应的端口,同时修改peers (需要将已经部署完成的gateway的IP:port写上,其他已经部署的gateway不需要修改相应的nodes.json; + +注意,tars_listen_port需要比之前部署的最后一个端口相差大于5,例如之前部署的最后一个节点的tars_listen_port=40423,那么此次的rpc中tars_listen_port最小为40428,gateway中tars_listen_port最小为44429 +``` + +新RPC/Gateway服务的配置`config.toml`如下: + +``` +[tars] +tars_pkg_dir = "/data/yongmi/task/BcosBuilder/pro/binary" + +[chain] +chain_id="chain0" +# the rpc-service enable sm-ssl or not, default disable sm-ssl +rpc_sm_ssl=false +# the gateway-service enable sm-ssl or not, default disable sm-ssm +gateway_sm_ssl=false +# the existed rpc service ca path, will generate new ca if not configured +rpc_ca_cert_path="/data/yongmi/task/generate/rpc/chain0/ca" +# the existed gateway service ca path, will generate new ca if not configured +gateway_ca_cert_path="/data/yongmi/task/generate/gateway/chain0/ca" + +[[group]] +group_id="group0" +# the genesis configuration path of the group, will generate new genesis configuration if not configured +# genesis_config_path = "" +genesis_config_path = "/data/yongmi/task/generate/172.31.184.227/group0_node_44402/conf/config.genesis" + +# VM type, now only support evm/wasm +vm_type="evm" +# use sm-crypto or not +sm_crypto=false +# enable auth-check or not +auth_check=false +init_auth_address="0xff2c2db03e58da828e89c12f94bec03eecb0d309" + +# the genesis config +# the number of blocks generated by each leader +leader_period = 1 +# the max number of transactions of a block +block_tx_count_limit = 1000 +# consensus algorithm now support PBFT(consensus_type=pbft) +consensus_type = "pbft" +# transaction gas limit +gas_limit = "3000000000" +# compatible version, can be dynamically upgraded through setSystemConfig +compatibility_version="3.4.0" + +[[agency]] +name = "agencyE" +# enable data disk encryption for rpc/gateway or not, default is false +enable_storage_security = false +# url of the key center, in format of ip:port, please refer to https://github.com/FISCO-BCOS/key-manager for details +# key_center_url = +# cipher_data_key = + + [agency.rpc] + deploy_ip=["172.30.32.99"] + # rpc listen ip + listen_ip="0.0.0.0" + # rpc listen port + listen_port=22200 + thread_count=4 + # rpc tars server listen ip + tars_listen_ip="0.0.0.0" + # rpc tars server listen port + tars_listen_port=44428 + + [agency.gateway] + deploy_ip=["172.30.32.99"] + # gateway listen ip + listen_ip="0.0.0.0" + # gateway listen port + listen_port=33300 + # gateway connected peers, should be all of the gateway peers info + peers=["172.31.184.227:33300", "172.30.32.85:33300", "172.30.35.60:33300", "172.30.93.111:33300", "172.30.32.99:33300"] + tars_listen_port=44429 + +``` + +### 3.2 生成RPC/Gateway + +``` + +# 扩容并部署RPC服务 +bash build_chain.sh -C expand_service -V pro -o expand_service -c ./config.toml +``` + +扩容过程中生成的RPC/Gateway服务相关的配置位于expand_service/${deploy_ip}`目录,具体如下: + +```shell +$ tree expand_service/172.30.32.99/ +expand_service/172.30.32.99/ +├── gateway_33300 +│ ├── BcosGatewayService +│ ├── conf +│ │ ├── ca.crt +│ │ ├── cert.cnf +│ │ ├── config.ini +│ │ ├── nodes.json +│ │ ├── ssl.crt +│ │ ├── ssl.key +│ │ ├── ssl.nodeid +│ │ ├── tars.conf +│ │ └── tars_proxy.ini +│ ├── start.sh +│ └── stop.sh +├── rpc_22200 +│ ├── BcosRpcService +│ ├── conf +│ │ ├── ca.crt +│ │ ├── cert.cnf +│ │ ├── config.ini +│ │ ├── sdk +│ │ │ ├── ca.crt +│ │ │ ├── cert.cnf +│ │ │ ├── sdk.crt +│ │ │ ├── sdk.key +│ │ │ └── sdk.nodeid +│ │ ├── ssl.crt +│ │ ├── ssl.key +│ │ ├── ssl.nodeid +│ │ ├── tars.conf +│ │ └── tars_proxy.ini +│ ├── start.sh +│ └── stop.sh +├── start_all.sh +└── stop_all.sh +``` + +### 3.3通过控制台获取新扩容的服务信息 + +将生成的产物放置到对应ip机器中并启动服务,启动控制台,输入`getPeers`命令,控制台显示的Gateway服务节点数目由4个增加为5个。 + +``` +[group0]: /apps> getPeers +PeersInfo{ + p2pNodeID='3082010a0282010100bd8417a6197cac386e2223d81aad70fdefad4967f1e5dcf8af6b8322f06e1065b8684eb89ac3f948bcdd490940cf1620c14d23a56e466deb34c52805b486919893d4ddafcb2ee061f5e80195fa954ae1d95ba9a835659294fb61415c6d0214da8504e6e3c530369f4cc21bab8dad34dab46367df6bfbe7f3c806cf3f084891a242eeec560c00a6d2bc227af5e9e7cf1311b4bc28a51b3fae6604fbd0ca8d1a9c969d1e20eb67d6255536dc29e87d70b6beb2afff4ddc52421b81e3ef0fd7ada7706045ff5928aff7cb2aed5c6845523baf753e1ba4191e5b35278b5388f92ffeaf85ed131db5839f5ffbd7741e4c5dee473d59c424a7ff67868d753f75a3a9d50203010001', + endPoint='0.0.0.0:33300', + groupNodeIDInfo=[ + NodeIDInfo{ + group='group0', + nodeIDList=[ + 2300e14703675333d947154d8f01bb9d9f814c23a89980872404f368b301d45be2d3acebe306fa6d5a2859fd67f6ba2515eba0b263c940a5f67977ca440f806d + ] + } + ], + peers=[ + PeerInfo{ + p2pNodeID='3082010a0282010100e40cc1ede22791a4650a812d81e50c8f8a7ecc6bee39ef75241d5a762f5ace499625198af3b541c828e246b7ab3172c4f9ec157d05b44279acc48b72873fb8e646434713bb8a74c8f0aa66f727a30f22c028faefd11dd686dcf23dfc9c56eec913aaebf5d223cb908ed50cbf99b63e9b95ca11f1fe14157aca83cc0b1f26b4e79a97510ea06723b7d5adf9a743af5cd43ffcde5700ed9e1f9f7cdd4bc96559473e80a97d51083120e60e56d3b01ad6dfe02191bcddc15cd95804519c07df929fbe4a44d9f92145db828e15c7b4b8cdb1f26e3c84b992fcad2005132b178609464ec0980ca61b34e0d0d7d3f739aabf02dcba8850960e0a73f191a5117ce27f0b0203010001', + endPoint='172.30.32.99:53638', + groupNodeIDInfo=[ + + ] + }, + PeerInfo{ + p2pNodeID='3082010a0282010100c9234db7916d84645cd555e7290df98f393dfa3c7beb4181220effee59c912ba2b8e5bbc984effd42aa035f2208700e7c648b3e4227da3b01d4a386fbc9098f4647c2a87e153273bf8c1d10a5dc50140c6220b28a0a7ae1740bb6f7a38546cfa053a35b3420af80d1172f65ac7b678b7dd4881e4c112b5fa47cd45b6a6fa248a37941c9508c85a446ea8d381f135250b5bfc8cf83c07aade88c0a523ea3591065f72b7130bb6685741cce72ed33953ab353fd0297bf383cd2d0757f31a9db4266b6a75aa784a0421f1b853c9c08f9f862cc6ec7bc0e2ea7ae0fdf7db48f772f7f97b6052c1a097e1244d7bad9205591232a028f147054e31fa648c9e31bc776b0203010001', + endPoint='172.30.35.60:44374', + groupNodeIDInfo=[ + NodeIDInfo{ + group='group0', + nodeIDList=[ + 6ca18750f51e9f748e653aa88d4f275659aecfc7c0062a105863b9415e75f0189e8c931f0d13e1254bb776c29da7e90f8316467e5bbf50663a1866c0c6fca46c + ] + } + ] + }, + PeerInfo{ + p2pNodeID='3082010a0282010100cae0ec076d0e2c9ab16751eb162d45dc2f0c5c2262fc6e71f3bd3ce73dc307b9a8ed0a45d4badc44f7b940369c731f7c027a04445702e86e09fdb530e9afa63e5ff5593c0c0738ca80fb9d589956daec439293e64685e941a753349f81c1d8be6bfa309541e93f8e7367fc9f26c3681c750348696652472a888f5bf2d5d32e6fbc2ed3cd914684a79b804616efaf01601d37f8428295a5f321c514662c849d614c56bbd64c7872a94117ffcc89517cf9d08c690b44a63775146a5ade93b665f2c3451ec73047ac9031648f0e89d9048ff41990f67c28f69438482b1a68b9a55cb11f87adb83d4aff152f12fb50a166591e56e777409d55e30f2b222deb665def0203010001', + endPoint='172.30.32.85:34372', + groupNodeIDInfo=[ + NodeIDInfo{ + group='group0', + nodeIDList=[ + f760b1ef241faa35f2cc68cdb370bfc6b27f9b94b4c79a3d24d4fee41021d87d858a486b611c8ec8213c666faf9e6eec41115cb289bb0249d233e8866f22a94a + ] + } + ] + }, + PeerInfo{ + p2pNodeID='3082010a0282010100c3c2095e5da5ce1b57c69a8bd2a47936fa14acc87e2b2aed726da8e278d23f99c17ffbeb9e78c584019e28c2ab5bbeeb9b19f120d233728c60d179c240b974d6d2ed1fee1e1885f644710399687c932e06419c06c590becbfe6f3dabd5f60e867bf8e068eb5e296d58d54e1bbd2062b05870cf869322544e8929c1cec68a684f496319ad1e8818e801f59b4e2a7ca446c5aebfd8c1b15b9d98e07a0a9302ec23b309d301fec989e81a03b685a5013c52e58b5b9fdede13390fdb69d6bfd91a2bd76506a90981f0f084fd459435d85b55064c72a060acd56ee30441a7d83aa5914d535741951b1a6630806b74a4d4fafcf9cad622972989e7b3799d3ae0f1f5430203010001', + endPoint='172.30.93.111:40340', + groupNodeIDInfo=[ + NodeIDInfo{ + group='group0', + nodeIDList=[ + 582a3bc29ca8088ce63124c7b13d8627f931579bc3d896a983209a3b302be3f69cbd653f4bcd8ea3cdbf34d008960ee263644011af5826a7297c732b4ef4a1be + ] + } + ] + } + ] +} +``` + + + +## 4、扩容群组 + +### 4.1 设置群组配置 + +具体步骤如下: + +``` +1、拷贝部署时的配置文件,修改[[group]] 中的group_id; +2、[tars]中的tars_pkg_dir指定服务的二进制位置; +3、设置[[agency]] 中 [[agency.group]] 的group_id; +4、设置[[agency.group.node]] 中的tars_listen_port,tars_listen_port需要比之前部署的最后一个端口相差大于5; +``` + +配置新群组的config.toml 如下: + +``` +[tars] +tars_pkg_dir = "./binary" + +[chain] +chain_id="chain0" +# the rpc-service enable sm-ssl or not, default disable sm-ssl +rpc_sm_ssl=false +# the gateway-service enable sm-ssl or not, default disable sm-ssm +gateway_sm_ssl=false +# the existed rpc service ca path, will generate new ca if not configured +#rpc_ca_cert_path="" +# the existed gateway service ca path, will generate new ca if not configured +#gateway_ca_cert_path="" + +[[group]] +group_id="group1" +# the genesis configuration path of the group, will generate new genesis configuration if not configured +# genesis_config_path = "" +# VM type, now only support evm/wasm +vm_type="evm" +# use sm-crypto or not +sm_crypto=false +# enable auth-check or not +auth_check=false +init_auth_address="0xff2c2db03e58da828e89c12f94bec03eecb0d309" + +# the genesis config +# the number of blocks generated by each leader +leader_period = 1 +# the max number of transactions of a block +block_tx_count_limit = 1000 +# consensus algorithm now support PBFT(consensus_type=pbft) +consensus_type = "pbft" +# transaction gas limit +gas_limit = "3000000000" +# compatible version, can be dynamically upgraded through setSystemConfig +compatibility_version="3.4.0" + +[[agency]] +name = "agencyA" +# enable data disk encryption for rpc/gateway or not, default is false +enable_storage_security = false +# url of the key center, in format of ip:port, please refer to https://github.com/FISCO-BCOS/key-manager for details +# key_center_url = +# cipher_data_key = + + [agency.rpc] + deploy_ip=["172.31.184.227"] + # rpc listen ip + listen_ip="0.0.0.0" + # rpc listen port + listen_port=22200 + thread_count=4 + # rpc tars server listen ip + tars_listen_ip="0.0.0.0" + # rpc tars server listen port + tars_listen_port=44400 + + [agency.gateway] + deploy_ip=["172.31.184.227"] + # gateway listen ip + listen_ip="0.0.0.0" + # gateway listen port + listen_port=33300 + # gateway connected peers, should be all of the gateway peers info + peers=["172.31.184.227:33300", "172.30.32.85:33300", "172.30.35.60:33300", "172.30.93.111:33300"] + tars_listen_port=44401 + + [[agency.group]] + group_id = "group1" + + [[agency.group.node]] + # node name, Notice: node_name in the same agency and group must be unique + node_name = "node0" + deploy_ip = "172.31.184.227" + # node tars server listen ip + tars_listen_ip="0.0.0.0" + # node tars server listen port, Notice: the tars server of the node will cost five ports, then the port tars_listen_port ~ tars_listen_port + 4 should be in free + tars_listen_port=44428 + # enable data disk encryption for bcos node or not, default is false + enable_storage_security = false + # url of the key center, in format of ip:port, please refer to https://github.com/FISCO-BCOS/key-manager for details + # key_center_url = + # cipher_data_key = + monitor_listen_port = "3901" + # monitor log path example:"/home/fisco/tars/framework/app_log/" + monitor_log_path = "" + +[[agency]] +name = "agencyB" +# enable data disk encryption for rpc/gateway or not, default is false +enable_storage_security = false +# url of the key center, in format of ip:port, please refer to https://github.com/FISCO-BCOS/key-manager for details +# key_center_url = +# cipher_data_key = + + [agency.rpc] + deploy_ip=["172.30.32.85"] + # rpc listen ip + listen_ip="0.0.0.0" + # rpc listen port + listen_port=22200 + thread_count=4 + # rpc tars server listen ip + tars_listen_ip="0.0.0.0" + # rpc tars server listen port + tars_listen_port=44407 + + [agency.gateway] + deploy_ip=["172.30.32.85"] + # gateway listen ip + listen_ip="0.0.0.0" + # gateway listen port + listen_port=33300 + # gateway connected peers, should be all of the gateway peers info + peers=["172.31.184.227:33300", "172.30.32.85:33300", "172.30.35.60:33300", "172.30.93.111:33300"] + tars_listen_port=44408 + + [[agency.group]] + group_id = "group1" + + [[agency.group.node]] + # node name, Notice: node_name in the same agency and group must be unique + node_name = "node0" + deploy_ip = "172.30.32.85" + # node tars server listen ip + tars_listen_ip="0.0.0.0" + # node tars server listen port, Notice: the tars server of the node will cost five ports, then the port tars_listen_port ~ tars_listen_port + 4 should be in free + tars_listen_port=44433 + # enable data disk encryption for bcos node or not, default is false + enable_storage_security = false + # url of the key center, in format of ip:port, please refer to https://github.com/FISCO-BCOS/key-manager for details + # key_center_url = + # cipher_data_key = + monitor_listen_port = "3901" + # monitor log path example:"/home/fisco/tars/framework/app_log/" + monitor_log_path = "" +``` + +### 4.2生成新群组 + +``` +# 扩容新群组 +bash build_chain.sh -C expand_group -V pro -o expand_group -c ./config.toml +``` + +扩容过程中生成的群组相关的配置位于expand_group目录,具体如下: + +``` +$ tree expand_group +expand_group +├── 172.30.32.85 +│ ├── group1_node_44433 +│ │ ├── BcosNodeService +│ │ ├── conf +│ │ │ ├── config.genesis +│ │ │ ├── config.ini +│ │ │ ├── node.nodeid +│ │ │ ├── node.pem +│ │ │ ├── tars.conf +│ │ │ └── tars_proxy.ini +│ │ ├── start.sh +│ │ └── stop.sh +│ ├── start_all.sh +│ └── stop_all.sh +└── 172.31.184.227 + ├── group1_node_44428 + │ ├── BcosNodeService + │ ├── conf + │ │ ├── config.genesis + │ │ ├── config.ini + │ │ ├── node.nodeid + │ │ ├── node.pem + │ │ ├── tars.conf + │ │ └── tars_proxy.ini + │ ├── start.sh + │ └── stop.sh + ├── start_all.sh + └── stop_all.sh +``` + +### 4.2 通过控制台获取新扩容的服务信息 + +将生成的产物放置到对应ip机器中并启动服务,启动控制台,输入`getPeers`命令,控制台显示增加的group1 有两个node; + +``` +[group1]: /apps> getPeers +PeersInfo{ + p2pNodeID='3082010a0282010100bd8417a6197cac386e2223d81aad70fdefad4967f1e5dcf8af6b8322f06e1065b8684eb89ac3f948bcdd490940cf1620c14d23a56e466deb34c52805b486919893d4ddafcb2ee061f5e80195fa954ae1d95ba9a835659294fb61415c6d0214da8504e6e3c530369f4cc21bab8dad34dab46367df6bfbe7f3c806cf3f084891a242eeec560c00a6d2bc227af5e9e7cf1311b4bc28a51b3fae6604fbd0ca8d1a9c969d1e20eb67d6255536dc29e87d70b6beb2afff4ddc52421b81e3ef0fd7ada7706045ff5928aff7cb2aed5c6845523baf753e1ba4191e5b35278b5388f92ffeaf85ed131db5839f5ffbd7741e4c5dee473d59c424a7ff67868d753f75a3a9d50203010001', + endPoint='0.0.0.0:33300', + groupNodeIDInfo=[ + NodeIDInfo{ + group='group0', + nodeIDList=[ + 2300e14703675333d947154d8f01bb9d9f814c23a89980872404f368b301d45be2d3acebe306fa6d5a2859fd67f6ba2515eba0b263c940a5f67977ca440f806d + ] + }, + NodeIDInfo{ + group='group1', + nodeIDList=[ + 68ab51bcabeddf62eebbb23af731a4c6ba43a9724e2d264e081be811b39542f8ac1acf490dfe837f416b32c7c3d86e4c917f0a0b559c350146ba10f21b0c961b + ] + } + ], + peers=[ + PeerInfo{ + p2pNodeID='3082010a0282010100c9234db7916d84645cd555e7290df98f393dfa3c7beb4181220effee59c912ba2b8e5bbc984effd42aa035f2208700e7c648b3e4227da3b01d4a386fbc9098f4647c2a87e153273bf8c1d10a5dc50140c6220b28a0a7ae1740bb6f7a38546cfa053a35b3420af80d1172f65ac7b678b7dd4881e4c112b5fa47cd45b6a6fa248a37941c9508c85a446ea8d381f135250b5bfc8cf83c07aade88c0a523ea3591065f72b7130bb6685741cce72ed33953ab353fd0297bf383cd2d0757f31a9db4266b6a75aa784a0421f1b853c9c08f9f862cc6ec7bc0e2ea7ae0fdf7db48f772f7f97b6052c1a097e1244d7bad9205591232a028f147054e31fa648c9e31bc776b0203010001', + endPoint='172.30.35.60:44374', + groupNodeIDInfo=[ + NodeIDInfo{ + group='group0', + nodeIDList=[ + 6ca18750f51e9f748e653aa88d4f275659aecfc7c0062a105863b9415e75f0189e8c931f0d13e1254bb776c29da7e90f8316467e5bbf50663a1866c0c6fca46c + ] + } + ] + }, + PeerInfo{ + p2pNodeID='3082010a0282010100cae0ec076d0e2c9ab16751eb162d45dc2f0c5c2262fc6e71f3bd3ce73dc307b9a8ed0a45d4badc44f7b940369c731f7c027a04445702e86e09fdb530e9afa63e5ff5593c0c0738ca80fb9d589956daec439293e64685e941a753349f81c1d8be6bfa309541e93f8e7367fc9f26c3681c750348696652472a888f5bf2d5d32e6fbc2ed3cd914684a79b804616efaf01601d37f8428295a5f321c514662c849d614c56bbd64c7872a94117ffcc89517cf9d08c690b44a63775146a5ade93b665f2c3451ec73047ac9031648f0e89d9048ff41990f67c28f69438482b1a68b9a55cb11f87adb83d4aff152f12fb50a166591e56e777409d55e30f2b222deb665def0203010001', + endPoint='172.30.32.85:34372', + groupNodeIDInfo=[ + NodeIDInfo{ + group='group0', + nodeIDList=[ + f760b1ef241faa35f2cc68cdb370bfc6b27f9b94b4c79a3d24d4fee41021d87d858a486b611c8ec8213c666faf9e6eec41115cb289bb0249d233e8866f22a94a + ] + }, + NodeIDInfo{ + group='group1', + nodeIDList=[ + 920f69840c1a2d4bdbda160992582cf4b3baacd6000c4cf259078a7188a7490470c90ad3f7250f64e5f84f38745ccce07a928469ca7608d7d875bbfcdaaf739d + ] + } + ] + }, + PeerInfo{ + p2pNodeID='3082010a0282010100c3c2095e5da5ce1b57c69a8bd2a47936fa14acc87e2b2aed726da8e278d23f99c17ffbeb9e78c584019e28c2ab5bbeeb9b19f120d233728c60d179c240b974d6d2ed1fee1e1885f644710399687c932e06419c06c590becbfe6f3dabd5f60e867bf8e068eb5e296d58d54e1bbd2062b05870cf869322544e8929c1cec68a684f496319ad1e8818e801f59b4e2a7ca446c5aebfd8c1b15b9d98e07a0a9302ec23b309d301fec989e81a03b685a5013c52e58b5b9fdede13390fdb69d6bfd91a2bd76506a90981f0f084fd459435d85b55064c72a060acd56ee30441a7d83aa5914d535741951b1a6630806b74a4d4fafcf9cad622972989e7b3799d3ae0f1f5430203010001', + endPoint='172.30.93.111:40340', + groupNodeIDInfo=[ + NodeIDInfo{ + group='group0', + nodeIDList=[ + 582a3bc29ca8088ce63124c7b13d8627f931579bc3d896a983209a3b302be3f69cbd653f4bcd8ea3cdbf34d008960ee263644011af5826a7297c732b4ef4a1be + ] + } + ] + } + ] +} +``` +