# 部署示例合约 通过本文,你将可以获得一个示例合约,并使用命令行工具cmc、长安链管理台、或者SDK完成一个简单合约的部署及调用,并在相应的区块链浏览器上查看到上链信息。 ## 示例合约说明 下文以部署rust合约为例做讲解,如要部署其他合约,自行替换合约即可,其余流程皆类似。 ### 合约源码展示-rust Chainmaker 支持 Golang、Rust、TinyGo、 Solidity和C++ 多种语言的合约,本文提供的示例合约是Rust语言合约,如需查看其他合约,或者需要自行开发合约,相关内容参考:[智能合约开发](../instructions/智能合约开发.md) - 本合约为存证合约,主要实现两个功能,通过文件哈希、文件名、和时间进行存证;通过文件哈希查询链上结果进行存证核验。 - rust 合约源码如下: ```bash /// /// Copyright (C) BABEC. All rights reserved. /// /// SPDX-License-Identifier: Apache-2.0 /// use crate::easycodec::*; use crate::sim_context; use sim_context::*; // 安装合约时会执行此方法,必须 #[no_mangle] pub extern "C" fn init_contract() { // 安装时的业务逻辑,内容可为空 sim_context::log("init_contract"); } // 升级合约时会执行此方法,必须 #[no_mangle] pub extern "C" fn upgrade() { // 升级时的业务逻辑,内容可为空 sim_context::log("upgrade"); let ctx = &mut sim_context::get_sim_context(); ctx.ok("upgrade success".as_bytes()); } struct Fact { file_hash: String, file_name: String, time: i32, ec: EasyCodec, } impl Fact { fn new_fact(file_hash: String, file_name: String, time: i32) -> Fact { let mut ec = EasyCodec::new(); ec.add_string("file_hash", file_hash.as_str()); ec.add_string("file_name", file_name.as_str()); ec.add_i32("time", time); Fact { file_hash, file_name, time, ec, } } fn get_emit_event_data(&self) -> Vec { let mut arr: Vec = Vec::new(); arr.push(self.file_hash.clone()); arr.push(self.file_name.clone()); arr.push(self.time.to_string()); arr } fn to_json(&self) -> String { self.ec.to_json() } fn marshal(&self) -> Vec { self.ec.marshal() } fn unmarshal(data: &Vec) -> Fact { let ec = EasyCodec::new_with_bytes(data); Fact { file_hash: ec.get_string("file_hash").unwrap(), file_name: ec.get_string("file_name").unwrap(), time: ec.get_i32("time").unwrap(), ec, } } } // save 保存存证数据 #[no_mangle] pub extern "C" fn save() { // 获取上下文 let ctx = &mut sim_context::get_sim_context(); // 获取传入参数 let file_hash = ctx.arg_as_utf8_str("file_hash"); let file_name = ctx.arg_as_utf8_str("file_name"); let time_str = ctx.arg_as_utf8_str("time"); // 构造结构体 let r_i32 = time_str.parse::(); if r_i32.is_err() { let msg = format!("time is {:?} not int32 number.", time_str); ctx.log(&msg); ctx.error(&msg); return; } let time: i32 = r_i32.unwrap(); let fact = Fact::new_fact(file_hash, file_name, time); // 事件 ctx.emit_event("topic_vx", &fact.get_emit_event_data()); // 序列化后存储 ctx.put_state( "fact_ec", fact.file_hash.as_str(), fact.marshal().as_slice(), ); } // find_by_file_hash 根据file_hash查询存证数据 #[no_mangle] pub extern "C" fn find_by_file_hash() { // 获取上下文 let ctx = &mut sim_context::get_sim_context(); // 获取传入参数 let file_hash = ctx.arg_as_utf8_str("file_hash"); // 校验参数 if file_hash.len() == 0 { ctx.log("file_hash is null"); ctx.ok("".as_bytes()); return; } // 查询 let r = ctx.get_state("fact_ec", &file_hash); // 校验返回结果 if r.is_err() { ctx.log("get_state fail"); ctx.error("get_state fail"); return; } let fact_vec = r.unwrap(); if fact_vec.len() == 0 { ctx.log("None"); ctx.ok("".as_bytes()); return; } // 查询 let r = ctx.get_state("fact_ec", &file_hash).unwrap(); let fact = Fact::unmarshal(&r); let json_str = fact.to_json(); // 返回查询结果 ctx.ok(json_str.as_bytes()); ctx.log(&json_str); } ``` ## Cert账户模式的链部署合约 ### 使用CMC工具部署/调用合约 #### 环境准备 进行下面步骤前,请先确保已通过快速入门通过命令行工具启动链,步骤完成申请证书,启动链等过程。如未完成请先[前往完成](../quickstart/通过命令行体验链.md),如已完成,请将 chainmaker-go 的父目录当成 **$WORKDIR**。' 命令解释: - $WORKDIR 代表一个普通字符串,表示工作目录的名称,例如 test, mytest 等。 - 后面步骤中,全部的源码下载和执行,默认都是在 $WORKDIR 内执行。 #### 编译 CMC 工具源码,并配置证书 ##### 编译 CMC 工具 ```bash # 进入 CMC 代码目录 $ cd $WORKDIR/chainmaker-go/tools/cmc # 编译 CMC 命令行工具 $ go build ``` 命令解释: - 命令执行成功后,会在当前目录生成可执行文件 cmc。 ##### 为 CMC 工具配置证书 ```bash # 为 CMC 工具配置证书,以连接 chainmaker-go 节点 $ cp -r $WORKDIR/chainmaker-go/build/crypto-config $WORKDIR/chainmaker-go/tools/cmc/testdata/ ``` 命令解释: - cmc 将读取配置文件 ./testdata/sdk_config.yml 中证书相关的配置项。 - 配置文件 ./testdata/sdk_config.yml 默认将证书放在 ./testdata/crypto-config 目录下。 - 上面执行的 cp 命令将由 $WORKDIR/chainmaker-go/scripts/prepare.sh 脚本生成的证书文件复制到默认的目录。 #### 使用 CMC 工具安装一个合约 执行安装合约的 cmc 命令后,如果在终端打印出 **message:"OK"** 的输出,说明合约安装成功。 ```bash $ ./cmc client contract user create \ --contract-name=fact \ --runtime-type=WASMER \ --byte-code-path=./testdata/claim-wasm-demo/rust-fact-2.0.0.wasm \ --version=1.0 \ --sdk-conf-path=./testdata/sdk_config.yml \ --admin-key-file-paths=./testdata/crypto-config/wx-org1.chainmaker.org/user/admin1/admin1.sign.key,./testdata/crypto-config/wx-org2.chainmaker.org/user/admin1/admin1.sign.key,./testdata/crypto-config/wx-org3.chainmaker.org/user/admin1/admin1.sign.key \ --admin-crt-file-paths=./testdata/crypto-config/wx-org1.chainmaker.org/user/admin1/admin1.sign.crt,./testdata/crypto-config/wx-org2.chainmaker.org/user/admin1/admin1.sign.crt,./testdata/crypto-config/wx-org3.chainmaker.org/user/admin1/admin1.sign.crt \ --sync-result=true \ --params="{}" response: message:"OK" contract_result: tx_id:"adb92135c6c64ebc9568c0eb21366ede1cd851d9a773400fbc13ff3b9b3601b2" ``` 命令解释: - 合约名:fact - 合约类型:WASMER - 合约文件位置:./testdata/claim-wasm-demo/rust-fact-2.0.0.wasm - 函数参数:空 #### 使用 CMC 工具调用一个合约 执行调用合约的 cmc 命令后,如果在终端打印出 **[code:0]/[msg:OK]** 的输出,说明合约调用成功。 ```bash $ ./cmc client contract user invoke \ --contract-name=fact \ --method=save \ --sdk-conf-path=./testdata/sdk_config.yml \ --params="{\"file_name\":\"name007\",\"file_hash\":\"ab3456df5799b87c77e7f88\",\"time\":\"6543234\"}" \ --sync-result=true INVOKE contract resp, [code:0]/[msg:OK]/[contractResult:gas_used:13383607 contract_event: ]/[txId:e6b1b398c758410496221339b8e9a995ad4c64a9c80344ecb2e9a69d3dddde39] ``` 命令解释: - 合约名:fact - 合约类型:WASMER - 调用方法名:save - 函数参数:{"file_name": "name007", "file_hash":"ab3456df5799b87c77e7f88", "time": "6543234"}
#### 使用 cmc 工具查询交易信息 ```bash $./cmc query tx [txid] \ --chain-id=chain1 \ --sdk-conf-path=./testdata/sdk_config.yml ``` 命令解释: - [txid]:交易序列号,成功执行部署合约、执行合约的命令后,返回的交易标识(tx_id字段) - chain-id: 链号,在 chainmaker.yml 配置文件中设定 ### 在区块链浏览器上查看交易信息 每次部署或者调用合约,都会产生相关都交易信息,您可以在区块链浏览器的交易详情里查看具体信息,如果您尚未部署区块链浏览器,请[前往部署](../dev/区块链浏览器.md)。 ### 使用管理台部署/调用合约 #### 环境准备 进行下面步骤前,请先确保已通过快速入门通过管理台启动链,完成申请证书,启动链、订阅链等过程。如未完成请先[前往完成](../quickstart/通过管理台体验链.md)。 #### 合约可部署文件下载 此处提供示例合约的已编译之后的合约文件,可直接下载文件并通过管理台部署合约。 - Rust:[rust-fact-2.0.0.wasm](https://git.chainmaker.org.cn/chainmaker/chainmaker-go/-/raw/v2.2.0/test/wasm/rust-fact-2.0.0.wasm) #### 通过管理台,部署合约 - 在长安链管理台-区块链管理-合约管理模块的部署合约功能里,上传所下载的合约文件即可部署合约。 - 部署合约时,如果是Solidity合约,需要一并上传abi文件,管理台会根据abi文件自动解析出合约调用方法。 - 其他类型的合约可手动录入调用方法,以便后面引用。 - 上文示例合约的调用方法如下 | 方法名称 | Method | Param | |----------------|---------------------|-----------------------------| | 存证方法 | save | file_hash, file_name, time | | 查询方法 | find_by_file_hash | file_hash | - 通过管理平台起的长安链,如您未特殊修改,一般部署的权限为Majority,故您在合约管理模块部署完成后,还需要到区块链管理-投票管理处,进行多签投票,当满足Majority条件后,所部署的合约才生效。 #### 通过管理台,调用合约 在长安链管理台-区块链管理-上链管理模块的发起上链功能里,可输入参数对已部署的合约进行调用。 #### 通过管理台,查看交易信息 每次部署或者调用合约,都会产生相关都交易信息,您可以在管理台-区块链管理-区块链浏览器模块中交易详情里查看具体信息。 ### 使用SDK部署/调用合约 下文以GO-SDK为例,其他SDK类似,可自行借鉴 #### 源码下载 > 在chainmaker-go工程同级目录,执行下面命令获取go sdk ``` $ git clone -b v2.3.2 --depth=1 https://git.chainmaker.org.cn/chainmaker/sdk-go.git ``` #### 证书关联 > 将通过prepare.sh工具生成的crypto-config目录,软连接到sdk-go/testdata目录 ``` $ cd sdk-go/testdata # 这里我们使用新生成的用户证书,请先将testdata已有的crypto-config移除 $ /bin/rm -rf crypto-config # 软连接使用prepare.sh脚本生成的证书目录 $ ln -s ../../chainmaker-go/build/crypto-config/ . ``` #### 配置修改 >修改sdk单元测试使用的配置文件:sdk-go/testdata/sdk_config.yml 根据需要修改节点地址: ``` nodes: - # 节点地址,格式为:IP:端口:连接数 node_addr: "127.0.0.1:12301" ``` 如果证书路径有调整,修改对应的证书路径配置: ``` # 客户端用户私钥路径 user_key_file_path: "./testdata/crypto-config/wx-org1.chainmaker.org/user/client1/client1.tls.key # 客户端用户证书路径 user_crt_file_path: "./testdata/crypto-config/wx-org1.chainmaker.org/user/client1/client1.tls.crt" # 客户端用户交易签名私钥路径(若未设置,将使用user_key_file_path) user_sign_key_file_path: "./testdata/crypto-config/wx-org1.chainmaker.org/user/client1/client1.sign.key" # 客户端用户交易签名证书路径(若未设置,将使用user_crt_file_path) user_sign_crt_file_path: "./testdata/crypto-config/wx-org1.chainmaker.org/user/client1/client1.sign.crt" ``` #### 执行存证合约example 该单测会进行存证合约的部署、调用和查询。 ``` $ cd sdk-go/examples/user_contract_claim $ go run main.go ``` 看到success或OK等类似输出,说明功能验证成功。 ## PK账户模式的链部署合约 ### 使用CMC工具部署/调用合约 #### 修改CMC工具配置 其中,cmc工具配置文件sdk_config_pk.yaml需要进行相应修改,主要修改以下配置: - 客户端私钥:user_sign_key_file_path ```yaml chain_client: # 链ID chain_id: "chain1" # 客户端用户交易签名私钥路径 user_sign_key_file_path: "./testdata/crypto-config/node1/admin/admin1/admin1.key" # 管理员1私钥 # 签名使用的哈希算法,和节点保持一直 crypto: hash: SHA256 auth_type: public # 默认支持TimestampKey,如果开启enableNormalKey则使用NormalKey enable_normal_key: false nodes: - # 节点地址,格式为:IP:端口:连接数 node_addr: "127.0.0.1:12301" # 节点连接数 conn_cnt: 10 - # 节点地址,格式为:IP:端口:连接数 node_addr: "127.0.0.1:12302" # 节点连接数 conn_cnt: 10 archive: # 数据归档链外存储相关配置 type: "mysql" dest: "root:123456:localhost:3306" secret_key: xxx rpc_client: max_receive_message_size: 16 # grpc客户端接收消息时,允许单条message大小的最大值(MB) max_send_message_size: 16 # grpc客户端发送消息时,允许单条message大小的最大值(MB) ``` 注:由于我们使用管理员账户进行测试,所以user_sign_key_file_path需要指定管理员私钥 #### 部署合约,调用合约的CMC命令 **管理员创建合约** ```shell ./cmc client contract user create \ --contract-name=fact \ --runtime-type=WASMER \ --byte-code-path=./testdata/claim-wasm-demo/rust-fact-2.0.0.wasm \ --version=1.0 \ --sdk-conf-path=./testdata/sdk_config_pk.yml \ --admin-key-file-paths=./testdata/crypto-config/node1/admin/admin1/admin1.key \ --sync-result=true \ --params="{}" ``` **管理员调用合约** ./cmc client contract user invoke \ --contract-name=fact \ --method=save \ --sdk-conf-path=./testdata/sdk_config_pk.yml \ --params="{\"file_name\":\"name007\",\"file_hash\":\"ab3456df5799b87c77e7f88\",\"time\":\"6543234\"}" \ --sync-result=true \ --admin-key-file-paths=./testdata/crypto-config/node1/admin/admin1/admin1.key \ ### 使用管理台部署合约 PK账户模式的部署合约的方式和Cert账户模式的链一致,可参看上文Cert模式链如何在管理台内部署合约,将链订阅到管理台后,直接部署即可。 ### 使用SDK部署/调用合约,待补充