概述
智能合约本质上是运行在某种环境(例如虚拟机)中的一段代码逻辑。
长安链的智能合约是运行在长安链上的一组“动态代码”,类似于Fabric的chaincode,Fabric的智能合约称为链码(chaincode),分为系统链码和用户链码。长安链的合约分为用户合约和系统合约。
长安链·ChainMaker目前已经支持使用C++、Go、Rust、Solidity进行智能合约开发,这里介绍goland合约的开发参考。
环境依赖
长安链运行docker-go合约的环境依赖如下:
名称 | 版本 | 描述 | 是否必须 |
---|---|---|---|
docker | 18+ | 独立运行容器 | 是 |
7zip | 16+ | 压缩、解压合约文件 | 是 |
2.2.0版本的合约开发:
编写测试合约:
2.2.0的sdk 不支持 go mod 的形式 ,需要下载sdk 源码,参考其中的“demo”文件夹下的示例合约,
Files · v2.2.0 · chainmaker / contract-sdk-go · ChainMaker
以及开源文档,“5.7.3.1.示例代码说明”
5. 智能合约开发 — chainmaker-docs v2.2.0 documentation
方法说明:
shim/interfaces.go · v2.2.0 · chainmaker / contract-sdk-go · ChainMaker
以下是根据官网编写的一份示例合约
/*Copyright (C) BABEC. All rights reserved.Copyright (C) THL A29 Limited, a Tencent company. All rights reserved.SPDX-License-Identifier: Apache-2.0*/package demoimport ("crypto/rand""encoding/json""log""math/big""strconv""fmt" // print"chainmaker.org/chainmaker/contract-sdk-go/v2/pb/protogo""chainmaker.org/chainmaker/contract-sdk-go/v2/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)case "random":return f.random()default:return shim.Error("invalid method")}}func (f *FactContract) random() protogo.Response {// 随机数逻辑number, _ := rand.Int(rand.Reader, big.NewInt(10000000000))fmt.Println(number)number_str := fmt.Sprint(number)//number_str := strconv.Itoa(number)// 返回结果return shim.Success([]byte(number_str))}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)}}
编译合约
编译几个必要条件:
需要在linux环境下编译,如果在mac平台编译,需要把build.sh里面的go build main.go改成CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build main.go
代码入口包名必须为
main 见步骤1,2
main的固定写法:
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和Errorfunc (t *TestContract) InitContract(stub shim.CMStubInterface) protogo.Response {return shim.Success([]byte("Init Success"))}// 用于合约的调用// @param stub: 合约接口// @return: 合约返回结果,包括Success和Errorfunc (t *TestContract) InvokeContract(stub shim.CMStubInterface) protogo.Response {return shim.Success([]byte("Invoke Success"))}
编译合约的具体操作步骤:
1. 将contract-sdk-go/main.go的内容替换成自己编写的合约:
2. 将自己编写的合约中package 改为main
3. 运行./build.sh 输入合约名。
2.3.0版本的合约开发:
2.3.0版本的Golang合约SDK支持通过go.mod的方式引用,可直接使用go get引用
1. 创建一个project:
2. 在terminal里执行
go get chainmaker.org/chainmaker/contract-sdk-go/v2@v2.3.2
3. 按照官方给出的示例编写合约。
2. 使用Golang进行智能合约开发 — chainmaker-docs v2.3.0 documentation
4. 在当前目录 下运行:
go build -ldflags=”-s -w” -o contract_save.go
生成结果如下:
├── contract_save 生成的二进制文件
├── contract_save.7z 打包好的压缩文件,用于在链上创建合约
└── contract_save.go 源文件
部署合约到区块链中
使用长安链提供的cmc工具部署:
示例脚本:
# pk模式./cmc client contract user create \--contract-name=save_random \--runtime-type=DOCKER_GO \--byte-code-path=./testdata/docker-go-demo/save_random.7z \--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="{}"# cert模式./cmc client contract user create \--contract-name=save_random \--runtime-type=DOCKER_GO \--byte-code-path=./testdata/docker-go-demo/save_random.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="{}"
使用sdk部署:chainmaker / sdk-go · ChainMaker
示例代码参考: sdk-go/examples/user_contract_claim_docker
func testUserContractClaimCreate(client *sdk.ChainClient, withSyncResult bool, isIgnoreSameContract bool, usernames ...string) string {resp, err := createUserContract(client, claimContractName, claimVersion, claimByteCodePath,common.RuntimeType_DOCKER_GO, []*common.KeyValuePair{}, withSyncResult, usernames...)if err != nil {if !isIgnoreSameContract {log.Fatalln(err)} else {fmt.Printf("CREATE claim contract failed, err: %s, resp: %+v\n", err, resp)}} else {fmt.Printf("CREATE claim contract success, resp: %+v\n", resp)}if resp != nil {return resp.TxId}return ""}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}payload = client.AttachGasLimit(payload, &common.Limit{GasLimit: 60000000,})//endorsers, err := examples.GetEndorsers(payload, usernames...)endorsers, err := examples.GetEndorsersWithAuthType(client.GetHashType(),client.GetAuthType(), payload, usernames...)if err != nil {return nil, err}resp, err := client.SendContractManageRequest(payload, endorsers, createContractTimeout, withSyncResult)if err != nil {return resp, err}err = examples.CheckProposalRequestResp(resp, true)if err != nil {return resp, err}return resp, nil}