区块链学习2-合约开发

概述

智能合约本质上是运行在某种环境(例如虚拟机)中的一段代码逻辑。

长安链的智能合约是运行在长安链上的一组“动态代码”,类似于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 demo

import (
	"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)
	}
}

编译合约

编译几个必要条件:

  1.  需要在linux环境下编译,如果在mac平台编译,需要把build.sh里面的go build main.go改成CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build main.go

  2. 代码入口包名必须为main 见步骤1,2

  3. 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和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. 将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/[email protected]

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: %+vn", err, resp)
		}
	} else {
		fmt.Printf("CREATE claim contract success, resp: %+vn", 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
}

本图文内容来源于网友网络收集整理提供,作为学习参考使用,版权属于原作者。
THE END
分享
二维码

)">
< <上一篇

)">
下一篇>>