Novo modelo de software para o código de código do Hyperledger Fabric



Há pouco tempo, foi lançada a primeira versão do fabric-contract-api-go - a implementação do novo modelo de software para o código de código RFC 0001 . Vamos ver o que é e como usá-lo.

Aqui eu preparei um repositório com uma rede Fabric simples, onde os pares são executados no modo dev. Siga as instruções do repositório para iniciar a rede e retornar (não levará mais de 5 minutos).

Agora que você tem a rede em execução e o código de código está instalado, vejamos o interior do código de código que trabalha no novo modelo.

No SimpleContract.go, importamos o módulo com a nova API:

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

Em seguida, descrevemos nosso contrato usando a estrutura SimpleContract na qual a estrutura do Contrato está incorporada :

type SimpleContract struct {
	contractapi.Contract
}


É imperativo criar um Contrato para que nosso contrato satisfaça a interface ContractInterface . Aqui você deve fazer uma reserva e dizer que o contrato! = Chaincode. Um código de código é um contêiner de um número indefinido de contratos. Chaincode armazena seus contratos no mapa, como visto nesta listagem:


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

Os contratos de mapa são usados ​​internamente pelo Invoke para rotear solicitações:

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))
}  

Então, de volta ao SimpleContract. Todos os métodos devem ter um parâmetro ctx que satisfaça a interface TransactionContextInterface . Por padrão, todos os métodos obtêm um TransactionContext padrão , que na maioria dos casos é suficiente.

Esse contexto nos permite trabalhar com ClientIdentity , por exemplo, assim:

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

Ou obtenha o stub familiar (shim.ChaincodeStubInterface) para executar todas as ações usuais para interagir com o razão:

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

Mas! No código do nosso repositório de demonstração, você pode ver um contexto completamente diferente nos métodos:

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
}

Este é um contexto personalizado. É criado de maneira muito simples. Preste atenção ao context.go do nosso repositório:

1. Declaramos uma interface compatível com contractapi.TransactionContextInterface

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

2. A estrutura na qual incorporamos contractapi.TransactionContext

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

3. Implementamos os métodos declarados

// 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
}

Agora, ao inicializar o contrato, simplesmente passamos essa estrutura como manipulador:

simpleContract := new(SimpleContract)

simpleContract.TransactionContextHandler = new(CustomTransactionContext)

E todos os métodos de nosso contrato agora, em vez de ctx contractapi.TransactionContextInterface aceitam ctx CustomTransactionContextInterface .

Um contexto personalizado é necessário para enviar um estado através de ganchos transacionais . Ganchos transacionais são um nome bonito para middleware que é acionado antes ou depois de chamar o método de contrato.

Um exemplo de gancho que, antes de chamar um método, extrai do livro o valor da chave passada pelo primeiro parâmetro na transação:

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

Agora podemos obter o valor da chave solicitada nos métodos um pouco mais concisos:

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
}

O gancho após a chamada do método é quase idêntico, exceto que, além do contexto, ele aceita uma interface vazia (por que precisamos dela, descobriremos mais tarde):

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

Este gancho exibe o ID da transação e o valor que o método retornou antes do gancho . Para verificar este posthook, você pode ir para o contêiner da CLI e chamar o método de contrato: Alterne para o terminal em que o código está sendo executado, a saída será algo como isto:

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




e503e98e4c71285722f244a481fbcbf0ff4120adcd2f9067089104e5c3ed0efe # txid
beforeValue Olá, lá # valor do método anterior

E se quisermos processar solicitações com um nome de função inexistente? Para isso, qualquer contrato tem um campo 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

Isso também pode ser verificado através da CLI: Conclusão:

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




Erro: falha de endosso durante a consulta. response: status: 500 mensagem: "Função inválida BadRequest passada com args [BadKey]"

Para que o código inicie no mesmo nível, devemos chamar o método Start () como antes, antes de transferir todos os nossos contratos para o código :

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

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

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

Total


O novo modelo do código de código resolveu o problema de roteamento, middleware, serialização de valores de retorno, desserialização de argumentos de string (qualquer tipo, exceto a interface {} pode ser usado ). Agora resta aguardar a implementação do novo modelo para o Go SDK.

Obrigado pela atenção.

Parte dois

All Articles