1. 智能合约

“长安链·ChainMaker”目前已经支持使用C++、Go、Rust、Solidity进行智能合约开发,很快将支持AssemblyScript(JavaScript)。

本章节将介绍各种语言的合约的编写环境、编写、编译等相关知识。

将合约编译为wasm文件后,可以使用命令行工具安装、调用、查询合约,请参看:【命令行工具】,也可使用SDK进行合约的安装、调用、查询,请参看:【SDK】

1.1. 约束条件和已知问题

  • 在安装CPP智能合约时,要求共识节点、非共识节点必须安装GCC。

  • TinyGo对wasm的支持不太完善,对内存逃逸分析、GC等方面有不足之处,比较容易造成栈溢出。在开发合约时,应尽可能减少循环、内存申请等业务逻辑,使变量的栈内存地址在64K以内,要求tinygo version >= 0.17.0,推荐使用0.17.0。

  • TinyGo对导入的包支持有限,请参考:https://tinygo.org/lang-support/stdlib/ 对列表中显示已支持的包,实际测试发现支持的并不完整,会发生一些错误,需要在实际开发过程中进行测试检验。

  • TinyGo引擎不支持fmtstrconv包。

  • 所有合约不能使用随机数,签名(部分签名算法也会引用随机数)功能,避免无法达成共识

1.2. 合约开发语言和虚拟机

“长安链·ChainMaker”目前已经支持使用C++、TinyGo、Rust、Solidity、原生Go进行智能合约开发,每种开发语言实现的合约由不同的虚拟机执行,在将合约发布到链上时通过Runtime Type来指定虚拟机类型,语言和类型的对应关系如下:

语言 类型
系统合约 RuntimeType_NATIVE = 1
rust RuntimeType_WASMER = 2
c++ RuntimeType_WXVM = 3
tinygo RuntimeType_GASM = 4
solidity RuntimeType_EVM = 5
go RuntimeType_DOCKER_GO = 6

1.3. 使用Rust进行智能合约开发

读者对象:本章节主要描述使用Rust进行ChainMaker合约编写的方法,主要面向于使用Rust进行ChainMaker的合约开发的开发者。

rust 安装及教程请参考:rust 官网

1.3.1. 使用Docker镜像进行合约开发

ChainMaker官方已经将容器发布至 docker hub

拉取镜像

docker pull chainmakerofficial/chainmaker-rust-contract:2.1.0

请指定你本机的工作目录$WORK_DIR,例如/data/workspace/contract,挂载到docker容器中以方便后续进行必要的一些文件拷贝

docker run -it --name chainmaker-rust-contract -v $WORK_DIR:/home chainmakerofficial/chainmaker-rust-contract:2.1.0 bash
# 或者先后台启动
docker run -d  --name chainmaker-rust-contract -v $WORK_DIR:/home chainmakerofficial/chainmaker-rust-contract:2.1.0 bash -c "while true; do echo hello world; sleep 5;done"
# 再进入容器
docker exec -it chainmaker-rust-contract bash

编译合约

cd /home/
tar xvf /data/contract_rust_template.tar.gz
cd contract_rust
make build

生成合约的字节码文件在

/home/contract_rust/target/wasm32-unknown-unknown/release/chainmaker_contract.wasm

1.3.1.1. 框架描述

解压缩contract_rust_template.tar.gz后,文件描述如下:

chainmaker-contract-sdk-rust$ tree -I target
.
├── Cargo.lock # 依赖版本信息
├── Cargo.toml # 项目配置及依赖,参考:https://rustwasm.github.io/wasm-pack/book/cargo-toml-configuration.html
├── Makefile   # build一个wasm文件
├── README.md  # 编译环境说明
├── src
│   ├── contract_fact.rs			# 存证示例代码
│   ├── easycodec.rs                # 序列化工具类
│   ├── lib.rs                      # 程序入口
│   ├── sim_context.rs              # 合约SDK主要接口及实现
│   ├── sim_context_bulletproofs.rs # 合约SDK基于bulletproofs的范围证明接口实现
│   ├── sim_context_paillier.rs     # 合约SDK基于paillier的半同态运算接口实现
│   ├── sim_context_rs.rs           # 合约SDK sql接口实现
│   └── vec_box.rs                  # 内存管理类

1.3.1.2. 示例代码说明

1.3.1.2.1. 存证合约

存证合约示例:contract_fact.rs 实现如下两个功能

1、存储文件哈希和文件名称和时间。

2、通过文件哈希查询该条记录

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<String> {
        let mut arr: Vec<String> = 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<u8> {
        self.ec.marshal()
    }

    fn unmarshal(data: &Vec<u8>) -> 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_default_blank("file_hash");
    let file_name = ctx.arg_default_blank("file_name");
    let time_str = ctx.arg_default_blank("time");

    // 构造结构体
    let r_i32 = time_str.parse::<i32>();
    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_default_blank("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);
}
1.3.1.2.2. 迭代器使用

点击此处查看接口说明

使用示例如下:

#[no_mangle]
pub extern "C" fn how_to_use_iterator() {
    let ctx = &mut sim_context::get_sim_context();

    // 构造数据
    ctx.put_state("key1", "field1", "val".as_bytes());
    ctx.put_state("key1", "field2", "val".as_bytes());
    ctx.put_state("key1", "field23", "val".as_bytes());
    ctx.put_state("key1", "field3", "val".as_bytes());
    // 使用迭代器,能查出来  field1,field2,field23 三条数据
    let r = ctx.new_iterator_with_field("key1", "field1", "field3");
    if r.is_ok() {
        let rs = r.unwrap();
        // 遍历
        while rs.has_next() {
            // 获取下一行值
            let row = rs.next_row().unwrap();
            let key = row.get_string("key").unwrap();
            let field = row.get_bytes("field");
            let val = row.get_bytes("value");
            // do something
        }
        // 关闭游标
        rs.close();
    }

    ctx.put_state("key2", "field1", "val".as_bytes());
    ctx.put_state("key3", "field2", "val".as_bytes());
    ctx.put_state("key33", "field2", "val".as_bytes());
    ctx.put_state("key4", "field3", "val".as_bytes());
    // 能查出来 key2,key3,key33 三条数据
    ctx.new_iterator("key2", "key4");
    // 能查出来 key3,key33 两条数据
    ctx.new_iterator_prefix_with_key("key3");
    // 能查出来  field2,field23 三条数据
    ctx.new_iterator_prefix_with_key_field("key1", "field2");
    
    
    ctx.put_state_from_key("key5","val".as_bytes());
    ctx.put_state_from_key("key56","val".as_bytes());
    ctx.put_state_from_key("key6","val".as_bytes());
    // 能查出来 key5,key56 两条数据
    ctx.new_iterator("key5", "key6");
}

1.3.1.3. 代码编写规则

对链暴露方法写法为:

  • #[no_mangle] 表示方法名编译后是固定的,不写会生成 _ZN4rustfn1_34544tert54grt5 类似的混淆名

  • pub extern “C”

  • method_name(): 不可带参数,无返回值

#[no_mangle]// no_mangle注解,表明对外暴露方法名称不可变
pub extern "C" fn init_contract() { // pub extern "C" 集成C
    // do something 
}

其中init_contract、upgrade方法必须有且对外暴露

  • init_contract:创建合约会执行该方法

  • upgrade: 升级合约会执行该方法

// 安装合约时会执行此方法。ChainMaker不允许用户直接调用该方法。
#[no_mangle]
pub extern "C" fn init_contract() {
    // dosome thing
}

// 升级合约时会执行此方法。ChainMaker不允许用户直接调用该方法。
#[no_mangle]
pub extern "C" fn upgrade() {
    
}

获取与链交互的上下文sim_context

1、在lib.rs中引入sim_context及其依赖

2、在lib.rs中引入自己合约文件,如:contract_fact,如下

// sdk
pub mod sim_context;
pub mod sim_context_bulletproofs;
pub mod sim_context_paillier;
pub mod sim_context_rs;
pub mod easycodec;
pub mod vec_box;
// contract
pub mod contract_fact;

3、在使用时引入sim_context

use crate::sim_context;
use sim_context::*;

fn method_name() {
    // 获取上下文
    let ctx = &mut sim_context::get_sim_context();
    
}

1.3.1.4. 编译说明

在ChainMaker IDE中集成了编译器,可以对合约进行编译,集成的rust编译器是rustc: 1.48.0,wasm编译器是wasm-pack: 0.9.1, 采用默认cargo管理包,版本为cargo: 1.49.0, 默认提供base64库,合约支持在线其他轻量级序列化方式。用户如果手工编译需在项目根目录执行命令: make或者wasm-pack build --release,生成的wasm文件所在目录为:target\wasm32-unknown-unknown\release\chainmaker_contract.wasm。

1.3.2. 合约发布过程

请参考:《chainmaker-go-sdk》发送创建合约请求的部分,或者《chainmaker-java-sdk》创建合约的部分。

1.3.3. 合约调用过程

请参考:《chainmaker-go-sdk》合约调用的部分,或者《chainmaker-java-sdk》执行合约的部分。

1.3.4. Rust SDK API描述

1.3.4.1. 内置链交互接口

用于链与SDK数据交互,用户无需关心。

// 申请size大小内存,返回该内存的首地址
pub extern "C" fn allocate(size: usize) -> i32 {}
// 释放某地址
pub extern "C" fn deallocate(pointer: *mut c_void) {}
// 获取SDK运行时环境
pub extern "C" fn runtime_type() -> i32 {}

1.3.4.2. 用户与链交互接口

/// SimContext is a interface with chainmaker interaction
pub trait SimContext {
    // common method
    fn call_contract(
        &self,
        contract_name: &str,
        method: &str,
        param: EasyCodec,
    ) -> Result<Vec<u8>, result_code>;
    fn ok(&self, value: &[u8]) -> result_code;
    fn error(&self, body: &str) -> result_code;
    fn log(&self, msg: &str);
    fn arg(&self, key: &str) -> Result<Vec<u8>, String>;
    fn arg_as_utf8_str(&self, key: &str) -> String;
    fn args(&self) -> &EasyCodec;
    fn get_creator_org_id(&self) -> String;
    fn get_creator_pub_key(&self) -> String;
    fn get_creator_role(&self) -> String;
    fn get_sender_org_id(&self) -> String;
    fn get_sender_pub_key(&self) -> String;
    fn get_sender_role(&self) -> String;
    fn get_block_height(&self) -> u64;
    fn get_tx_id(&self) -> String;
    fn emit_event(&mut self, topic: &str, data: &Vec<String>) -> result_code;
    // paillier
    fn get_paillier_sim_context(&self) -> Box<dyn PaillierSimContext>;
    // bulletproofs
    fn get_bulletproofs_sim_context(&self) -> Box<dyn BulletproofsSimContext>;
    // sql
    fn get_sql_sim_context(&self) -> Box<dyn SqlSimContext>;

    // KV method
    fn get_state(&self, key: &str, field: &str) -> Result<Vec<u8>, result_code>;
    fn get_state_from_key(&self, key: &str) -> Result<Vec<u8>, result_code>;
    fn put_state(&self, key: &str, field: &str, value: &[u8]) -> result_code;
    fn put_state_from_key(&self, key: &str, value: &[u8]) -> result_code;
    fn delete_state(&self, key: &str, field: &str) -> result_code;
    fn delete_state_from_key(&self, key: &str) -> result_code;

    /// new_iterator range of [startKey, limitKey), front closed back open
    fn new_iterator(
        &self,
        start_key: &str,
        limit_key: &str,
    ) -> Result<Box<dyn ResultSet>, result_code>;

    /// new_iterator_with_field range of [key+"#"+startField, key+"#"+limitField), front closed back open
    fn new_iterator_with_field(
        &self,
        key: &str,
        start_field: &str,
        limit_field: &str,
    ) -> Result<Box<dyn ResultSet>, result_code>;

    /// new_iterator_prefix_with_key_field range of [key+"#"+field, key+"#"+field], front closed back closed
    fn new_iterator_prefix_with_key_field(
        &self,
        key: &str,
        field: &str,
    ) -> Result<Box<dyn ResultSet>, result_code>;

    /// new_iterator_prefix_with_key range of [key, key], front closed back closed
    fn new_iterator_prefix_with_key(&self, key: &str) -> Result<Box<dyn ResultSet>, result_code>;
}


pub trait SqlSimContext {
    fn execute_query_one(&self, sql: &str) -> Result<EasyCodec, result_code>;
    fn execute_query(&self, sql: &str) -> Result<Box<dyn ResultSet>, result_code>;

    /// #### ExecuteUpdateSql execute update/insert/delete sql
    /// ##### It is best to update with primary key
    ///
    /// as:
    ///
    /// - update table set name = 'Tom' where uniqueKey='xxx'
    /// - delete from table where uniqueKey='xxx'
    /// - insert into table(id, xxx,xxx) values(xxx,xxx,xxx)
    ///
    /// ### not allow:
    /// - random methods: NOW() RAND() and so on
    fn execute_update(&self, sql: &str) -> Result<i32, result_code>;

    /// ExecuteDDLSql execute DDL sql, for init_contract or upgrade method. allow table create/alter/drop/truncate
    ///
    /// ## You must have a primary key to create a table
    /// ### allow:     
    /// - CREATE TABLE tableName
    /// - ALTER TABLE tableName
    /// - DROP TABLE tableName   
    /// - TRUNCATE TABLE tableName
    ///
    /// ### not allow:
    /// - CREATE DATABASE dbName
    /// - CREATE TABLE dbName.tableName
    /// - ALTER TABLE dbName.tableName
    /// - DROP DATABASE dbName   
    /// - DROP TABLE dbName.tableName   
    /// - TRUNCATE TABLE dbName.tableName
    /// not allow:
    /// - random methods: NOW() RAND() and so on
    ///
    fn execute_ddl(&self, sql: &str) -> Result<i32, result_code>;
}


pub trait PaillierSimContext {
    // Paillier method
    fn add_ciphertext(
        &self,
        pubkey: Vec<u8>,
        ciphertext1: Vec<u8>,
        ciphertext2: Vec<u8>,
    ) -> Result<Vec<u8>, result_code>;
    fn add_plaintext(
        &self,
        pubkey: Vec<u8>,
        ciphertext: Vec<u8>,
        plaintext: &str,
    ) -> Result<Vec<u8>, result_code>;
    fn sub_ciphertext(
        &self,
        pubkey: Vec<u8>,
        ciphertext1: Vec<u8>,
        ciphertext2: Vec<u8>,
    ) -> Result<Vec<u8>, result_code>;
    fn sub_plaintext(
        &self,
        pubkey: Vec<u8>,
        ciphertext: Vec<u8>,
        plaintext: &str,
    ) -> Result<Vec<u8>, result_code>;
    fn num_mul(
        &self,
        pubkey: Vec<u8>,
        ciphertext: Vec<u8>,
        plaintext: &str,
    ) -> Result<Vec<u8>, result_code>;
}
/// BulletproofsSimContext is the trait that wrap the bulletproofs method
pub trait BulletproofsSimContext {
    /// Compute a commitment to x + y from a commitment to x without revealing the value x, where y is a scalar
    ///
    /// # Arguments
    ///
    /// * `commitment` - C = xB + rB'
    /// * `num` - the value y
    ///
    /// # return
    ///
    /// * `return1` - the new commitment to x + y: C' = (x + y)B + rB'
    ///
    fn pedersen_add_num(&self, commitment: Vec<u8>, num: &str) -> Result<Vec<u8>, result_code>;

    /// Compute a commitment to x + y from commitments to x and y, without revealing the value x and y
    ///
    /// # Arguments
    ///
    /// * `commitment1` - commitment to x: Cx = xB + rB'
    /// * `commitment2` - commitment to y: Cy = yB + sB'
    ///
    /// # return
    ///
    /// * `return1` - commitment to x + y: C = (x + y)B + (r + s)B'
    ///
    fn pedersen_add_commitment(
        &self,
        commitment1: Vec<u8>,
        commitment2: Vec<u8>,
    ) -> Result<Vec<u8>, result_code>;

    /// Compute a commitment to x - y from a commitment to x without revealing the value x, where y is a scalar
    ///
    /// # Arguments
    ///
    /// * `commitment1` - C = xB + rB'
    /// * `num` - the value y
    ///
    /// # return
    ///
    /// * `return1` - the new commitment to x - y: C' = (x - y)B + rB'
    fn pedersen_sub_num(&self, commitment: Vec<u8>, num: &str) -> Result<Vec<u8>, result_code>;

    /// Compute a commitment to x - y from commitments to x and y, without revealing the value x and y
    ///
    /// # Arguments
    ///
    /// * `commitment1` - commitment to x: Cx = xB + rB'
    /// * `commitment2` - commitment to y: Cy = yB + sB'
    ///
    /// # return
    ///
    /// * `return1` - commitment to x - y: C = (x - y)B + (r - s)B'
    fn pedersen_sub_commitment(
        &self,
        commitment1: Vec<u8>,
        commitment2: Vec<u8>,
    ) -> Result<Vec<u8>, result_code>;

    /// Compute a commitment to x * y from a commitment to x and an integer y, without revealing the value x and y
    ///
    /// # Arguments
    ///
    /// * `commitment1` - commitment to x: Cx = xB + rB'
    /// * `num` - integer value y
    ///
    /// # return
    ///
    /// * `return1` - commitment to x * y: C = (x * y)B + (r * y)B'
    fn pedersen_mul_num(&self, commitment: Vec<u8>, num: &str) -> Result<Vec<u8>, result_code>;

    /// Verify the validity of a proof
    ///
    /// # Arguments
    ///
    /// * `proof` - the zero-knowledge proof proving the number committed in commitment is in the range [0, 2^64)
    /// * `commitment` - commitment bindingly hiding the number x
    ///
    /// # return
    ///
    /// * `return1` - true on valid proof, false otherwise
    fn verify(&self, proof: Vec<u8>, commitment: Vec<u8>) -> Result<Vec<u8>, result_code>;
}

get_state

// 获取合约账户信息。该接口可从链上获取类别 “key” 下属性名为 “field” 的状态信息。
// @param key: 需要查询的key值
// @param field: 需要查询的key值下属性名为field
// @return: 查询到的value值,及错误代码 0: success, 1: failed
fn get_state(&self, key: &str, field: &str) -> Result<Vec<u8>, result_code>;

get_state_from_key

// 获取合约账户信息。该接口可以从链上获取类别为key的状态信息
// @param key: 需要查询的key值
// @return1: 查询到的值
// @return2:  0: success, 1: failed
fn get_state_from_key(&self, key: &str) -> Result<Vec<u8>, result_code>;

put_state

// 写入合约账户信息。该接口可把类别 “key” 下属性名为 “filed” 的状态更新到链上。更新成功返回0,失败则返回1。
// @param key: 需要存储的key值
// @param field: 需要存储的key值下属性名为field
// @param value: 需要存储的value值
// @return: 0: success, 1: failed
fn put_state(&self, key: &str, field: &str, value: &[u8]) -> result_code;

put_state_from_key

// 写入合约账户信息。
// @param key: 需要存储的key值
// @param value: 需要存储的value值
// @return: 0: success, 1: failed
fn put_state_from_key(&self, key: &str, value: &[u8]) -> result_code;

delete_state

// 删除合约账户信息。该接口可把类别 “key” 下属性名为 “name” 的状态从链上删除。
// @param key: 需要删除的key值
// @param field: 需要删除的key值下属性名为field
// @return: 0: success, 1: failed
fn delete_state(&self, key: &str, field: &str) -> result_code;

delete_state_from_key

// 删除合约账户信息。该接口可把类别 “key” 下属性名为 “name” 的状态从链上删除。
// @param key: 需要删除的key值
// @return: 0: success, 1: failed
fn delete_state_from_key(&self, key: &str) -> result_code;

call_contract

// 跨合约调用
// @param contract_name: 合约名称
// @param method: 合约方法
// @param EasyCodec: 合约参数
fn call_contract(&self, contract_name: &str, method: &str, param: EasyCodec) -> Result<Vec<u8>, result_code>;

args

// @return: EasyCodec
fn args(&self) -> &EasyCodec;

arg

// 该接口可返回属性名为 “key” 的参数的属性值。
// @param key: 获取的参数名
// @return: 获取的参数值 或 错误信息。当未传该key的值时,报错param not found
fn arg(&self, key: &str) -> Result<Vec<u8>, String>;

ok

// 该接口可记录用户操作成功的信息,并将操作结果记录到链上。
// @param body: 成功返回的信息
fn ok(&self, value: &[u8]) -> result_code;

error

// 该接口可记录用户操作失败的信息,并将操作结果记录到链上。
// @param body: 失败信息
fn error(&self, body: &str) -> result_code;

log

// 该接口可记录事件日志。查看方式为在链配置的log.yml中,开启vm:debug即可看到类似:wasmer log>> + msg
// @param msg: 事件信息
fn log(&self, msg: &str);

get_creator_org_id

// 获取合约创建者所属组织ID
// @return: 合约创建者的组织ID
fn get_creator_org_id(&self) -> String;

get_creator_role

// 获取合约创建者角色
// @return: 合约创建者的角色
fn get_creator_role(&self) -> String;

get_creator_pub_key

// 获取合约创建者公钥
// @return: 合约创建者的公钥的SKI
fn get_creator_pub_key(&self) -> String;

get_sender_org_id

// 获取交易发起者所属组织ID
// @return: 交易发起者的组织ID
fn get_sender_org_id(&self) -> String;

get_sender_role

// 获取交易发起者角色
// @return: 交易发起者角色
fn get_sender_role(&self) -> String;

get_sender_pub_key()

// 获取交易发起者公钥
// @return 交易发起者的公钥的SKI
fn get_sender_pub_key(&self) -> String;

get_block_height

// 获取当前区块高度
// @return: 当前块高度
fn get_block_height(&self) -> i32;

get_tx_id

// 获取交易ID
// @return 交易ID
fn get_tx_id(&self) -> String;

emit_event

// 发送合约事件
// @param topic: 合约事件主题
// @data: 合约事件数据,vertor中事件数据个数不可大于16,不可小于1
fn emit_event(&mut self, topic: &str, data: &Vec<String>) -> result_code;

new_iterator

/// new_iterator range of [startKey, limitKey), front closed back open
// 新建key范围迭代器,key前闭后开,即:start_key <= dbkey < limit_key
// @param start_key: 开始的key
// @param limit_key: 结束的key
// @return: 结果集游标
fn new_iterator(&self,start_key: &str,limit_key: &str) -> Result<Box<dyn ResultSet>, result_code>;

/// new_iterator_with_field range of [key+"#"+startField, key+"#"+limitField), front closed back open
// 新建field范围迭代器,key需相同,field前闭后开,即:key = dbdbkey and start_field <= dbfield < limit_field
// @param key: 固定key
// @param start_field: 开始的field
// @param limit_field: 结束的field
// @return: 结果集游标
fn new_iterator_with_field(&self, key: &str, start_field: &str, limit_field: &str) -> Result<Box<dyn ResultSet>, result_code>;

/// new_iterator_prefix_with_key range of [key, key], front closed back closed
// 新建指定key前缀匹配迭代器,key需前缀一致,即dbkey.startWith(key)
// @param key: key前缀
// @return: 结果集游标
fn new_iterator_prefix_with_key(&self, key: &str) -> Result<Box<dyn ResultSet>, result_code>;

/// new_iterator_prefix_with_key_field range of [key+"#"+field, key+"#"+field], front closed back closed
// 新建指定field前缀匹配迭代器,key需相同,field前缀一致,即dbkey = key and dbfield.startWith(field)
// @param key: key前缀
// @return: 结果集游标
fn new_iterator_prefix_with_key_field(&self, key: &str, field: &str) -> Result<Box<dyn ResultSet>, result_code>;

1.4. 使用Go(TinyGo)进行智能合约开发

读者对象:本章节主要描述使用Go进行ChainMaker合约编写的方法,主要面向于使用Go进行ChainMaker的合约开发的开发者。为了最小化wasm文件尺寸,使用的是TinyGO编译器。

1.4.1. 使用Docker镜像进行合约开发

ChainMaker官方已经将容器发布至 https://hub.docker.com/u/chainmakerofficial

拉取镜像

docker pull chainmakerofficial/chainmaker-go-contract:2.1.0

请指定你本机的工作目录$WORK_DIR,例如/data/workspace/contract,挂载到docker容器中以方便后续进行必要的一些文件拷贝

docker run -it --name chainmaker-go-contract -v $WORK_DIR:/home chainmakerofficial/chainmaker-go-contract:2.1.0 bash
# 或者先后台启动
docker run -d  --name chainmaker-go-contract -v $WORK_DIR:/home chainmakerofficial/chainmaker-go-contract:2.1.0 bash -c "while true; do echo hello world; sleep 5;done"
# 再进入容器
docker exec -it chainmaker-go-contract bash

编译合约

cd /home/
# 解压缩合约SDK源码
tar xvf /data/contract_go_template.tar.gz
cd contract_tinygo
# 编译main.go合约
sh build.sh

生成合约的字节码文件在

/home/contract_tinygo/chainmaker-contract-go.wasm

1.4.1.1. 框架描述

解压缩contract_go_template.tar.gz后,文件描述如下:

/home/contract_tinygo# ls -l
total 64
-rw-rw-r-- 1 1000 1000    56 Jul  2 12:45 build.sh            	# 编译脚本
-rw-rw-r-- 1 1000 1000  4149 Jul  2 12:44 bulletproofs.go		# 合约SDK基于bulletproofs的范围证明接口实现
-rw-rw-r-- 1 1000 1000 18871 Jul  2 12:44 chainmaker.go			# 合约SDK主要接口及实现
-rw-rw-r-- 1 1000 1000  4221 Jul  2 12:44 chainmaker_rs.go		# 合约SDK sql接口实现
-rw-rw-r-- 1 1000 1000 11777 May 24 13:27 easycodec.go			# 序列化工具类
-rw-rw-r-- 1 1000 1000  3585 Jul  2 12:44 main.go				# 存证示例代码
-rwxr-xr-x 1 root root 65122 Jul  6 07:22 main.wasm				# 编译成功后的wasm文件
-rw-rw-r-- 1 1000 1000  1992 Jul  2 12:44 paillier.go 			# 合约SDK基于paillier的半同态运算接口实现

1.4.1.2. 示例代码说明

1.4.1.2.1. 存证合约示例

实现功能:

1、存储文件哈希和文件名称和该交易的ID。

2、通过文件哈希查询该条记录

/*
Copyright (C) BABEC. All rights reserved.

SPDX-License-Identifier: Apache-2.0

一个 文件存证 的存取示例 fact

*/

package main

import (
  "chainmaker.org/contract-sdk-tinygo/sdk/convert"
)

// 安装合约时会执行此方法,必须
//export init_contract
func initContract() {
  // 此处可写安装合约的初始化逻辑

}

// 升级合约时会执行此方法,必须
//export upgrade
func upgrade() {
  // 此处可写升级合约的逻辑

}

// 存证对象
type Fact struct {
  fileHash string
  fileName string
  time     int32 // second
  ec       *EasyCodec
}

// 新建存证对象
func NewFact(fileHash string, fileName string, time int32) *Fact {
  fact := &Fact{
    fileHash: fileHash,
    fileName: fileName,
    time:     time,
  }
  return fact
}

// 获取序列化对象
func (f *Fact) getEasyCodec() *EasyCodec {
  if f.ec == nil {
    f.ec = NewEasyCodec()
    f.ec.AddString("fileHash", f.fileHash)
    f.ec.AddString("fileName", f.fileName)
    f.ec.AddInt32("time", f.time)
  }
  return f.ec
}

// 序列化为json字符串
func (f *Fact) toJson() string {
  return f.getEasyCodec().ToJson()
}

// 序列化为cmec编码
func (f *Fact) marshal() []byte {
  return f.getEasyCodec().Marshal()
}

// 反序列化cmec为存证对象
func unmarshalToFact(data []byte) *Fact {
  ec := NewEasyCodecWithBytes(data)
  fileHash, _ := ec.GetString("fileHash")
  fileName, _ := ec.GetString("fileName")
  time, _ := ec.GetInt32("time")

  fact := &Fact{
    fileHash: fileHash,
    fileName: fileName,
    time:     time,
    ec:       ec,
  }
  return fact
}

// 对外暴露 save 方法,供用户由 SDK 调用
//export save
func save() {
  // 获取上下文
  ctx := NewSimContext()

  // 获取参数
  fileHash, err1 := ctx.ArgString("file_hash")
  fileName, err2 := ctx.ArgString("file_name")
  timeStr, err3 := ctx.ArgString("time")

  if err1 != SUCCESS || err2 != SUCCESS || err3 != SUCCESS {
    ctx.Log("get arg fail.")
    ctx.ErrorResult("get arg fail.")
    return
  }

  time, err := convert.StringToInt32(timeStr)
  if err != nil {
    ctx.ErrorResult(err.Error())
    ctx.Log(err.Error())
    return
  }

  // 构建结构体
  fact := NewFact(fileHash, fileName, int32(time))

  // 序列化:两种方式
  jsonStr := fact.toJson()
  bytesData := fact.marshal()

  //发送事件
  ctx.EmitEvent("topic_vx", fact.fileHash, fact.fileName)

  // 存储数据
  ctx.PutState("fact_json", fact.fileHash, jsonStr)
  ctx.PutStateByte("fact_bytes", fact.fileHash, bytesData)

  // 记录日志
  ctx.Log("【save】 fileHash=" + fact.fileHash)
  ctx.Log("【save】 fileName=" + fact.fileName)
  // 返回结果
  ctx.SuccessResult(fact.fileName + fact.fileHash)
}

// 对外暴露 find_by_file_hash 方法,供用户由 SDK 调用
//export find_by_file_hash
func findByFileHash() {
  ctx := NewSimContext()
  // 获取参数
  fileHash, _ := ctx.ArgString("file_hash")
  // 查询Json
  if result, resultCode := ctx.GetStateByte("fact_json", fileHash); resultCode != SUCCESS {
    // 返回结果
    ctx.ErrorResult("failed to call get_state, only 64 letters and numbers are allowed. got key:" + "fact" + ", field:" + fileHash)
  } else {
    // 返回结果
    ctx.SuccessResultByte(result)
    // 记录日志
    ctx.Log("get val:" + string(result))
  }

  // 查询EcBytes
  if result, resultCode := ctx.GetStateByte("fact_bytes", fileHash); resultCode == SUCCESS {
    // 反序列化
    fact := unmarshalToFact(result)
    // 返回结果
    ctx.SuccessResult(fact.toJson())
    // 记录日志
    ctx.Log("get val:" + fact.toJson())
    ctx.Log("【find_by_file_hash】 fileHash=" + fact.fileHash)
    ctx.Log("【find_by_file_hash】 fileName=" + fact.fileName)
  }
}

func main() {

}
1.4.1.2.2. 迭代器使用示例
//export test_kv_iterator
func howToUseIterator() {
	ctx := NewSimContext()
    // 构造数据
	ctx.PutState("key1", "field1", "val")
	ctx.PutState("key1", "field2", "val")
	ctx.PutState("key1", "field23", "val")
	ctx.PutState("key1", "field3", "val")
	// 使用迭代器,能查出来  field1,field2,field23 三条数据
	rs, code := ctx.NewIteratorWithField("key1", "field1", "field3")
	if code == SUCCESS {
		for rs.HasNext() {
			key, field, val, code := rs.Next()
			if code == SUCCESS {
				// do something
			} else {
				rs.Close()
				ctx.ErrorResult("err")
				return
			}
		}
		rs.Close()
	}

	ctx.PutState("key2", "field1", "val")
	ctx.PutState("key3", "field2", "val")
	ctx.PutState("key33", "field23", "val")
	ctx.PutState("key4", "field3", "val")
	// 能查出来 key2,key3,key33 三条数据
	ctx.NewIterator("key2", "key4")
	// 能查出来 key3,key33 两条数据
	ctx.NewIteratorPrefixWithKey("key3")
	// 能查出来  key1 field2,key1 field23 三条数据
	ctx.NewIteratorPrefixWithKeyField("key1", "field2")

	ctx.PutStateFromKey("key5", "val")
	ctx.PutStateFromKey("key56", "val")
	ctx.PutStateFromKey("key6", "val")
	// 能查出来 key5,key56 两条数据
	ctx.NewIterator("key5", "key6")
}

1.4.1.3. 代码编写规则

代码入口

func main() { // sdk代码中,有且仅有一个main()方法
	// 空,不做任何事。仅用于对tinygo编译支持
}

对链暴露方法写法为:

  • //export upgrade

  • func method_name(): 不可带参数,无返回值

//export init_contract 表明对外暴露方法名称
func initContract() {

}

其中init_contract、upgrade方法必须有且对外暴露

  • init_contract:创建合约会执行该方法

  • upgrade: 升级合约会执行该方法

// 安装合约时会执行此方法,必须。ChainMaker不允许用户直接调用该方法。
//export init_contract
func initContract() {

}
// 升级合约时会执行此方法,必须。ChainMaker不允许用户直接调用该方法。
//export upgrade
func upgrade() {

}

1.4.1.4. 编译说明

在ChainMaker IDE中集成了编译器,可以对合约进行编译。集成的编译器是 TinyGo。用户如果手工编译,需要将 SDK 和用户编写的智能合约放入同一个文件夹,并在此文件夹的当前路径执行如下编译命令:

tinygo build -no-debug -opt=s -o name.wasm -target wasm

命令中 “name.wasm” 为生成的WASM 字节码的文件名,由用户自行指定。

1.4.2. 合约发布过程

请参考:《chainmaker-go-sdk》发送创建合约请求的部分,或者《chainmaker-java-sdk》创建合约的部分。

1.4.3. 合约调用过程

请参考:《chainmaker-go-sdk》合约调用的部分,或者《chainmaker-java-sdk》执行合约的部分。

1.4.4. Go SDK API描述

1.4.4.1. 用户与链交互接口

// SimContextCommon common context
type SimContextCommon interface {
	// Arg get arg from transaction parameters, as:  arg1, code := ctx.Arg("arg1")
	Arg(key string) ([]byte, ResultCode)
	// Arg get arg from transaction parameters, as:  arg1, code := ctx.ArgString("arg1")
	ArgString(key string) (string, ResultCode)
	// Args return args
	Args() []*EasyCodecItem
	// Log record log to chain server
	Log(msg string)
	// SuccessResult record the execution result of the transaction, multiple calls will override
	SuccessResult(msg string)
	// SuccessResultByte record the execution result of the transaction, multiple calls will override
	SuccessResultByte(msg []byte)
	// ErrorResult record the execution result of the transaction. multiple calls will append. Once there is an error, it cannot be called success method
	ErrorResult(msg string)
	// CallContract cross contract call
	CallContract(contractName string, method string, param map[string][]byte) ([]byte, ResultCode)
	// GetCreatorOrgId get tx creator org id
	GetCreatorOrgId() (string, ResultCode)
	// GetCreatorRole get tx creator role
	GetCreatorRole() (string, ResultCode)
	// GetCreatorPk get tx creator pk
	GetCreatorPk() (string, ResultCode)
	// GetSenderOrgId get tx sender org id
	GetSenderOrgId() (string, ResultCode)
	// GetSenderOrgId get tx sender role
	GetSenderRole() (string, ResultCode)
	// GetSenderOrgId get tx sender pk
	GetSenderPk() (string, ResultCode)
	// GetBlockHeight get tx block height
	GetBlockHeight() (string, ResultCode)
	// GetTxId get current tx id
	GetTxId() (string, ResultCode)
	// EmitEvent emit event, you can subscribe to the event using the SDK
	EmitEvent(topic string, data ...string) ResultCode
}

// SimContext kv context
type SimContext interface {
	SimContextCommon
	// GetState get [key+"#"+field] from chain and db
	GetState(key string, field string) (string, ResultCode)
	// GetStateByte get [key+"#"+field] from chain and db
	GetStateByte(key string, field string) ([]byte, ResultCode)
	// GetStateByte get [key] from chain and db
	GetStateFromKey(key string) ([]byte, ResultCode)
	// PutState put [key+"#"+field, value] to chain
	PutState(key string, field string, value string) ResultCode
	// PutStateByte put [key+"#"+field, value] to chain
	PutStateByte(key string, field string, value []byte) ResultCode
	// PutStateFromKey put [key, value] to chain
	PutStateFromKey(key string, value string) ResultCode
	// PutStateFromKeyByte put [key, value] to chain
	PutStateFromKeyByte(key string, value []byte) ResultCode
	// DeleteState delete [key+"#"+field] to chain
	DeleteState(key string, field string) ResultCode
	// DeleteStateFromKey delete [key] to chain
	DeleteStateFromKey(key string) ResultCode
	// NewIterator range of [startKey, limitKey), front closed back open
	NewIterator(startKey string, limitKey string) (ResultSetKV, ResultCode)
	// NewIteratorWithField range of [key+"#"+startField, key+"#"+limitField), front closed back open
	NewIteratorWithField(key string, startField string, limitField string) (ResultSetKV, ResultCode)
	// NewIteratorPrefixWithKeyField range of [key+"#"+field, key+"#"+field], front closed back closed
	NewIteratorPrefixWithKeyField(key string, field string) (ResultSetKV, ResultCode)
	// NewIteratorPrefixWithKey range of [key, key], front closed back closed
	NewIteratorPrefixWithKey(key string) (ResultSetKV, ResultCode)
}


// ResultSet iterator query result
type ResultSet interface {
	// NextRow get next row,
	// sql: column name is EasyCodec key, value is EasyCodec string val. as: val := ec.getString("columnName")
	// kv iterator: key/value is EasyCodec key for "key"/"value", value type is []byte. as: k, _ := ec.GetString("key") v, _ := ec.GetBytes("value")
	NextRow() (*EasyCodec, ResultCode)
	// HasNext return does the next line exist
	HasNext() bool
	// close
	Close() (bool, ResultCode)
}

type ResultSetKV interface {
	ResultSet
	// Next return key,field,value,code
	Next() (string, string, []byte, ResultCode)
}

type SqlSimContext interface {
	SimContextCommon
	// sql method
	// ExecuteQueryOne
	ExecuteQueryOne(sql string) (*EasyCodec, ResultCode)
	ExecuteQuery(sql string) (ResultSet, ResultCode)
	// #### ExecuteUpdateSql execute update/insert/delete sql
	// ##### It is best to update with primary key
	//
	// as:
	//
	// - update table set name = 'Tom' where uniqueKey='xxx'
	// - delete from table where uniqueKey='xxx'
	// - insert into table(id, xxx,xxx) values(xxx,xxx,xxx)
	//
	// ### not allow:
	// - random methods: NOW() RAND() and so on
	// return: 1 Number of rows affected;2 result code
	ExecuteUpdate(sql string) (int32, ResultCode)
	// ExecuteDDLSql execute DDL sql, for init_contract or upgrade method. allow table create/alter/drop/truncate
	//
	// ## You must have a primary key to create a table
	// ### allow:
	// - CREATE TABLE tableName
	// - ALTER TABLE tableName
	// - DROP TABLE tableName
	// - TRUNCATE TABLE tableName
	//
	// ### not allow:
	// - CREATE DATABASE dbName
	// - CREATE TABLE dbName.tableName
	// - ALTER TABLE dbName.tableName
	// - DROP DATABASE dbName
	// - DROP TABLE dbName.tableName
	// - TRUNCATE TABLE dbName.tableName
	// not allow:
	// - random methods: NOW() RAND() and so on
	//
	ExecuteDdl(sql string) (int32, ResultCode)
} 

GetState

// 获取合约账户信息。该接口可从链上获取类别 “key” 下属性名为 “field” 的状态信息。
// @param key: 需要查询的key值
// @param field: 需要查询的key值下属性名为field
// @return1: 查询到的value值
// @return2: 0: success, 1: failed
func GetState(key string, field string) (string, ResultCode)

GetStateFromKey

// 获取合约账户信息。该接口可以从链上获取类别为key的状态信息
// @param key: 需要查询的key值
// @return1: 查询到的值
// @return: 0: success, 1: failed
func GetStateFromKey(key string) ([]byte, ResultCode)

PutState

// 写入合约账户信息。该接口可把类别 “key” 下属性名为 “filed” 的状态更新到链上。更新成功返回0,失败则返回1。
// @param key: 需要存储的key值,注意key长度不允许超过64,且只允许大小写字母、数字、下划线、减号、小数点符号
// @param field: 需要存储的key值下属性名为field,注意field长度不允许超过64,且只允许大小写字母、数字、下划线、减号、小数点符号
// @param value: 需要存储的value值,注意存储的value字节长度不能超过200
// @return: 0: success, 1: failed
func PutState(key string, field string, value string) ResultCode

PutStateFromKey

// 写入合约账户信息。
// @param key: 需要存储的key值
// @param value: 需要存储的value值
// @return: 0: success, 1: failed
func PutStateFromKey(key string, value string) ResultCode

DeleteState

// 删除合约账户信息。该接口可把类别 “key” 下属性名为 “name” 的状态从链上删除。
// @param key: 需要删除的key值
// @param field: 需要删除的key值下属性名为field
// @return: 0: success, 1: failed
func DeleteState(key string, field string) ResultCode {}

CallContract

// 跨合约调用。
// @param contractName 合约名称
// @param method 合约方法
// @param param 参数
// @return 0:合约返回结果, 1:合约执行结果
func CallContract(contractName string, method string, param map[string][]byte) ([]byte, ResultCode) {}

Args

// 该接口调用 getArgsMap() 接口,把 json 格式的数据反序列化,并将解析出的数据返还给用户。
// @return: 返回值类型为*EasyCodecItem数组
func Args() []*EasyCodecItem {}  

Arg

// 该接口可返回属性名为 “key” 的参数的属性值。
// @param key: 获取的参数名
// @return: 获取的参数值,结果返回值
func Arg(key string) ([]byte, ResultCode) {}  

SuccessResult

// 该接口可记录用户操作成功的信息,并将操作结果记录到链上。
// @param msg: 成功信息
func SuccessResult(msg string) {}  

ErrorResult

// 该接口可记录用户操作失败的信息,并将操作结果记录到链上。
// @param msg: 失败信息
func ErrorResult(msg string) {}

LogMessage

// 该接口可记录事件日志。查看方式为在链配置的log.yml中,开启vm:debug即可看到类似:gasm log>> + msg
// @param msg: 事件信息
func LogMessage(msg string) {}

GetCreatorOrgId

// 获取合约创建者所属组织ID
// @return: 合约创建者的组织ID,结果返回值
func GetCreatorOrgId() (string, ResultCode) {}  

GetCreatorRole

// 获取合约创建者角色
// @return: 合约创建者的角色,结果返回值
func GetCreatorRole() (string, ResultCode) {}  

GetCreatorPk

// 获取合约创建者公钥
// @return: 合约创建者的公钥,结果返回值
func GetCreatorPk() (string, ResultCode) {} 

GetSenderOrgId

// 获取交易发起者所属组织ID
// @return: 交易发起者的组织ID,结果返回值
func GetSenderOrgId() (string, ResultCode) {}  

GetSenderRole

// 获取交易发起者角色
// @return: 交易发起者角色,结果返回值
func GetSenderRole() (string, ResultCode) {} 

GetSenderPk()

// 获取交易发起者公钥
// @return 交易发起者的公钥,结果返回值
func GetSenderPk() (string, ResultCode) {}  

GetBlockHeight

// 获取当前区块高度
// @return: 当前块高度,结果返回值
func GetBlockHeight() (string, ResultCode) {} 

GetTxId

// 获取交易ID
// @return 交易ID,结果返回值
func GetTxId() (string, ResultCode) {}

EmitEvent

// 发送合约事件
// @param topic: 合约事件主题
// @data ...: 可变参数,合约事件数据,参数数量不可大于16,不可小于1。
func EmitEvent(topic string, data ...string) ResultCode {}

NewIterator

// NewIterator range of [startKey, limitKey), front closed back open
// 新建key范围迭代器,key前闭后开,即:startKey <= dbkey < limitKey
// @param startKey: 开始的key
// @param limitKey: 结束的key
// @return: 结果集游标
NewIterator(startKey string, limitKey string) (ResultSetKV, ResultCode)
// NewIteratorWithField range of [key+"#"+startField, key+"#"+limitField), front closed back open
// 新建field范围迭代器,key需相同,field前闭后开,即:key = dbdbkey and startField <= dbfield < limitField
// @param key: 固定key
// @param startField: 开始的field
// @param limitField: 结束的field
// @return: 结果集游标
NewIteratorWithField(key string, startField string, limitField string) (ResultSetKV, ResultCode)
// NewIteratorPrefixWithKey range of [key, key], front closed back closed
// 新建指定key前缀匹配迭代器,key需前缀一致,即dbkey.startWith(key)
// @param key: key前缀
// @return: 结果集游标
NewIteratorPrefixWithKey(key string) (ResultSetKV, ResultCode)
// NewIteratorPrefixWithKeyField range of [key+"#"+field, key+"#"+field], front closed back closed
// 新建指定field前缀匹配迭代器,key需相同,field前缀一致,即dbkey = key and dbfield.startWith(field)
// @param key: key前缀
// @param field: 指定field
// @return: 结果集游标
NewIteratorPrefixWithKeyField(key string, field string) (ResultSetKV, ResultCode)

1.5. 使用C++进行智能合约开发

读者对象:本章节主要描述使用C++进行ChainMaker合约编写的方法,主要面向于使用C++进行ChainMaker的合约开发的开发者。

1.5.1. 用Docker镜像进行开发

ChainMaker官方已经将容器发布至 docker hub

拉取镜像

docker pull chainmakerofficial/chainmaker-cpp-contract:2.1.0

请指定你本机的工作目录$WORK_DIR,例如/data/workspace/contract,挂载到docker容器中以方便后续进行必要的一些文件拷贝

docker run -it --name chainmaker-cpp-contract -v $WORK_DIR:/home chainmakerofficial/chainmaker-cpp-contract:2.1.0 bash
# 或者先后台启动
docker run -d --name chainmaker-cpp-contract -v $WORK_DIR:/home chainmakerofficial/chainmaker-cpp-contract:2.1.0 bash -c "while true; do echo hello world; sleep 5;done"
# 再进入容器
docker exec -it chainmaker-cpp-contract bash

编译合约

cd /home/
tar xvf /data/contract_cpp_template.tar.gz
cd contract_cpp
make clean
emmake make

生成合约的字节码文件在

/home/contract_cpp/main.wasm

通过本地模拟环境运行合约(首次编译运行合约可能需要10秒左右,下面以存证作为示例)

# wxvm main.wasm save time 20210304 file_hash 12345678 file_name a.txt

2021-03-25 09:10:36.441      [DEBUG] [Vm]    xvm/context_service.go:257      wxvm log >>[1234567890123456789012345678901234567890123456789012345678901234] [1] call save() tx_id:
2021-03-25 09:10:36.463 [DEBUG] [Vm]    xvm/context_service.go:257      wxvm log >>[1234567890123456789012345678901234567890123456789012345678901234] [1] call save() file_hash:12345678
2021-03-25 09:10:36.464 [DEBUG] [Vm]    xvm/context_service.go:257      wxvm log >>[1234567890123456789012345678901234567890123456789012345678901234] [1] call save() file_name:a.txt
2021-03-25 09:10:36.465 [DEBUG] [Vm]    xvm/context_service.go:257      wxvm log >>[1234567890123456789012345678901234567890123456789012345678901234] [1] put success: a.txt 12345678
2021-03-25 09:10:36.466 [DEBUG] [Vm]    xvm/context_service.go:257      wxvm log >>[1234567890123456789012345678901234567890123456789012345678901234] [1] save====================================end
2021-03-25 09:10:36.467 [DEBUG] [Vm]    xvm/context_service.go:257      wxvm log >>[1234567890123456789012345678901234567890123456789012345678901234] [1] 
2021-03-25 09:10:36.467 [DEBUG] [Vm]    xvm/context_service.go:257      wxvm log >>[1234567890123456789012345678901234567890123456789012345678901234] [1] result:  a.txt 12345678
2021-03-25 09:10:36.469 [INFO]  [Vm] @chain01   main/main.go:31 contractResult :result:" a.txt 12345678"

其中存证的合约方法定义为:

#include "chainmaker/chainmaker.h"

using namespace chainmaker;

class Counter : public Contract {
public:
	...
	...
    void save()
    {
        Context *ctx = context();

        std::string file_hash;
        std::string file_name;
        std::string tx_id;

        ctx->arg("file_hash", file_hash);
        ctx->arg("file_name", file_name);
        ctx->arg("tx_id", tx_id);
        ctx->log("call save() tx_id:" + tx_id);
        ctx->log("call save() file_hash:" + file_hash);
        ctx->log("call save() file_name:" + file_name);
        ctx->emit_event("topic_vx",2,file_hash.c_str(),file_name.c_str());
        std::string test_str = tx_id + " " + file_name + " " + file_hash;
        ctx->put_object(file_hash, test_str);
        ctx->log("put success:" + test_str);
        ctx->log("save====================================end");
        std::string value;
        std::string value_str;
        EasyCodecItems *value_items;
        ctx->get_object(file_hash, &value);
        ctx->log(value);
        value_items = easy_unmarshal((byte *)value.data());
        value_str = (char *)value_items->get_value((char *)"value");
        ctx->log("result: " + value_str);
        ctx->success(test_str);
        delete (value_items);
    }
    ...
    ...
}

WASM_EXPORT void save() {
    Counter counter;
    counter.save();
}

1.5.1.1. 框架描述

解压缩contract_cpp_template.tar.gz后,文件描述如下:

  • chainmaker

    • basic_iterator.cc: 迭代器实现

    • basic_iterator.h: 迭代器头文件声明

    • chainmaker.h: sdk主要接口头文件声明,详情见SDK API描述

    • context_impl.cc: 与链交互接口实现

    • context_impl.h: 与链交互头文件声明

    • contract.cc: 合约基础工具类

    • error.h: 异常处理类

    • exports.js: 编译合约导出函数

    • safemath.h: assert异常处理

    • syscall.cc: 与链交互入口

    • syscall.h: 与链交互头文件声明

  • pb

    • contract.pb.cc:与链交互数据协议

    • contract.pb.h:与链交互数据协议头文件声明

  • main.cc: 用户写合约入口,如下

  • Makefile: 常用build命令

1.5.1.2. 示例代码说明

存证合约示例:main.cc,实现功能:

1、存储文件哈希和文件名称和该交易的ID

2、通过文件哈希查询该条记录

#include "chainmaker/chainmaker.h"

using namespace chainmaker;

class Counter : public Contract {
public:
    void init_contract() {}
    void upgrade() {}
    // 保存
    void save() {
        // 获取SDK 接口上下文
        Context* ctx = context();
        // 定义变量
        std::string time;
        std::string file_hash;
        std::string file_name;
        std::string tx_id;
		// 获取参数
        ctx->arg("time", time);
        ctx->arg("file_hash", file_hash);
        ctx->arg("file_name", file_name);
        ctx->arg("tx_id", tx_id);
        // 发送合约事件
        // 向topic:"topic_vx"发送2个event数据,file_hash,file_name
        ctx->emit_event("topic_vx",2,file_hash.c_str(),file_name.c_str());
		// 存储数据
        ctx->put_object("fact"+ file_hash,  tx_id+" "+time+" "+file_hash+" "+file_name);
        // 记录日志
        ctx->log("call save() result:" + tx_id+" "+time+" "+file_hash+" "+file_name);
        // 返回结果
        ctx->success(tx_id+" "+time+" "+file_hash+" "+file_name);
    }

    // 查询
    void find_by_file_hash() {
        // 获取SDK 接口上下文
    	Context* ctx = context();

		// 获取参数
        std::string file_hash;
        ctx->arg("file_hash", file_hash);
		
        // 查询数据
    	std::string value;
        ctx->get_object("fact"+ file_hash, &value);
        // 记录日志
        ctx->log("call find_by_file_hash()-" + file_hash + ",result:" + value);
        // 返回结果
        ctx->success(value);
    }

};

// 在创建本合约时, 调用一次init方法. ChainMaker不允许用户直接调用该方法.
WASM_EXPORT void init_contract() {
    Counter counter;
    counter.init_contract();
}

// 在升级本合约时, 对于每一个升级的版本调用一次upgrade方法. ChainMaker不允许用户直接调用该方法.
WASM_EXPORT void upgrade() {
    Counter counter;
    counter.upgrade();
}

WASM_EXPORT void save() {
    Counter counter;
    counter.save();
}

WASM_EXPORT void find_by_file_hash() {
    Counter counter;
    counter.find_by_file_hash();
}

1.5.1.3. 代码编写规则

对链暴露方法写法为:

  • WASM_EXPORT: 必须,暴露声明

  • void: 必须,无返回值

  • method_name(): 必须,暴露方法名称

// 示例
WASM_EXPORT void init_contract() {
    
}

其中init_contract、upgrade方法必须有且对外暴露

  • init_contract:创建合约会执行该方法

  • upgrade: 升级合约会执行该方法

// 在创建本合约时, 调用一次init方法. ChainMaker不允许用户直接调用该方法.
WASM_EXPORT void init_contract() {
    // 安装时的业务逻辑,可为空
    
}

// 在升级本合约时, 对于每一个升级的版本调用一次upgrade方法. ChainMaker不允许用户直接调用该方法.
WASM_EXPORT void upgrade() {
    // 升级时的业务逻辑,可为空
    
}

获取SDK 接口上下文

Context* ctx = context();

1.5.1.4. 编译说明

在ChainMaker提供的Docker容器中中集成了编译器,可以对合约进行编译,集成的编译器是emcc 1.38.48版本,protobuf 使用3.7.1版本。用户如果手工编译需要先使用emcc 编译 protobuf ,编译之后执行emmake make即可。

1.5.2. 合约发布过程

请参考:《chainmaker-go-sdk》发送创建合约请求的部分,或者《chainmaker-java-sdk》创建合约的部分。

1.5.3. 合约调用过程

请参考:《chainmaker-go-sdk》合约调用的部分,或者《chainmaker-java-sdk》执行合约的部分。

1.5.4. C++ SDK API描述

arg

// 该接口可返回属性名为 “name” 的参数的属性值。
// @param name: 要获取值的参数名称
// @param value: 获取的参数值
// @return: 是否成功
bool arg(const std::string& name, std::string& value){}

需要注意的是通过arg接口返回的参数,全都都是字符串,合约开发者有必要将其他数据类型的参数与字符串做转换,包括atoi、itoa、自定义序列化方式等。

get_object

// 获取key为"key"的值
// @param key: 获取对象的key
// @param value: 获取的对象值
// @return: 是否成功
bool get_object(const std::string& key, std::string* value){}

put_object

// 存储key为"key"的值
// @param key: 存储的对象key,注意key长度不允许超过64,且只允许大小写字母、数字、下划线、减号、小数点符号
// @param value: 存储的对象值install
// @return: 是否成功
bool put_object(const std::string& key, const std::string& value){}

delete_object

// 删除key为"key"的值
// @param key: 删除的对象key
// @return: 是否成功
bool delete_object(const std::string& key) {}

emit_event

// 发送合约事件
// @param topic: 合约事件主题
// @data_amount: 合约事件数据数量(data),data_amount的值必须要和data数量一致,最多不可大于16,最少不可小于1,不可为空
// @data ...: 可变参数合约事件数据,数量与data_amount一致。
bool emit_event(const std::string &topic, int data_amount, const std::string data, ...)

success

// 返回成功的结果
// @param body: 成功信息
void success(const std::string& body) {}

error

// 返回失败结果
// @param body: 失败信息
void error(const std::string& body) {}

call

// 跨合约调用
// @param contract: 合约名称
// @param method: 合约方法
// @param args: 调用合约的参数
// @param resp: 调用合约的响应
// @return: 是否成功
bool call(const std::string &contract,
                          const std::string &method,
                          EasyCodecItems *args,
                          std::string *resp){}

log

// 输出日志事件。查看方式为在链配置的log.yml中,开启vm:debug即可看到类似:wxvm log>> + msg
// @param body: 事件信息
void log(const std::string& body) {}

1.6. 使用Solidity进行智能合约开发

读者对象:本章节主要描述使用Solidity进行ChainMaker合约编写的方法,主要面向于使用Solidity进行ChainMaker的合约开发的开发者。

1.6.1. 合约开发

Solidity 是一门面向合约的、为实现智能合约而创建的高级编程语言。这门语言受到了 C++,Python 和 Javascript 语言的影响,设计的目的是能在虚拟机(EVM)上运行。

Solidity 是静态类型语言,支持继承、库和复杂的用户定义类型等特性。

1.6.1.1. 通过Docker执行evm步骤

ChainMaker官方已经将容器发布至 docker hub

拉取镜像

docker pull chainmakerofficial/chainmaker-solidity-contract:2.0.0

请指定你本机的工作目录$WORK_DIR,例如/data/workspace/contract,挂载到docker容器中以方便后续进行必要的一些文件拷贝

docker run -it --name chainmaker-solidity-contract -v $WORK_DIR:/home chainmakerofficial/chainmaker-solidity-contract:2.0.0 bash
# 或者先后台启动
docker run -d --name chainmaker-solidity-contract -v $WORK_DIR:/home chainmakerofficial/chainmaker-solidity-contract:2.0.0 bash -c "while true; do echo hello world; sleep 5;done"
# 再进入容器
docker exec -it chainmaker-solidity-contract bash

编译合约

# cd /home/
# tar xvf /data/contract_solidity_template.tar.gz
# cd contract_solidity
# solc --abi --bin --hashes --overwrite -o . token.sol

solc为编译命令, –abi选项指示生成abi文件,–bin指示生成字节码文件, –hashes指示生成函数签名文件, –overwrite指示如果生成文件已存在则覆盖, -o 指示编译生成的文件存放的目录。

生成的字节码在:

/home/contract_solidity/Token.bin

执行部署:

# evm Token.bin init_contract data 00000000000000000000000013f0c1639a9931b0ce17e14c83f96d4732865b58

evm为测试合约部署和调用的命令,.bin文件后跟随的是被调用合约的方法,init_contract表示调用的是合约的构造方法,该方法只在部署合约时调用一次。

data标识紧随其后的数据 00000000000000000000000013f0c1639a9931b0ce17e14c83f96d4732865b58 为 calldata,calldata由被调用方法的签名和参数的ABI编码组成。calldata需要通过ABI接口生成,这里的calldata是一个地址,是示例token合约构造方法的参数。注意,这里的calldata数据不包括方法签名,因为构造方法不需要签名。

calldata的代码生成示例:

abiBytes, _ := ioutil.ReadFile("xxxx.abi")
abiObj, _ := abi.JSON(strings.NewReader(string(abiBytes)))
calldata, err := abiObj.Pack("methodName", big.NewInt(100))

执行上述步骤后,把返回的result手动保存在DeployedToken.bin文件中,

/home/contract_solidity/DeployedToken.bin

再次执行调用其中的balanceOf(address)方法,可以查到balanceOf(address)的方法签名为70a08231

# cat Token.signatures 
dd62ed3e: allowance(address,address)
095ea7b3: approve(address,uint256)
70a08231: balanceOf(address)
42966c68: burn(uint256)
313ce567: decimals()
06fdde03: name()
c47f0027: setName(string)
be9a6555: start()
07da68f5: stop()
75f12b21: stopped()
95d89b41: symbol()
18160ddd: totalSupply()
a9059cbb: transfer(address,uint256)
23b872dd: transferFrom(address,address,uint256)

再次执行balanceOf(address)方法:

# evm DeployedToken.bin 0x70a08231 data 0x70a0823100000000000000000000000013f0c1639a9931b0ce17e14c83f96d4732865b58

1.6.2. 示例代码说明

Token合约示例,实现功能ERC20

/*
SPDX-License-Identifier: Apache-2.0
*/
pragma solidity >0.5.11;
contract Token {

    string public name = "token";      //  token name
    string public symbol = "TK";           //  token symbol
    uint256 public decimals = 6;            //  token digit

    mapping (address => uint256) public balanceOf;
    mapping (address => mapping (address => uint256)) public allowance;

    uint256 public totalSupply = 0;
    bool public stopped = false;

    uint256 constant valueFounder = 100000000000000000;
    address owner = address(0x0);

    modifier isOwner {
        assert(owner == msg.sender);
        _;
    }

    modifier isRunning {
        assert (!stopped);
        _;
    }

    modifier validAddress {
        assert(address(0x0) != msg.sender);
        _;
    }

    constructor (address _addressFounder) {
        owner = msg.sender;
        totalSupply = valueFounder;
        balanceOf[_addressFounder] = valueFounder;
        
        emit Transfer(address(0x0), _addressFounder, valueFounder);
    }

    function transfer(address _to, uint256 _value) public isRunning validAddress returns (bool success) {
        require(balanceOf[msg.sender] >= _value);
        require(balanceOf[_to] + _value >= balanceOf[_to]);
        balanceOf[msg.sender] -= _value;
        balanceOf[_to] += _value;
        emit Transfer(msg.sender, _to, _value);
        return true;
    }

    function transferFrom(address _from, address _to, uint256 _value) public isRunning validAddress returns (bool success) {
        require(balanceOf[_from] >= _value);
        require(balanceOf[_to] + _value >= balanceOf[_to]);
        require(allowance[_from][msg.sender] >= _value);
        balanceOf[_to] += _value;
        balanceOf[_from] -= _value;
        allowance[_from][msg.sender] -= _value;
        emit Transfer(_from, _to, _value);
        return true;
    }

    function approve(address _spender, uint256 _value) public isRunning validAddress returns (bool success) {
        require(_value == 0 || allowance[msg.sender][_spender] == 0);
        allowance[msg.sender][_spender] = _value;
        emit Approval(msg.sender, _spender, _value);
        return true;
    }

    function stop() public isOwner {
        stopped = true;
    }

    function start() public isOwner {
        stopped = false;
    }

    function setName(string memory _name) public isOwner {
        name = _name;
    }

    function burn(uint256 _value) public {
        require(balanceOf[msg.sender] >= _value);
        balanceOf[msg.sender] -= _value;
        balanceOf[address(0x0)] += _value;
        emit Transfer(msg.sender, address(0x0), _value);
    }

    event Transfer(address indexed _from, address indexed _to, uint256 _value);
    event Approval(address indexed _owner, address indexed _spender, uint256 _value);
}

1.6.3. 合约发布过程

请参考:《chainmaker-go-sdk》发送创建合约请求的部分,或者《chainmaker-java-sdk》创建合约的部分。

1.6.4. 合约调用过程

请参考:《chainmaker-go-sdk》合约调用的部分,或者《chainmaker-java-sdk》执行合约的部分。

1.6.5. EVM地址说明

ChainMaker目前已支持的证书模型与以太坊的公钥模型不相同,为此ChainMaker在SDK中支持通过证书的SKI字段转换为EVM中所支持的地址格式。

以下为样例证书信息部分内容:

Certificate:
    Data:
        Version: 3 (0x2)
        Serial Number: 775620 (0xbd5c4)
    Signature Algorithm: ecdsa-with-SHA256
        Issuer: C=CN, ST=Beijing, L=Beijing, O=wx-org1.chainmaker.org, OU=root-cert, CN=ca.wx-org1.chainmaker.org
        Validity
            Not Before: Apr 30 07:04:20 2021 GMT
            Not After : Apr 29 07:04:20 2026 GMT
        Subject: C=CN, ST=Beijing, L=Beijing, O=wx-org1.chainmaker.org, OU=admin, CN=admin1.sign.wx-org1.chainmaker.org
        Subject Public Key Info:
            Public Key Algorithm: id-ecPublicKey
                Public-Key: (256 bit)
                pub: 
                    04:05:63:4d:46:6f:b0:3e:30:cb:4f:b3:12:93:da:
                    10:1e:d4:50:ea:36:ac:3f:85:e4:3b:c3:a8:7e:ff:
                    4a:57:7d:f1:55:b7:21:0d:94:2f:9a:be:92:5b:dc:
                    90:a2:36:75:82:6e:8c:35:55:ff:f2:96:30:e2:f4:
                    cc:cf:b7:75:5d
                ASN1 OID: prime256v1
                NIST CURVE: P-256
        X509v3 extensions:
            X509v3 Key Usage: critical
                Digital Signature, Key Encipherment, Certificate Sign, CRL Sign
            X509v3 Extended Key Usage: 
                Any Extended Key Usage
						// 地址由SKI(Subject Key Identifier)值HASH生成(不包括':'符号)
            X509v3 Subject Key Identifier:               08:E6:25:3A:8B:F0:2B:BB:ED:03:34:71:B4:24:D0:A5:F1:C4:02:CC:B6:44:6E:42:30:AB:51:76:3A:34:C3:7B

在ChainMaker Evm中,地址的生成参见以下流程:

1、SDK调合约方法,传入SKI

//调用blanceOf函数,查询指定用户余额。

//某用户SKI信息
const client1AddrSki = "08E6253A8BF02BBBED033471B424D0A5F1C402CCB6446E4230AB51763A34C37B"

func testUserContractTokenEVMBalanceOf(t *testing.T, client *ChainClient, address string, withSyncResult bool) {
	abiJson, err := ioutil.ReadFile(tokenABIPath)
	require.Nil(t, err)

	myAbi, err := abi.JSON(strings.NewReader(string(abiJson)))
	require.Nil(t, err)
  //通过SKI生成address 
	addrInt, err := evmutils.MakeAddressFromHex(client1AddrSki)
	addr := evmutils.BigToAddress(addrInt)
  //指定合约方法
	methodName := "balanceOf"
	dataByte, err := myAbi.Pack(methodName, addr)
	require.Nil(t, err)

	data := hex.EncodeToString(dataByte)
	method := data[0:8]

	pairs := map[string]string{
		"data": data,
	}
  //调用合约
	result, err := invokeUserContractWithResult(client, tokenContractName, method, "", pairs, withSyncResult)
	require.Nil(t, err)

	balance, err := myAbi.Unpack(methodName, result)
	require.Nil(t, err)
	fmt.Printf("addr [%s] => %d\n", address, balance)
}

2、对其HASH(Keccak256)并截取,生成address

func MakeAddressFromHex(str string) (*Int, error) {
	data, err := hex.DecodeString(str)
	if err != nil {
		return nil, err
	}
	return MakeAddress(data), nil
}
func MakeAddressFromString(str string) (*Int, error) {
	return MakeAddress([]byte(str)), nil
}
//将SKI进行HASH并截取。
func MakeAddress(data []byte) *Int {
	address := Keccak256(data)
	addr := hex.EncodeToString(address)[24:]
	return FromHexString(addr)
}
func BigToAddress(b *Int) Address { return BytesToAddress(b.Bytes()) }

1.7. 使用Docker-go进行智能合约开发

1.7.1. 部署环境

  1. 操作系统

    目前仅支持在Linux系统下部署和运行Docker VM。长安链与Docker VM的通信是基于unix domain socket,在非Linux系统中,会出现权限问题。

  2. 软件依赖

    1. docker

      启动长安链之前,确认docker已经启动;

      安装步骤,请参看:docker官方网站

      $ docker version
      Client: Docker Engine - Community
       Version:           20.10.7
       API version:       1.41
       Go version:        go1.13.15
       Git commit:        f0df350
       Built:             Wed Jun  2 11:56:38 2021
       OS/Arch:           linux/amd64
       Context:           default
       Experimental:      true
      
      Server: Docker Engine - Community
       Engine:
        Version:          20.10.7
        API version:      1.41 (minimum version 1.12)
        Go version:       go1.13.15
        Git commit:       b0f5bc3
        Built:            Wed Jun  2 11:54:50 2021
        OS/Arch:          linux/amd64
        Experimental:     false
       containerd:
        Version:          1.4.8
        GitCommit:        7eba5930496d9bbe375fdf71603e610ad737d2b2
       runc:
        Version:          1.0.0
        GitCommit:        v1.0.0-0-g84113ee
       docker-init:
        Version:          0.19.0
        GitCommit:        de40ad0
      
    2. 7zip 启动长安链之前,确认7zip已经安装完毕。

      7zip安装命令如下:

      # centos
      sudo yum install p7zip
      # ubuntu
      sudo apt-get install p7zip-full
      

1.7.2. 参数说明

使用Docker VM先拉取官方镜像:

docker pull chainmakerofficial/chainmaker-vm-docker-go:v2.1.0
  1. 启用Docker VM

    $ ./prepare.sh 4 1 11300 12300
    begin check params...
    begin generate certs, cnt: 4
    input consensus type (0-SOLO,1-TBFT(default),3-HOTSTUFF,4-RAFT,5-DPOS): 
    input log level (DEBUG|INFO(default)|WARN|ERROR): 
    ## 开启Docker VM, 请输入YES, 默认为NO:不开启Docker VM
    enable docker vm (YES|NO(default))YES  
    enable docker vm
    begin generate node1 config...
    begin generate node2 config...
    begin generate node3 config...
    begin generate node4 config...
    
  2. 启动参数

    1. chainmaker.yml

      chainmaker.ymlenable_dockervm 配置项设置为 true

      # Docker go virtual machine configuration
      vm:
        # Enable docker go virtual machine
        enable_dockervm: true
        # Docker go virtual machine container name
        dockervm_container_name: chainmaker-vm-docker-go-container
        # Mount point in chain maker
        dockervm_mount_path: ../data/org1/docker-go
        # Specify log file path
        dockervm_log_path: ../log/org1/dockervm
        # Whether to print log at terminal
        log_in_console: false
        # Log level
        log_level: INFO
        # Unix domain socket open, used for chainmaker and docker manager communication
        uds_open: true
        # The size of the channel where transactions are stored in docker manager
        tx_size: 1000
        # Number of user Ids
        user_num: 100
        # Timeout per transaction, Unit: second
        time_limit: 20
      

1.7.3. 使用docker镜像进行合约开发

ChainMaker官方已经将容器发布至 https://hub.docker.com/u/chainmakerofficial

注意:节点平台必须和合约编译平台相同,不能跨平台使用。

  1. 拉取镜像

    $ docker pull chainmakerofficial/chainmaker-docker-go-contract:v2.1.0
    
  2. 请指定你本机的工作目录$WORK_DIR,例如/data/workspace/contract,挂载到docker容器中以方便后续进行必要的一些文件拷贝

    $ docker run -it --name chainmaker-docker-go-contract -v <WORK_DIR>:/home chainmakerofficial/chainmaker-docker-go-contract:v2.1.0 bash
    
  3. 编译合约,压缩合约

    $ cd data/
    $ tar xvf /data/contract_docker_go_template.tar.gz
    $ cd contract_docker_go
    $ ./build.sh
    please input contract name, contract name should be same as name in tx: 
    <contract_name> #此处contract_name必须和交易中的合约名一致
    please input zip file: 
    <zip_file_name> #建议与contract_name保持一致(不包含文件后缀)
    ...
    
  4. 编译,压缩好的文件位置在

    /home/contract_docker_go/target/release/<contract_name>.7z
    

1.7.3.1. 示例代码说明

1.7.3.1.1. 存证合约示例

实现功能:

  1. 存储文件哈希,文件名

  2. 通过文件哈希查询该条交易

package main

import (
	"encoding/json"
	"log"
	"strconv"

	"chainmaker.org/chainmaker-contract-sdk-docker-go/pb/protogo"
	"chainmaker.org/chainmaker-contract-sdk-docker-go/shim"
)

type FactContract struct {
}

// 存证对象
type Fact struct {
	FileHash string `json:"FileHash"`
	FileName string `json:"FileName"`
	Time     int32  `json:"time"`
}

// 新建存证对象
func NewFact(FileHash string, FileName string, time int32) *Fact {
	fact := &Fact{
		FileHash: FileHash,
		FileName: FileName,
		Time:     time,
	}
	return fact
}

func (f *FactContract) InitContract(stub shim.CMStubInterface) protogo.Response {

	return shim.Success([]byte("Init Success"))

}

func (f *FactContract) InvokeContract(stub shim.CMStubInterface) protogo.Response {

	// 获取参数
	method := string(stub.GetArgs()["method"])

	switch method {
	case "save":
		return f.save(stub)
	case "findByFileHash":
		return f.findByFileHash(stub)
	default:
		return shim.Error("invalid method")
	}

}

func (f *FactContract) save(stub shim.CMStubInterface) protogo.Response {
	params := stub.GetArgs()

	// 获取参数
	fileHash := string(params["file_hash"])
	fileName := string(params["file_name"])
	timeStr := string(params["time"])
	time, err := strconv.Atoi(timeStr)
	if err != nil {
		msg := "time is [" + timeStr + "] not int"
		stub.Log(msg)
		return shim.Error(msg)
	}

	// 构建结构体
	fact := NewFact(fileHash, fileName, int32(time))

	// 序列化
	factBytes, _ := json.Marshal(fact)

	// 发送事件
	stub.EmitEvent("topic_vx", []string{fact.FileHash, fact.FileName})

	// 存储数据
	err = stub.PutStateByte("fact_bytes", fact.FileHash, factBytes)
	if err != nil {
		return shim.Error("fail to save fact bytes")
	}

	// 记录日志
	stub.Log("[save] FileHash=" + fact.FileHash)
	stub.Log("[save] FileName=" + fact.FileName)

	// 返回结果
	return shim.Success([]byte(fact.FileName + fact.FileHash))

}

func (f *FactContract) findByFileHash(stub shim.CMStubInterface) protogo.Response {
	// 获取参数
	FileHash := string(stub.GetArgs()["file_hash"])

	// 查询结果
	result, err := stub.GetStateByte("fact_bytes", FileHash)
	if err != nil {
		return shim.Error("failed to call get_state")
	}

	// 反序列化
	var fact Fact
	_ = json.Unmarshal(result, &fact)

	// 记录日志
	stub.Log("[find_by_file_hash] FileHash=" + fact.FileHash)
	stub.Log("[find_by_file_hash] FileName=" + fact.FileName)

	// 返回结果
	return shim.Success(result)
}

func main() {

	err := shim.Start(new(FactContract))
	if err != nil {
		log.Fatal(err)
	}
}
1.7.3.1.2. 迭代器使用示例
func (t *TestContract) kvIterator(stub shim.CMStubInterface) protogo.Response {
	stub.Log("===construct START===")
    // 添加一批数据
	dataList := []struct {
		key   string
		field string
		value string
	}{
		{key: "key1", field: "field1", value: "val"},
		{key: "key1", field: "field2", value: "val"},
		{key: "key1", field: "field23", value: "val"},
		{key: "key1", field: "field3", value: "val"},
		{key: "key2", field: "field1", value: "val"},
		{key: "key3", field: "field2", value: "val"},
		{key: "key33", field: "field2", value: "val"},
		{key: "key4", field: "field3", value: "val"},
	}

	for _, data := range dataList {
		err := stub.PutState(data.key, data.field, data.value)
		if err != nil {
			msg := fmt.Sprintf("constructData failed, %s", err.Error())
			stub.Log(msg)
			return shim.Error(msg)
		}
	}
	stub.Log("===construct END===")

	stub.Log("===kvIterator START===")
	iteratorList := make([]shim.ResultSetKV, 4)

	// 创建迭代器,能查询出 key2, key3, key33 三条数据
	iterator, err := stub.NewIterator("key2", "key4")
	if err != nil {
		msg := "NewIterator failed"
		stub.Log(msg)
		return shim.Error(msg)
	}
	iteratorList[0] = iterator

	// 创建迭代器,能查询出 field1, field2, field23 三条数据
	iteratorWithField, err := stub.NewIteratorWithField("key1", "field1", "field3")
	if err != nil {
		// msg := "create with " + string(key1) + string(field1) + string(field3) + " failed"
		msg := "create with " + "key1" + "field1" + "field3" + " failed"
		stub.Log(msg)
		return shim.Error(msg)
	}
	iteratorList[1] = iteratorWithField

	// 创建前缀匹配迭代器,能查询出 key3, key33 两条数据
	preWithKeyIterator, err := stub.NewIteratorPrefixWithKey("key3")
	if err != nil {
		msg := "NewIteratorPrefixWithKey failed"
		stub.Log(msg)
		return shim.Error(msg)
	}
	iteratorList[2] = preWithKeyIterator

	// 创建前缀匹配迭代器,能查询出 field2, field23 三条数据
	preWithKeyFieldIterator, err := stub.NewIteratorPrefixWithKeyField("key1", "field2")
	if err != nil {
		msg := "NewIteratorPrefixWithKeyField failed"
		stub.Log(msg)
		return shim.Error(msg)
	}
	iteratorList[3] = preWithKeyFieldIterator

	for index, iter := range iteratorList {
		index++
		stub.Log(fmt.Sprintf("===iterator %d START===", index))
        // 判断迭代器是否有下一个元素
		for iter.HasNext() {
			stub.Log("HasNext Success")
            // 有下一个元素,则获取下一个元素的值,并记录日志
			key, field, value, err := iter.Next()
			if err != nil {
				msg := "iterator failed to get the next element"
				stub.Log(msg)
				return shim.Error(msg)
			}

			stub.Log(fmt.Sprintf("===[key: %s]===", key))
			stub.Log(fmt.Sprintf("===[field: %s]===", field))
			stub.Log(fmt.Sprintf("===[value: %s]===", value))
		}

        // 关闭该迭代器
		closed, err := iter.Close()
		if !closed || err != nil {
			msg := fmt.Sprintf("iterator %d close failed, %s", index, err.Error())
			stub.Log(msg)
			return shim.Error(msg)
		}
		stub.Log(fmt.Sprintf("===iterator %d END===", index))
	}
	stub.Log("===kvIterator END===")

	return shim.Success([]byte("SUCCESS"))
}
1.7.3.1.3. key历史查询示例
func (t *TestContract) keyHistoryIter(stub shim.CMStubInterface) protogo.Response {
	stub.Log("===Key History Iter START===")
    // 获取客户端指定的用于查询历史value的key、field,field可为空
	args := stub.GetArgs()
	key := string(args["key"])
	field := string(args["field"])

    // 使用指定的key、field创建迭代器
	iter, err := stub.NewHistoryKvIterForKey(key, field)
	if err != nil {
		msg := "NewHistoryIterForKey failed"
		stub.Log(msg)
		return shim.Error(msg)
	}

	stub.Log("===create iter success===")

	count := 0
    // 判断迭代器是否有下一个元素
	for iter.HasNext() {
		stub.Log("HasNext")
		count++
        // 有下一个元素则获取该值,并记录日志
		km, err := iter.Next()
		if err != nil {
			msg := "iterator failed to get the next element"
			stub.Log(msg)
			return shim.Error(msg)
		}

        // 该迭代器的每个元素都记录了以下信息
		/*
			type KeyModification struct {
				Key         string // key
				Field       string // field
				Value       []byte // value
				TxId        string // 所在交易的TxId
				BlockHeight int    // 所在快高
				IsDelete    bool   // 是否是删除操作
				Timestamp   string // 所在块的时间戳,如果为0则还未被打包进块里
			}
		*/

		stub.Log(fmt.Sprintf("=== Data History [%d] Info:", count))
		stub.Log(fmt.Sprintf("=== Key: [%s]", km.Key))
		stub.Log(fmt.Sprintf("=== Field: [%s]", km.Field))
		stub.Log(fmt.Sprintf("=== Value: [%s]", km.Value))
		stub.Log(fmt.Sprintf("=== TxId: [%s]", km.TxId))
		stub.Log(fmt.Sprintf("=== BlockHeight: [%d]", km.BlockHeight))
		stub.Log(fmt.Sprintf("=== IsDelete: [%t]", km.IsDelete))
		stub.Log(fmt.Sprintf("=== Timestamp: [%s]", km.Timestamp))
	}

    // 关闭迭代器
	closed, err := iter.Close()
	if !closed || err != nil {
		msg := fmt.Sprintf("iterator close failed, %s", err.Error())
		stub.Log(msg)
		return shim.Error(msg)
	}
	stub.Log("===iter close success===")

	stub.Log("===Key History Iter END===")

	return shim.Success([]byte("get key history successfully"))
}

// putState、delState用于构造一批关于key的历史数据
func (t *TestContract) putState(stub shim.CMStubInterface) protogo.Response {
	args := stub.GetArgs()

	getKey := string(args["key"])
	getField := string(args["field"])
	getValue := string(args["value"])

	err := stub.PutState(getKey, getField, getValue)
	if err != nil {
		return shim.Error(err.Error())
	}

	return shim.Success([]byte("put state successfully"))
}

// delete该数据,则在该key、field的历史数据中会出现value为空的一项记录,该记录的IsDelete被记录为true
func (t *TestContract) delState(stub shim.CMStubInterface) protogo.Response {
	args := stub.GetArgs()

	getKey := string(args["key"])
	getField := string(args["field"])

	err := stub.DelState(getKey, getField)
	if err != nil {
		return shim.Error(err.Error())
	}
	return shim.Success([]byte("delete successfully"))
}

1.7.3.2. 调用示例

调用Docker-Go的合约与之前保持一致,可以通过cmc或者Go SDK,通过发交易的方式进行合约的部署和调用。 区别在于,调用合约的具体方法需要放入参数中,并且runtime-type为DOCKER_GO。以下示例的准备工作请按官网文档进行。

1.7.3.2.1. 存证合约调用示例
  1. 使用cmc工具

    ## 创建合约
    ./cmc client contract user create \
    --contract-name=contract_fact \
    --runtime-type=DOCKER_GO \
    --byte-code-path=./testdata/docker-go-demo/contract_fact.7z \
    --version=1.0 \
    --sdk-conf-path=./testdata/sdk_config.yml \
    --admin-key-file-paths=./testdata/crypto-config/wx-org1.chainmaker.org/user/admin1/admin1.tls.key,./testdata/crypto-config/wx-org2.chainmaker.org/user/admin1/admin1.tls.key,./testdata/crypto-config/wx-org3.chainmaker.org/user/admin1/admin1.tls.key,./testdata/crypto-config/wx-org4.chainmaker.org/user/admin1/admin1.tls.key \
    --admin-crt-file-paths=./testdata/crypto-config/wx-org1.chainmaker.org/user/admin1/admin1.tls.crt,./testdata/crypto-config/wx-org2.chainmaker.org/user/admin1/admin1.tls.crt,./testdata/crypto-config/wx-org3.chainmaker.org/user/admin1/admin1.tls.crt,./testdata/crypto-config/wx-org4.chainmaker.org/user/admin1/admin1.tls.crt \
    --sync-result=true \
    --params="{}"
    
    ## 调用合约
    ./cmc client contract user invoke \
    --contract-name=contract_fact \
    --method=invoke_contract \
    --sdk-conf-path=./testdata/sdk_config.yml \
    --params="{\"method\":\"save\",\"file_name\":\"name007\",\"file_hash\":\"ab3456df5799b87c77e7f88\",\"time\":\"6543234\"}" \
    --sync-result=true
    
    ## 查询合约
    ./cmc client contract user get \
    --contract-name=contract_fact \
    --method=invoke_contract \
    --sdk-conf-path=./testdata/sdk_config.yml \
    --params="{\"method\":\"findByFileHash\",\"file_hash\":\"ab3456df5799b87c77e7f88\"}"
    
  2. 使用Go SDK

    // 创建合约
    func testUserContractCreate(client *sdk.ChainClient, withSyncResult bool, isIgnoreSameContract bool, usernames ...string) {
    
        resp, err := createUserContract(client, factContractName, factVersion, factByteCodePath,
            common.RuntimeType_DOCKER_GO, []*common.KeyValuePair{}, withSyncResult, usernames...)
        if !isIgnoreSameContract {
            if err != nil {
                log.Fatalln(err)
            }
        }
    
        fmt.Printf("CREATE claim contract resp: %+v\n", resp)
    }
    
    func createUserContract(client *sdk.ChainClient, contractName, version, byteCodePath string, runtime common.RuntimeType, kvs []*common.KeyValuePair, withSyncResult bool, usernames ...string) (*common.TxResponse, error) {
    
        payload, err := client.CreateContractCreatePayload(contractName, version, byteCodePath, runtime, kvs)
        if err != nil {
            return nil, err
        }
    
        endorsers, err := examples.GetEndorsers(payload, usernames...)
        if err != nil {
            return nil, err
        }
    
        resp, err := client.SendContractManageRequest(payload, endorsers, createContractTimeout, withSyncResult)
        if err != nil {
            return nil, err
        }
    
        err = examples.CheckProposalRequestResp(resp, true)
        if err != nil {
            return nil, err
        }
    
        return resp, nil
    }
    
    // 调用合约
    // 调用或者查询合约时,method参数请设置为 invoke_contract,此方法会调用合约的InvokeContract方法,再通过参数获得具体方法
    func testUserContractInvoke(client *sdk.ChainClient, method string, withSyncResult bool) (string, error) {
    
        curTime := strconv.FormatInt(time.Now().Unix(), 10)
    
        fileHash := uuid.GetUUID()
        kvs := []*common.KeyValuePair{
            {
                Key: "method",
                Value: []byte("save"),
            },
            {
                Key:   "time",
                Value: []byte(curTime),
            },
            {
                Key:   "file_hash",
                Value: []byte(fileHash),
            },
            {
                Key:   "file_name",
                Value: []byte(fmt.Sprintf("file_%s", curTime)),
            },
        }
    
        err := invokeUserContract(client, factContractName, method, "", kvs, withSyncResult)
        if err != nil {
            return "", err
        }
    
        return fileHash, nil
    }
    
    func invokeUserContract(client *sdk.ChainClient, contractName, method, txId string, kvs []*common.KeyValuePair, withSyncResult bool) error {
    
        resp, err := client.InvokeContract(contractName, method, txId, kvs, -1, withSyncResult)
        if err != nil {
            return err
        }
    
        if resp.Code != common.TxStatusCode_SUCCESS {
            return fmt.Errorf("invoke contract failed, [code:%d]/[msg:%s]\n", resp.Code, resp.Message)
        }
    
        if !withSyncResult {
            fmt.Printf("invoke contract success, resp: [code:%d]/[msg:%s]/[txId:%s]\n", resp.Code, resp.Message, resp.ContractResult.Result)
        } else {
            fmt.Printf("invoke contract success, resp: [code:%d]/[msg:%s]/[contractResult:%s]\n", resp.Code, resp.Message, resp.ContractResult)
        }
    
        return nil
    }
    
1.7.3.2.2. 迭代器合约调用示例
  1. cmc调用

    ## 创建合约
    ./cmc client contract user create \
    --contract-name=contract_fvt \
    --runtime-type=DOCKER_GO \
    --byte-code-path=./testdata/docker-go-demo/contract_fvt.7z \
    --version=1.0 \
    --sdk-conf-path=./testdata/sdk_config.yml \
    --admin-key-file-paths=./testdata/crypto-config/wx-org1.chainmaker.org/user/admin1/admin1.tls.key,./testdata/crypto-config/wx-org2.chainmaker.org/user/admin1/admin1.tls.key,./testdata/crypto-config/wx-org3.chainmaker.org/user/admin1/admin1.tls.key,./testdata/crypto-config/wx-org4.chainmaker.org/user/admin1/admin1.tls.key \
    --admin-crt-file-paths=./testdata/crypto-config/wx-org1.chainmaker.org/user/admin1/admin1.tls.crt,./testdata/crypto-config/wx-org2.chainmaker.org/user/admin1/admin1.tls.crt,./testdata/crypto-config/wx-org3.chainmaker.org/user/admin1/admin1.tls.crt,./testdata/crypto-config/wx-org4.chainmaker.org/user/admin1/admin1.tls.crt \
    --sync-result=true \
    --params="{}"
    
    ## 调用
    ./cmc client contract user get \
    --contract-name=contract_fvt \
    --method=invoke_contract \
    --sdk-conf-path=./testdata/sdk_config.yml \
    --params="{\"method\":\"kv_iterator_test\"}"
    
  2. GO SDK调用

    const (
    	createContractTimeout = 5
    	factContractName      = "contract_fvt"
    	factVersion           = "1.0.0"
        factByteCodePath = "{..}/contract_fvt.7z"
    
    	sdkConfigOrg1Client1Path = "../sdk_configs/sdk_config_org1_client1.yml"
    )
    
    func main() {
    	testUserContractDockerGo()
    }
    
    // 调用主流程
    func testUserContractDockerGo() {
    	fmt.Println("====================== create client ======================")
    	client, err := examples.CreateChainClientWithSDKConf(sdkConfigOrg1Client1Path)
    	if err != nil {
    		log.Fatalln(err)
    	}
    
    	fmt.Println("====================== 创建合约 ======================")
    	usernames := []string{examples.UserNameOrg1Admin1, examples.UserNameOrg2Admin1, examples.UserNameOrg3Admin1, examples.UserNameOrg4Admin1}
    	testUserContractCreate(client, true, true, usernames...)
    
    	fmt.Println("====================== 调用合约 ======================")
    	_, err = testUserContractInvoke(client, "invoke_contract", true)
    	if err != nil {
    		log.Fatalln(err)
    	}
    }
    
    // 创建合约
    func testUserContractCreate(client *sdk.ChainClient, withSyncResult bool, isIgnoreSameContract bool, usernames ...string) {
    	resp, err := createUserContract(client, factContractName, factVersion, factByteCodePath,
    		common.RuntimeType_DOCKER_GO, []*common.KeyValuePair{}, withSyncResult, usernames...)
    	if !isIgnoreSameContract {
    		if err != nil {
    			log.Fatalln(err)
    		}
    	}
    
    	fmt.Printf("CREATE claim contract resp: %+v\n", resp)
    }
    
    // 调用合约
    func testUserContractKvIterator(client *sdk.ChainClient,
    	method string, withSyncResult bool) (string, error) {
    	fileHash := uuid.GetUUID()
    	kvs := []*common.KeyValuePair{
    		{
    			Key:   "method",
    			Value: []byte("kv_iterator_test"),
    		},
    	}
    
    	err := invokeUserContract(client, factContractName, method, "", kvs, withSyncResult)
    	if err != nil {
    		return "", err
    	}
    
    	return "SUCCESS", nil
    }
    
1.7.3.2.3. key历史查询示例
  1. cmc调用

    ## 创建合约 如果前面创建过同名合约,则这里会创建失败
    ./cmc client contract user create \
    --contract-name=contract_fvt \
    --runtime-type=DOCKER_GO \
    --byte-code-path=./testdata/docker-go-demo/contract_fvt.7z \
    --version=1.0 \
    --sdk-conf-path=./testdata/sdk_config.yml \
    --admin-key-file-paths=./testdata/crypto-config/wx-org1.chainmaker.org/user/admin1/admin1.tls.key,./testdata/crypto-config/wx-org2.chainmaker.org/user/admin1/admin1.tls.key,./testdata/crypto-config/wx-org3.chainmaker.org/user/admin1/admin1.tls.key,./testdata/crypto-config/wx-org4.chainmaker.org/user/admin1/admin1.tls.key \
    --admin-crt-file-paths=./testdata/crypto-config/wx-org1.chainmaker.org/user/admin1/admin1.tls.crt,./testdata/crypto-config/wx-org2.chainmaker.org/user/admin1/admin1.tls.crt,./testdata/crypto-config/wx-org3.chainmaker.org/user/admin1/admin1.tls.crt,./testdata/crypto-config/wx-org4.chainmaker.org/user/admin1/admin1.tls.crt \
    --sync-result=true \
    --params="{}"
    
    ## 构造数据
    ./cmc client contract user invoke \
    --contract-name=contract_fvt \
    --method=invoke_contract \
    --sdk-conf-path=./testdata/sdk_config.yml \
    --params="{\"method\":\"put_state\",\"key\":\"key1\",\"field\":\"field1\",\"value\":\"value-1\"}" \
    --sync-result=true
    # 多次调用,构造一批关于 key1, field1 的历史数据
    # --params="{\"method\":\"put_state\",\"key\":\"key1\",\"field\":\"field1\",\"value\":\"value-1\"}"
    # --params="{\"method\":\"put_state\",\"key\":\"key1\",\"field\":\"field1\",\"value\":\"value-2\"}"
    # --params="{\"method\":\"put_state\",\"key\":\"key1\",\"field\":\"field1\",\"value\":\"value-3\"}"
    # --params="{\"method\":\"put_state\",\"key\":\"key1\",\"field\":\"field1\",\"value\":\"value-4\"}"
    # --params="{\"method\":\"del_state\",\"key\":\"key1\",\"field\":\"field1\"}"
    # --params="{\"method\":\"put_state\",\"key\":\"key1\",\"field\":\"field1\",\"value\":\"value-5\"}"
    # --params="{\"method\":\"put_state\",\"key\":\"key1\",\"field\":\"field1\",\"value\":\"value-6\"}"
    
    ## 查询key历史
    ./cmc client contract user get \
    --contract-name=contract_fvt \
    --method=invoke_contract \
    --sdk-conf-path=./testdata/sdk_config.yml \
    --params="{\"method\":\"key_history_kv_iter\",\"key\":\"key1\",\"field\":\"field1\"}"
    
  2. GO SDK调用

    const (
    	createContractTimeout = 5
    	factContractName      = "contract_name"
    	factVersion           = "1.0.0"
        factByteCodePath = "{contract-path}/contract_name.7z"
    
        sdkConfigOrg1Client1Path = "{sdk-config-path}s/sdk_config_org1_client1.yml"
    )
    
    func main() {
    	testUserContractDockerGo()
    }
    
    func testUserContractDockerGo() {
    	fmt.Println("====================== create client ======================")
    	client, err := examples.CreateChainClientWithSDKConf(sdkConfigOrg1Client1Path)
    	if err != nil {
    		log.Fatalln(err)
    	}
    
    	fmt.Println("====================== 创建合约 ======================")
    	usernames := []string{examples.UserNameOrg1Admin1, examples.UserNameOrg2Admin1, examples.UserNameOrg3Admin1, examples.UserNameOrg4Admin1}
    	testUserContractCreate(client, true, true, usernames...)
    
    	fmt.Println("====================== 调用合约,构造数据 ======================")
    	_, err = testUserContractInvoke(client, "invoke_contract", true)
    	if err != nil {
    		log.Fatalln(err)
    	}
    
    	fmt.Println("====================== 调用合约 ======================")
    	_, err = testUserContractKeyHistoryIterator(client, "invoke_contract", true)
    	if err != nil {
    		log.Fatalln(err)
    	}
    }
    
    // 创建合约
    func testUserContractCreate(client *sdk.ChainClient, withSyncResult bool, isIgnoreSameContract bool, usernames ...string) {
    
    	resp, err := createUserContract(client, factContractName, factVersion, factByteCodePath,
    		common.RuntimeType_DOCKER_GO, []*common.KeyValuePair{}, withSyncResult, usernames...)
    	if !isIgnoreSameContract {
    		if err != nil {
    			log.Fatalln(err)
    		}
    	}
    
    	fmt.Printf("CREATE claim contract resp: %+v\n", resp)
    }
    
    // 构造数据
    func testUserContractInvoke(client *sdk.ChainClient,
    	method string, withSyncResult bool) (string, error) {
    
    	fileHash := uuid.GetUUID()
    
    	kvs := [][]*common.KeyValuePair{
    		{
    			{Key: "method", Value: []byte("put_state")},
    			{Key: "key", Value: []byte("key1")},
    			{Key: "field", Value: []byte("field1")},
    			{Key: "value", Value: []byte("val1-1")},
    		},
    		{
    			{Key: "method", Value: []byte("put_state")},
    			{Key: "key", Value: []byte("key1")},
    			{Key: "field", Value: []byte("field1")},
    			{Key: "value", Value: []byte("val1-2")},
    		},
    		{
    			{Key: "method", Value: []byte("put_state")},
    			{Key: "key", Value: []byte("key1")},
    			{Key: "field", Value: []byte("field1")},
    			{Key: "value", Value: []byte("val1-3")},
    		},
    		{
    			{Key: "method", Value: []byte("put_state")},
    			{Key: "key", Value: []byte("key1")},
    			{Key: "field", Value: []byte("field1")},
    			{Key: "value", Value: []byte("val1-4")},
    		},
    		{
    			{Key: "method", Value: []byte("del_state")},
    			{Key: "key", Value: []byte("key1")},
    			{Key: "field", Value: []byte("field1")},
    		},
    		{
    			{Key: "method", Value: []byte("put_state")},
    			{Key: "key", Value: []byte("key1")},
    			{Key: "field", Value: []byte("field1")},
    			{Key: "value", Value: []byte("val1-5")},
    		},
    		{
    			{Key: "method", Value: []byte("put_state")},
    			{Key: "key", Value: []byte("key1")},
    			{Key: "field", Value: []byte("field1")},
    			{Key: "value", Value: []byte("val1-6")},
    		},
    	}
    
    	for _, data := range kvs {
    		err := invokeUserContract(client, factContractName, method, "", data, withSyncResult)
    		if err != nil {
    			return "", err
    		}
    	}
    	return fileHash, nil
    }
    
    // 查询 key 历史数据
    func testUserContractKeyHistoryIterator(client *sdk.ChainClient,
    	method string, withSyncResult bool) (string, error) {
    
    	fileHash := uuid.GetUUID()
    	kvs := []*common.KeyValuePair{
    		{
    			Key:   "method",
    			Value: []byte("key_history_kv_iter"),
    		},
    		{
    			Key:   "key",
    			Value: []byte("key1"),
    		},
    		{
    			Key:   "field",
    			Value: []byte("field1"),
    		},
    	}
    
    	err := invokeUserContract(client, factContractName, method, "", kvs, withSyncResult)
    	if err != nil {
    		return "", err
    	}
    
    	return "SUCCESS", nil
    }
    

1.7.3.3. 代码编写规则

  1. 代码入口包名必须为main

  2. 代码入口

    package main
    
    // sdk代码中,有且仅有一个main()方法
    func main() {  
       // main()方法中,下面的代码为必须代码,不建议修改main()方法当中的代码
       // 其中,TestContract为用户实现合约的具体名称
    	err := shim.Start(new(TestContract))
    	if err != nil {
    		log.Fatal(err)
    	}
    }
    
  3. 合约必要代码

    // 合约结构体,合约名称需要写入main()方法当中
    type TestContract struct {
    }
    
    // 合约必须实现下面两个方法:
    // InitContract(stub shim.CMStubInterface) protogo.Response
    // InvokeContract(stub shim.CMStubInterface) protogo.Response
    
    // 用于合约的部署和升级
    // @param stub: 合约接口
    // @return: 	合约返回结果,包括Success和Error
    func (t *TestContract) InitContract(stub shim.CMStubInterface) protogo.Response {
    
    	return shim.Success([]byte("Init Success"))
    
    }
    
    // 用于合约的调用
    // @param stub: 合约接口
    // @return: 	合约返回结果,包括Success和Error
    func (t *TestContract) InvokeContract(stub shim.CMStubInterface) protogo.Response {
    
    	return shim.Success([]byte("Invoke Success"))
    
    }
    

1.7.3.4. 编译说明

用户如果手工编译,需要将SDK和智能合约放入同一个文件夹,同时保证是在Linux环境下编译,在此文件夹的当前路径执行如下编译命令:

go build -o contract_name

7z a zip_file_name contract_name

在编译合约时,首先使用golang编译程序,保证contract_name和接下来发起交易使用的合约名字一致

编译后使用7zip对编译好的程序进行压缩,目前用户上传的合约都是可执行文件,保证合约编译的环境也安装好7zip工具

1.7.4. 合约发布过程

请参考:《chainmaker-go-sdk》发送创建合约请求的部分,或者《chainmaker-java-sdk》创建合约的部分。

1.7.5. 合约调用过程

请参考:《chainmaker-go-sdk》合约调用的部分,或者《chainmaker-java-sdk》执行合约的部分。

1.7.6. 接口描述

用户与链交互接口

type CMStubInterface interface {
	// GetArgs get arg from transaction parameters
	// @return: 参数map
	GetArgs() map[string][]byte
	// GetState get [key, field] from chain and db
	// @param key: 获取的参数名
	// @param field: 获取的参数名
	// @return1: 获取结果,格式为string
	// @return2: 获取错误信息
	GetState(key, field string) (string, error)
	// GetStateByte get [key, field] from chain and db
	// @param key: 获取的参数名
	// @param field: 获取的参数名
	// @return1: 获取结果,格式为[]byte
	// @return2: 获取错误信息
	GetStateByte(key, field string) ([]byte, error)
	// GetStateFromKey get [key] from chain and db
	// @param key: 获取的参数名
	// @return1: 获取结果,格式为string
	// @return2: 获取错误信息
	GetStateFromKey(key string) (string, error)
	// GetStateFromKeyByte get [key] from chain and db
	// @param key: 获取的参数名
	// @return1: 获取结果,格式为[]byte
	// @return2: 获取错误信息
	GetStateFromKeyByte(key string) ([]byte, error)
	// PutState put [key, field, value] to chain
	// @param1 key: 参数名
	// @param1 field: 参数名
	// @param2 value: 参数值,类型为string
	// @return1: 上传参数错误信息
	PutState(key, field string, value string) error
	// PutStateByte put [key, field, value] to chain
	// @param1 key: 参数名
	// @param1 field: 参数名
	// @param2 value: 参数值,类型为[]byte
	// @return1: 上传参数错误信息
	PutStateByte(key, field string, value []byte) error
	// PutStateFromKey put [key, value] to chain
	// @param1 key: 参数名
	// @param2 value: 参数值,类型为string
	// @return1: 上传参数错误信息
	PutStateFromKey(key string, value string) error
	// PutStateFromKeyByte put [key, value] to chain
	// @param1 key: 参数名
	// @param2 value: 参数值,类型为[]byte
	// @return1: 上传参数错误信息
	PutStateFromKeyByte(key string, value []byte) error
	// DelState delete [key, field] to chain
	// @param1 key: 删除的参数名
	// @param1 field: 删除的参数名
	// @return1:删除参数的错误信息
	DelState(key, field string) error
	// DelStateFromKey delete [key] to chain
	// @param1 key: 删除的参数名
	// @return1:删除参数的错误信息
	DelStateFromKey(key string) error
	// GetCreatorOrgId get tx creator org id
	// @return1: 合约创建者的组织ID
	// @return2: 获取错误信息
	GetCreatorOrgId() (string, error)
	// GetCreatorRole get tx creator role
	// @return1: 合约创建者的角色
	// @return2: 获取错误信息
	GetCreatorRole() (string, error)
	// GetCreatorPk get tx creator pk
	// @return1: 合约创建者的公钥
	// @return2: 获取错误信息
	GetCreatorPk() (string, error)
	// GetSenderOrgId get tx sender org id
	// @return1: 交易发起者的组织ID
	// @return2: 获取错误信息
	GetSenderOrgId() (string, error)
	// GetSenderRole get tx sender role
	// @return1: 交易发起者的角色
	// @return2: 获取错误信息
	GetSenderRole() (string, error)
	// GetSenderPk get tx sender pk
	// @return1: 交易发起者的公钥
	// @return2: 获取错误信息
	GetSenderPk() (string, error)
	// GetBlockHeight get tx block height
	// @return1: 当前块高度
	// @return2: 获取错误信息
	GetBlockHeight() (int, error)
	// GetTxId get current tx id
	// @return1: 交易ID
	// @return2: 获取错误信息
	GetTxId() (string, error)
	// GetTxTimeStamp get tx timestamp
	// @return1: 交易timestamp
	// @return2: 获取错误信息
	GetTxTimeStamp() (string, error)
	// EmitEvent emit event, you can subscribe to the event using the SDK
	// @param1 topic: 合约事件的主题
	// @param2 data: 合约事件的数据,参数数量不可大于16
	EmitEvent(topic string, data []string)
	// Log record log to chain server
	// @param message: 事情日志的信息
	Log(message string)
	// CallContract invoke another contract and get response
	// @param1: 合约名称
	// @param2: 合约版本
	// @param3: 合约参数
	// @return1: 合约结果
	CallContract(contractName, contractVersion string, args map[string][]byte) protogo.Response
	// NewIterator range of [startKey, limitKey), front closed back open
	// @param1: 范围查询起始key
	// @param2: 范围查询结束key
	// @return1: 根据起始key生成的迭代器
	// @return2: 获取错误信息
	NewIterator(startKey string, limitKey string) (ResultSetKV, error)
	// NewIteratorWithField range of [key+"#"+startField, key+"#"+limitField), front closed back open
	// @param1: 分别与param2, param3 构成查询起始和结束的key
	// @param2: [param1 + "#" + param2] 来获取查询起始的key
	// @param3: [param1 + "#" + param3] 来获取查询结束的key
	// @return1: 根据起始位置生成的迭代器
	// @return2: 获取错误信息
	NewIteratorWithField(key string, startField string, limitField string) (ResultSetKV, error)
	// NewIteratorPrefixWithKeyField range of [key+"#"+field, key+"#"+field], front closed back closed
	// @param1: [ param1 + "#" +param2 ] 构成前缀范围查询的key
	// @param2: [ param1 + "#" +param2 ] 构成前缀范围查询的key
	// @return1: 根据起始位置生成的迭代器
	// @return2: 获取错误信息
	NewIteratorPrefixWithKeyField(key string, field string) (ResultSetKV, error)
	// NewIteratorPrefixWithKey range of [key, key], front closed back closed
	// @param1: 前缀范围查询起始key
	// @return1: 根据起始位置生成的迭代器
	// @return2: 获取错误信息
	NewIteratorPrefixWithKey(key string) (ResultSetKV, error)
	// NewHistoryKvIterForKey Build an iterator to traverse the historical value of the specified key and field
	// @param1: 指定查询历史数据的key 
	// @param2: 指定查询历史数据的field 
	// @return1: 根据指定的key、field生成迭代器,用于遍历该key、field对应value的历史值 
	// @return2: 获取错误信息
	NewHistoryKvIterForKey(key, field string) (KeyHistoryKvIter, error)
}

// ResultSet iterator query result
type ResultSet interface {
	// NextRow get next row,
	// sql: column name is EasyCodec key, value is EasyCodec string val. as: val := ec.getString("columnName")
	// kv iterator: key/value is EasyCodec key for "key"/"value", value type is []byte. as: k, _ := ec.GetString("key") v, _ := ec.GetBytes("value")
	NextRow() (*serialize.EasyCodec, error)
	// HasNext return does the next line exist
	HasNext() bool
	// Close .
	Close() (bool, error)
}

type ResultSetKV interface {
	ResultSet
	// Next return key,field,value,code
	Next() (string, string, []byte, error)
}

type KeyHistoryKvIter interface {
	ResultSet
	// Next return txId, blockHeight, timestamp, value, isDelete, error
	Next() (*KeyModification, error)
}