Hyperledger Fabric链码的新软件模型



不久之前,fabric-contract-api-go第一个版本 已发布-RFC 0001 chaincode的新软件模型的实现让我们看看它是什么以及如何使用它。

在这里,我准备了一个具有简单Fabric网络的存储库,对等方以dev模式运行。按照存储库中的说明启动网络并返回(最多不超过5分钟)。

现在您已经运行了网络并安装了链码,让我们看一下在新模型中工作的链码的内部。

SimpleContract.go中,我们使用新的API导入模块:

github.com/hyperledger/fabric-contract-api-go/contractapi

接下来,我们使用嵌入了Contract结构SimpleContract结构描述我们的合同

type SimpleContract struct {
	contractapi.Contract
}


必须先 构建Contract,以便我们的合同满足ContractInterface接口在这里,您应该预约并说合同!= Chaincode。链码是无限数量的合同的容器。Chaincode将其合同存储在地图上,如以下清单所示:


type ContractChaincode struct {
	DefaultContract       string
	contracts             map[string]contractChaincodeContract
	metadata              metadata.ContractChaincodeMetadata
	Info                  metadata.InfoMetadata
	TransactionSerializer serializer.TransactionSerializer
}

Invoke内部使用 地图合同来路由请求:

func (cc *ContractChaincode) Invoke(stub shim.ChaincodeStubInterface) peer.Response {

	nsFcn, params := stub.GetFunctionAndParameters()

	li := strings.LastIndex(nsFcn, ":")

	var ns string
	var fn string

	if li == -1 {
		ns = cc.DefaultContract
		fn = nsFcn
	} else {
		ns = nsFcn[:li]
		fn = nsFcn[li+1:]
	}
       ...
       nsContract := cc.contracts[ns]
       ...
       successReturn, successIFace, errorReturn = nsContract.functions[fn].Call(ctx, transactionSchema, &cc.metadata.Components, serializer, params...)
       ...
       return shim.Success([]byte(successReturn))
}  

回到SimpleContract。所有方法都必须具有一个满足TransactionContextInterface接口ctx参数默认情况下,所有方法都获得一个标准的TransactionContext,在大多数情况下就足够了。例如, 此上下文使我们可以使用ClientIdentity,如下所示:



func (sc *SimpleContract) Whois(ctx contractapi.TransactionContextInterface) (string, error) {
	return ctx.GetClientIdentity().GetID()
}

或获取熟悉的存根(shim.ChaincodeStubInterface)来执行与账本进行交互的所有常规操作:

func (sc *SimpleContract) Write(ctx contractapi.TransactionContextInterface, key string, value []byte) error {
	return ctx.GetStub().PutState(key, value)
}

但!在我们的演示存储库的代码中,您可以在方法中看到完全不同的上下文:

func (sc *SimpleContract) Create(ctx CustomTransactionContextInterface, key string, value string) error {
	existing := ctx.GetData()

	if existing != nil {
		return fmt.Errorf("Cannot create world state pair with key %s. Already exists", key)
	}

	err := ctx.GetStub().PutState(key, []byte(value))

	if err != nil {
		return errors.New("Unable to interact with world state")
	}

	return nil
}

这是一个自定义上下文。它的创建非常简单。从我们的存储库中注意context.go

1.我们声明一个与contractapi.TransactionContextInterface兼容的接口

type CustomTransactionContextInterface interface {
	contractapi.TransactionContextInterface
	GetData() []byte
	SetData([]byte)
}

2.我们将contractapi.TransactionContext嵌入其中的结构

type CustomTransactionContext struct {
	contractapi.TransactionContext
	data []byte
}

3.我们实现声明的方法

// GetData return set data
func (ctc *CustomTransactionContext) GetData() []byte {
	return ctc.data
}

// SetData provide a value for data
func (ctc *CustomTransactionContext) SetData(data []byte) {
	ctc.data = data
}

现在,初始化合同时,我们只需将此结构作为处理程序传递:

simpleContract := new(SimpleContract)

simpleContract.TransactionContextHandler = new(CustomTransactionContext)

现在,我们合同的所有方法都代替了ctx contractapi.TransactionContextInterface接受ctx CustomTransactionContextInterface

需要一个自定义上下文来通过事务挂钩推送状态事务挂钩是中间件的一个漂亮名称,它在调用contract方法之前或之后触发。

一个钩子的示例,该钩子在调用方法之前从分类账中提取交易中第一个参数传递的键的值:

SimpleContract.go
func GetWorldState(ctx CustomTransactionContextInterface) error {
	_, params := ctx.GetStub().GetFunctionAndParameters()

	if len(params) < 1 {
		return errors.New("Missing key for world state")
	}

	existing, err := ctx.GetStub().GetState(params[0])

	if err != nil {
		return errors.New("Unable to interact with world state")
	}

	ctx.SetData(existing)

	return nil
}

main.go
simpleContract.BeforeTransaction = GetWorldState

现在,我们可以在方法中更简洁一些地获取请求的键的值:

SimpleContract.go
func (sc *SimpleContract) Read(ctx CustomTransactionContextInterface, key string) (string, error) {
	existing := ctx.GetData()

	if existing == nil {
		return "", fmt.Errorf("Cannot read world state pair with key %s. Does not exist", key)
	}

	return string(existing), nil
}

方法调用之后的钩子几乎是相同的,除了它除了上下文外,它还接受一个空接口(为什么需要它,我们稍后会弄清楚):

YetAnotherContract.go
func After(ctx contractapi.TransactionContextInterface, beforeValue interface{}) error {
	fmt.Println(ctx.GetStub().GetTxID())
	fmt.Println("beforeValue", beforeValue)
	return nil
}

该挂钩显示事务ID和该挂钩之前方法返回要检查此后钩,可以进入CLI容器并调用contract方法: 切换到运行代码的终端,输出将类似于以下内容:

docker exec -it cli sh
peer chaincode query -n mycc -c '{"Args":["YetAnotherContract:SayHi"]}' -C myc




e503e98e4c71285722f244a481fbcbf0ff4120adcd2f9067089104e5c3ed0efe#txid
beforeValue Hi there#前一种方法的值

如果我们要处理功能名称不存在的请求怎么办?为此,任何合同都具有UnknownTransaction字段

unknown_handler.go
func UnknownTransactionHandler(ctx CustomTransactionContextInterface) error {
	fcn, args := ctx.GetStub().GetFunctionAndParameters()
	return fmt.Errorf("Invalid function %s passed with args %v", fcn, args)
}

main.go
simpleContract.UnknownTransaction = UnknownTransactionHandler

也可以通过CLI进行验证: 结论:

docker exec -it cli sh
peer chaincode query -n mycc -c '{"Args":["BadRequest", "BadKey"]}' -C myc




错误:查询期间背书失败。响应:状态:500消息:“无效参数BadRequest与args [BadKey]一起传递”

为了使代码在对等方开始,我们必须像以前一样调用Start()方法,然后再将所有合同传输到代码

main.go
cc, err := contractapi.NewChaincode(simpleContract, yetAnotherContract)

	if err != nil {
		panic(err.Error())
	}

	if err := cc.Start(); err != nil {
		panic(err.Error())
	}


新的代码模型解决了路由,中间件,返回值的序列化,字符串参数反序列化的问题(可以使用接口{}以外的任何类型)。现在仍然需要等待Go SDK的新模型的实现。

谢谢您的关注。

第二部分

All Articles