Fabric 智能合约 example02详细解读

引言

在部署单机多节点Fabric 网络时,我们使用的是e2e_cli测试样例中的智能合约:example02,其路径为:/opt/gopath/src/github.com/hyperledger/fabric/aberic/chaincode/go/chaincode_example02,本章将对这个简单的只能合约做深度的解析。

1、智能合约部署

首先安装智能合约的命令为:

peer chaincode install -n mychannel -p github.com/hyperledger/fabric/aberic/
chaincode/go/chaincode/go/chaincode_example02 -v 1.0

安装智能合约的日志如下:

2020-06-08 07:00:42.296 UTC [msp] GetLocalMSP -> DEBU 001 Returning existing local MSP
2020-06-08 07:00:42.296 UTC [msp] GetDefaultSigningIdentity -> DEBU 002 Obtaining default signing identity
2020-06-08 07:00:42.296 UTC [chaincodeCmd] checkChaincodeCmdParams -> INFO 003 Using default escc
2020-06-08 07:00:42.296 UTC [chaincodeCmd] checkChaincodeCmdParams -> INFO 004 Using default vscc
2020-06-08 07:00:42.408 UTC [golang-platform] getCodeFromFS -> DEBU 005 getCodeFromFS github.com/hyperledger/fabric/aberic/chaincode/go/chaincode_example02
2020-06-08 07:00:42.643 UTC [golang-platform] func1 -> DEBU 006 Discarding GOROOT package fmt
2020-06-08 07:00:42.643 UTC [golang-platform] func1 -> DEBU 007 Discarding provided package github.com/hyperledger/fabric/core/chaincode/shim
2020-06-08 07:00:42.643 UTC [golang-platform] func1 -> DEBU 008 Discarding provided package github.com/hyperledger/fabric/protos/peer
2020-06-08 07:00:42.643 UTC [golang-platform] func1 -> DEBU 009 Discarding GOROOT package strconv
2020-06-08 07:00:42.643 UTC [golang-platform] GetDeploymentPayload -> DEBU 00a done
2020-06-08 07:00:42.644 UTC [msp/identity] Sign -> DEBU 00b Sign: plaintext: 0A86070A5C08031A0C089AC4F7F60510...2FBAFE130000FFFFBC6386C1002C0000 
2020-06-08 07:00:42.644 UTC [msp/identity] Sign -> DEBU 00c Sign: digest: B8C34039C216016ABD32E7B74CA92DE3268BF433FB2717666208CF766E75FB1F 
2020-06-08 07:00:42.649 UTC [chaincodeCmd] install -> DEBU 00d Installed remotely response:<status:200 payload:"OK" > 
2020-06-08 07:00:42.649 UTC [main] main -> INFO 00e Exiting.....

2、实例化Chaincode

安装完成后需要进行实例化chaincode,执行如下命令:

peer chaincode instantiate -o orderer.example.com:7050 -C mychannel -n 
mychannel -c '{"Args":["init","A","10","B","10"]}' -P "OR ('Org1MSP.member')" -v 1.0

实例化chaincode的日志如下:

2020-06-08 07:05:59.323 UTC [msp] GetLocalMSP -> DEBU 001 Returning existing local MSP
2020-06-08 07:05:59.323 UTC [msp] GetDefaultSigningIdentity -> DEBU 002 Obtaining default signing identity
2020-06-08 07:05:59.324 UTC [chaincodeCmd] checkChaincodeCmdParams -> INFO 003 Using default escc
2020-06-08 07:05:59.324 UTC [chaincodeCmd] checkChaincodeCmdParams -> INFO 004 Using default vscc
2020-06-08 07:05:59.324 UTC [msp/identity] Sign -> DEBU 005 Sign: plaintext: 0A91070A6708031A0C08D7C6F7F60510...314D53500A04657363630A0476736363 
2020-06-08 07:05:59.324 UTC [msp/identity] Sign -> DEBU 006 Sign: digest: 58805553D0FCD1F5B30140EA706BC2CAB22E56627011C1CD55E3DCBDFDFD4B00 
2020-06-08 07:06:22.303 UTC [msp/identity] Sign -> DEBU 007 Sign: plaintext: 0A91070A6708031A0C08D7C6F7F60510...C6BE9382352C9F3E65C5165CB5442EFF 
2020-06-08 07:06:22.303 UTC [msp/identity] Sign -> DEBU 008 Sign: digest: 92235FAD70B935B6AC7A58928D7B6DF0B768010BC3C5F8910A43DE945FC6788B 
2020-06-08 07:06:22.308 UTC [main] main -> INFO 009 Exiting.....

3、智能合约分析

example02的源码如下所示:

package main
import (
	"fmt"
	"strconv"

	"github.com/hyperledger/fabric/core/chaincode/shim"
	pb "github.com/hyperledger/fabric/protos/peer"
)

// SimpleChaincode example simple Chaincode implementation
type SimpleChaincode struct {
}

func (t *SimpleChaincode) Init(stub shim.ChaincodeStubInterface) pb.Response {
	fmt.Println("ex02 Init")
	_, args := stub.GetFunctionAndParameters()
	var A, B string    // Entities
	var Aval, Bval int // Asset holdings
	var err error

	if len(args) != 4 {
		return shim.Error("Incorrect number of arguments. Expecting 4")
	}

	// Initialize the chaincode
	A = args[0]
	Aval, err = strconv.Atoi(args[1])
	if err != nil {
		return shim.Error("Expecting integer value for asset holding")
	}
	B = args[2]
	Bval, err = strconv.Atoi(args[3])
	if err != nil {
		return shim.Error("Expecting integer value for asset holding")
	}
	fmt.Printf("Aval = %d, Bval = %d\n", Aval, Bval)

	// Write the state to the ledger
	err = stub.PutState(A, []byte(strconv.Itoa(Aval)))
	if err != nil {
		return shim.Error(err.Error())
	}

	err = stub.PutState(B, []byte(strconv.Itoa(Bval)))
	if err != nil {
		return shim.Error(err.Error())
	}

	return shim.Success(nil)
}

func (t *SimpleChaincode) Invoke(stub shim.ChaincodeStubInterface) pb.Response {
	fmt.Println("ex02 Invoke")
	function, args := stub.GetFunctionAndParameters()
	if function == "invoke" {
		// Make payment of X units from A to B
		return t.invoke(stub, args)
	} else if function == "delete" {
		// Deletes an entity from its state
		return t.delete(stub, args)
	} else if function == "query" {
		// the old "Query" is now implemtned in invoke
		return t.query(stub, args)
	}

	return shim.Error("Invalid invoke function name. Expecting \"invoke\" \"delete\" \"query\"")
}

// Transaction makes payment of X units from A to B
func (t *SimpleChaincode) invoke(stub shim.ChaincodeStubInterface, args []string) pb.Response {
	var A, B string    // Entities
	var Aval, Bval int // Asset holdings
	var X int          // Transaction value
	var err error

	if len(args) != 3 {
		return shim.Error("Incorrect number of arguments. Expecting 3")
	}

	A = args[0]
	B = args[1]

	// Get the state from the ledger
	// TODO: will be nice to have a GetAllState call to ledger
	Avalbytes, err := stub.GetState(A)
	if err != nil {
		return shim.Error("Failed to get state")
	}
	if Avalbytes == nil {
		return shim.Error("Entity not found")
	}
	Aval, _ = strconv.Atoi(string(Avalbytes))

	Bvalbytes, err := stub.GetState(B)
	if err != nil {
		return shim.Error("Failed to get state")
	}
	if Bvalbytes == nil {
		return shim.Error("Entity not found")
	}
	Bval, _ = strconv.Atoi(string(Bvalbytes))

	// Perform the execution
	X, err = strconv.Atoi(args[2])
	if err != nil {
		return shim.Error("Invalid transaction amount, expecting a integer value")
	}
	Aval = Aval - X
	Bval = Bval + X
	fmt.Printf("Aval = %d, Bval = %d\n", Aval, Bval)

	// Write the state back to the ledger
	err = stub.PutState(A, []byte(strconv.Itoa(Aval)))
	if err != nil {
		return shim.Error(err.Error())
	}

	err = stub.PutState(B, []byte(strconv.Itoa(Bval)))
	if err != nil {
		return shim.Error(err.Error())
	}

	return shim.Success(nil)
}

// Deletes an entity from state
func (t *SimpleChaincode) delete(stub shim.ChaincodeStubInterface, args []string) pb.Response {
	if len(args) != 1 {
		return shim.Error("Incorrect number of arguments. Expecting 1")
	}

	A := args[0]

	// Delete the key from the state in ledger
	err := stub.DelState(A)
	if err != nil {
		return shim.Error("Failed to delete state")
	}

	return shim.Success(nil)
}

// query callback representing the query of a chaincode
func (t *SimpleChaincode) query(stub shim.ChaincodeStubInterface, args []string) pb.Response {
	var A string // Entities
	var err error

	if len(args) != 1 {
		return shim.Error("Incorrect number of arguments. Expecting name of the person to query")
	}

	A = args[0]

	// Get the state from the ledger
	Avalbytes, err := stub.GetState(A)
	if err != nil {
		jsonResp := "{\"Error\":\"Failed to get state for " + A + "\"}"
		return shim.Error(jsonResp)
	}

	if Avalbytes == nil {
		jsonResp := "{\"Error\":\"Nil amount for " + A + "\"}"
		return shim.Error(jsonResp)
	}

	jsonResp := "{\"Name\":\"" + A + "\",\"Amount\":\"" + string(Avalbytes) + "\"}"
	fmt.Printf("Query Response:%s\n", jsonResp)
	return shim.Success(Avalbytes)
}

func main() {
	err := shim.Start(new(SimpleChaincode))
	if err != nil {
		fmt.Printf("Error starting Simple chaincode: %s", err)
	}
}

首先,链码启动必须通过调用shim包中的start函数,传递一个类型为chaincode的参数,所以,我们首先来看看chaincode类型接口。

type chaincode interface{
	Init(stub ChaincodestubInterface) peer.Response
	Invoke(stub ChaincodestubInterface) peer.Response
}

ChainCode的Go代码需要定义一个SimpleChaincode这样一个struct,然后在该struct上定义Init和Invoke两个函数,然后还要定义一个main函数,作为ChainCode的启动入口。其中Init在链码实例化或者升级的时候被调用,而Invoke则在更新或查询账本数据状态时被调用,需要在此方法中实现响应调用或查询的业务逻辑。

3.1、首先看Init函数:
func (t *SimpleChaincode) Init(stub shim.ChaincodeStubInterface) pb.Response {
	fmt.Println("ex02 Init")
	_, args := stub.GetFunctionAndParameters()//GetFunctionAndParameters解析调用的时候传入的参数
	var A, B string    // Entities
	var Aval, Bval int // Asset holdings
	var err error

	if len(args) != 4 {
		return shim.Error("Incorrect number of arguments. Expecting 4")
	}

	// Initialize the chaincode
	A = args[0]
	Aval, err = strconv.Atoi(args[1])
	if err != nil {
		return shim.Error("Expecting integer value for asset holding")
	}
	B = args[2]
	Bval, err = strconv.Atoi(args[3])
	if err != nil {
		return shim.Error("Expecting integer value for asset holding")
	}
	fmt.Printf("Aval = %d, Bval = %d\n", Aval, Bval)

	// Write the state to the ledger
	err = stub.PutState(A, []byte(strconv.Itoa(Aval)))
	if err != nil {
		return shim.Error(err.Error())
	}

	err = stub.PutState(B, []byte(strconv.Itoa(Bval)))
	if err != nil {
		return shim.Error(err.Error())
	}

	return shim.Success(nil)
}

其中:

_, args := stub.GetFunctionAndParameters()

获取调用的时候传入的参数, 将字符串数组的参数分为两部分,数组第一个字是Function,剩下的都是Parameter


	if len(args) != 4 {
		return shim.Error("Incorrect number of arguments. Expecting 4")
	}

判断输入参数是否为4个,若不为4个,则返回错误:Incorrect number of arguments. Expecting 4


A = args[0]
	Aval, err = strconv.Atoi(args[1])//strconv为格式转换函数,strconv.Atoi()为int转字符串
	if err != nil {
		return shim.Error("Expecting integer value for asset holding")
	}

获取A账户(A赋值第一个参数),A账户给予10的初始资产(Aval赋值第二个参数,值为10),若查询失败,则返回错误:Expecting integer value for asset holding


fmt.Printf("Aval = %d, Bval = %d\n", Aval, Bval)

向日志中打印AB的余额。


	err = stub.PutState(A, []byte(strconv.Itoa(Aval)))
	if err != nil {
		return shim.Error(err.Error())
	}

	err = stub.PutState(B, []byte(strconv.Itoa(Bval)))
	if err != nil {
		return shim.Error(err.Error())
	}

将账户 A,B 的状态写入账本中


3.2 分析Invoke 函数
func (t *SimpleChaincode) Invoke(stub shim.ChaincodeStubInterface) pb.Response {
	fmt.Println("ex02 Invoke")
	function, args := stub.GetFunctionAndParameters()
	if function == "invoke" {
		// Make payment of X units from A to B
		return t.invoke(stub, args)
	} else if function == "delete" {
		// Deletes an entity from its state
		return t.delete(stub, args)
	} else if function == "query" {
		// the old "Query" is now implemtned in invoke
		return t.query(stub, args)
	}

	return shim.Error("Invalid invoke function name. Expecting \"invoke\" \"delete\" \"query\"")
}

其中:

function, args := stub.GetFunctionAndParameters()

function保存执行的函数名,args保存函数后跟的参数

然后根据function的不同,执行invoke、delete或query函数。

  • invoke 实现转账操作
  • delete 实现账户注销
  • query 实现账户查询操作

分析 invoke 函数

基本语法和之前的相似,就不一一说明了,在代码中标注

func (t *SimpleChaincode) invoke(stub shim.ChaincodeStubInterface, args []string) pb.Response {
	var A, B string    // 账户 A 和 B
	var Aval, Bval int // 账户余额
	var X int          // 转账金额
	var err error

	if len(args) != 3 {
		return shim.Error("Incorrect number of arguments. Expecting 3")
	}

	A = args[0]    // 账户 A 用户名
	B = args[1]    // 账户 B 用户名

	// Get the state from the ledger从账本中获取 A 的余额
	// TODO: will be nice to have a GetAllState call to ledger
	Avalbytes, err := stub.GetState(A)
	if err != nil {
		return shim.Error("Failed to get state")
	}
	if Avalbytes == nil {
		return shim.Error("Entity not found")
	}
	Aval, _ = strconv.Atoi(string(Avalbytes))
	//从账本中获取 B 的余额
	Bvalbytes, err := stub.GetState(B)
	if err != nil {
		return shim.Error("Failed to get state")
	}
	if Bvalbytes == nil {
		return shim.Error("Entity not found")
	}
	Bval, _ = strconv.Atoi(string(Bvalbytes))

	 X 为 转账金额
	X, err = strconv.Atoi(args[2])
	if err != nil {
		return shim.Error("Invalid transaction amount, expecting a integer value")
	}
	//转账
	Aval = Aval - X
	Bval = Bval + X
	fmt.Printf("Aval = %d, Bval = %d\n", Aval, Bval)

	// 更新转账后账本中 A 余额
	err = stub.PutState(A, []byte(strconv.Itoa(Aval)))
	if err != nil {
		return shim.Error(err.Error())
	}
// 更新转账后账本中 B 余额
	err = stub.PutState(B, []byte(strconv.Itoa(Bval)))
	if err != nil {
		return shim.Error(err.Error())
	}

	return shim.Success(nil)
}

分析 delete 函数

func (t *SimpleChaincode) delete(stub shim.ChaincodeStubInterface, args []string) pb.Response {
	if len(args) != 1 {
		return shim.Error("Incorrect number of arguments. Expecting 1")
	}

	A := args[0]// 账户用户名

	// 从账本中删除该账户状态
	err := stub.DelState(A)
	if err != nil {
		return shim.Error("Failed to delete state")
	}

	return shim.Success(nil)
}

分析 query 函数

func (t *SimpleChaincode) query(stub shim.ChaincodeStubInterface, args []string) pb.Response {
	var A string // Entities
	var err error

	if len(args) != 1 {
		return shim.Error("Incorrect number of arguments. Expecting name of the person to query")
	}

	A = args[0]//账户用户名

	// 从账本中获取该账户余额
	Avalbytes, err := stub.GetState(A)
	if err != nil {
		jsonResp := "{\"Error\":\"Failed to get state for " + A + "\"}"
		return shim.Error(jsonResp)
	}

	if Avalbytes == nil {
		jsonResp := "{\"Error\":\"Nil amount for " + A + "\"}"
		return shim.Error(jsonResp)
	}

	jsonResp := "{\"Name\":\"" + A + "\",\"Amount\":\"" + string(Avalbytes) + "\"}"
	fmt.Printf("Query Response:%s\n", jsonResp)
	// 返回转账金额
	return shim.Success(Avalbytes)
}

  • 3
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值