Nuevo modelo de software para el chaincode Hyperledger Fabric



No hace mucho tiempo, se lanzó el primer lanzamiento de fabric-contract-api-go : la implementación del nuevo modelo de software para el chaincode RFC 0001 . Veamos qué es y cómo usarlo.

Aquí preparé un repositorio con una red simple de Fabric, donde los compañeros se ejecutan en modo dev. Siga las instrucciones del repositorio para iniciar la red y regresar (no tomará más de 5 minutos).

Ahora que tiene la red funcionando y el chaincode instalado, echemos un vistazo al interior del chaincode que funciona en el nuevo modelo.

En SimpleContract.go, importamos el módulo con la nueva API: a

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

continuación, describimos nuestro contrato utilizando la estructura SimpleContract en la que está incrustada la estructura del contrato :

type SimpleContract struct {
	contractapi.Contract
}


Es imperativo incorporar un contrato para que nuestro contrato satisfaga la interfaz ContractInterface . Aquí debe hacer una reserva y decir que el contrato! = Chaincode. Un chaincode es un contenedor de un número indefinido de contratos. Chaincode almacena sus contratos en el mapa, como se ve en este listado:


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

Invoke utiliza internamente los contratos de mapas para enrutar las solicitudes:

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

Así que volvamos a SimpleContract. Todos los métodos deben tener un parámetro ctx que satisfaga la interfaz TransactionContextInterface . Por defecto, todos los métodos obtienen un TransactionContext estándar , que en la mayoría de los casos es suficiente.

Este contexto nos permite trabajar con ClientIdentity , por ejemplo, así:

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

O obtenga el stub familiar (shim.ChaincodeStubInterface) para realizar todas las acciones habituales para interactuar con el libro mayor:

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

¡Pero! En el código de nuestro repositorio de demostración, puede ver un contexto completamente diferente en los 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 es un contexto personalizado. Se crea de manera muy simple. Preste atención a context.go desde nuestro repositorio:

1. Declaramos una interfaz compatible con contractapi.TransactionContextInterface

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

2. La estructura en la que incrustamos contractapi.TransactionContext

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

3. Implementamos los 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
}

Ahora, al inicializar el contrato, simplemente pasamos esta estructura como un controlador:

simpleContract := new(SimpleContract)

simpleContract.TransactionContextHandler = new(CustomTransactionContext)

Y todos los métodos de nuestro contrato ahora en lugar de ctx contractapi.TransactionContextInterface aceptan ctx CustomTransactionContextInterface .

Se necesita un contexto personalizado para impulsar un estado a través de enlaces transaccionales . Los ganchos transaccionales son un hermoso nombre para el middleware que se activa antes o después de llamar al método de contrato.

Un ejemplo de un enlace que, antes de llamar a un método, extrae del libro mayor el valor de la clave pasada por el primer parámetro en la transacción:

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

Ahora podemos obtener el valor de la clave solicitada en los métodos un poco más conciso:

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
}

El enlace después de la llamada al método es casi idéntico, excepto que además del contexto, acepta una interfaz vacía (por qué la necesitamos, la resolveremos más adelante):

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

Este enlace muestra la identificación de la transacción y el valor que el método devolvió antes del enlace . Para verificar esta opción, puede ingresar al contenedor CLI y llamar al método de contrato: Cambie a la terminal donde se está ejecutando el código, la salida se verá así:

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




e503e98e4c71285722f244a481fbcbf0ff4120adcd2f9067089104e5c3ed0efe # txid
beforeValue Hi there # value del método anterior

¿Qué sucede si queremos procesar solicitudes con un nombre de función inexistente? Por esto, cualquier contrato tiene una UnknownTransaction campo :

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

Esto también se puede verificar a través de la CLI: Conclusión:

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




Error: error de aprobación durante la consulta. respuesta: estado: mensaje 500: "La función no válida BadRequest pasó con args [BadKey]"

Para que el chaincode se inicie en el par, debemos llamar al método Start () como antes, antes de transferir todos nuestros contratos al chaincode :

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

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

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

Total


El nuevo modelo del código de código resolvió el problema de enrutamiento, middleware, serialización de valores de retorno, deserialización de argumentos de cadena (se puede usar cualquier tipo excepto la interfaz {} ). Ahora queda esperar la implementación del nuevo modelo para Go SDK.

Gracias por la atención.

La segunda parte

All Articles