Hyperledger Fabric Desarrollo y prueba de contratos inteligentes

Hyperledger Fabric (HLF) es una plataforma de código abierto que utiliza tecnología de libro mayor distribuido (DLT), diseñada para desarrollar aplicaciones que funcionan en un entorno de red empresarial creado y controlado por un consorcio de organizaciones que utilizan reglas de acceso (con permiso).


La plataforma admite contratos inteligentes, en términos HLF: códigos de cadena creados en lenguajes generales como Golang, JavaScript, Java, a diferencia de, por ejemplo, Ethereum, que utiliza un lenguaje de Solidez de funcionalidad limitada orientado a contratos. (LLL, Viper, etc.).



El desarrollo y la prueba de códigos de cadena, debido a la necesidad de implementar un número significativo de componentes de la red blockchain, puede ser un proceso bastante largo con un alto tiempo dedicado a probar los cambios. Este artículo analiza un enfoque para el rápido desarrollo y prueba de los contratos inteligentes de HLF Golang utilizando la biblioteca CCKit .


Aplicación basada en HLF


Desde el punto de vista del desarrollador, la aplicación blockchain consta de dos partes principales:


  • En cadena : contratos (programas) inteligentes que operan en un entorno aislado de la red blockchain que determina las reglas para la creación y la composición de los atributos de la transacción. En un contrato inteligente, las acciones principales son leer, actualizar y eliminar datos del estado de la red blockchain. Se debe enfatizar que eliminar datos de un estado deja información de que estos datos estaban presentes.
  • Off-chain es una aplicación (por ejemplo, una API) que interactúa con el entorno blockchain a través del SDK. La interacción se entiende como llamar a funciones de contratos inteligentes y monitorear eventos de contratos inteligentes: los eventos externos pueden causar cambios en los datos del contrato inteligente, mientras que los eventos en el contrato inteligente pueden desencadenar acciones en sistemas externos.

Por lo general, los datos se leen a través del nodo de red blockchain "hogar". Para registrar datos, la aplicación envía solicitudes a los nodos de organizaciones que participan en la "política de aprobación" de un contrato inteligente en particular.


Para desarrollar código fuera de la cadena (API, etc.), se utiliza un SDK especializado que encapsula la interacción con los nodos de la cadena de bloques, recolectando respuestas, etc. Para HLF, hay implementaciones de SDK para Go ( 1 , 2 ), Node.Js y Java


Componentes de tela Hyperledger


Canal


Un canal es una subred separada de nodos que admite una cadena de bloques aislada (libro mayor), así como el estado actual (clave-valor) de la cadena de bloques ( estado mundial ) utilizado para operar contratos inteligentes. Un host puede tener acceso a un número arbitrario de canales.


Transacción


Una transacción en Hyperledger Fabric es una actualización atómica del estado de una cadena de bloques, el resultado de la ejecución del método chaincode. Una transacción consiste en una solicitud para llamar a un método chaincode con algunos argumentos (Propuesta de transacción) firmados por el nodo llamante, y un conjunto de respuestas (Respuesta de propuesta de transacción) de los nodos en los que la transacción fue "confirmada" (Endoso). Las respuestas contienen información sobre los pares clave-valor cambiantes del estado de la cadena de bloques del conjunto de lectura-escritura y la información de servicio (firmas y certificados de nodos que confirman la transacción). Porque Las cadenas de bloques de canales individuales están físicamente separadas, una transacción solo se puede realizar en el contexto de un canal.


Las plataformas de cadena de bloques "clásicas", como Bitcoin y Ethereum , utilizan el ciclo de transacción de Ejecución de pedidos ejecutado por todos los nodos, lo que limita la escalabilidad de la red de cadena de bloques.



Hyperledger Fabric utiliza una arquitectura de ejecución y distribución de transacciones que tiene 3 operaciones principales:


  • Ejecución ( ejecución ): creación mediante un contrato inteligente que se ejecuta en uno o varios nodos de la red, transacciones: cambios atómicos en el estado de un registro distribuido ( aprobación )


  • Pedidos: pedido y agrupación de transacciones en bloques por parte del servicio de pedidos especializado mediante un algoritmo de consenso conectable.


  • Validar: verificación por parte de los nodos de la red de las transacciones que provienen del orden antes de colocar la información de ellos en su copia del registro distribuido




Este enfoque le permite llevar a cabo la fase de ejecución de la transacción antes de que ingrese a la red blockchain, así como escalar horizontalmente el funcionamiento de los nodos de la red.


Chaincode


Un chaincode, que también se puede llamar un contrato inteligente, es un programa escrito en Golang, JavaScript (HLF 1.1+) o Java (HLF 1.3+), que define las reglas para crear transacciones que cambian el estado de una cadena de bloques. El programa se ejecuta simultáneamente en varios nodos independientes de la red distribuida de nodos de blockchain, lo que crea un entorno neutral para la ejecución de contratos inteligentes al conciliar los resultados del programa en todos los nodos necesarios para la "confirmación" de la transacción.


El código debe implementar una interfaz que consta de métodos:


type Chaincode interface { // Init is called during Instantiate transaction Init(stub ChaincodeStubInterface) pb.Response // Invoke is called to update or query the ledger Invoke(stub ChaincodeStubInterface) pb.Response } 

  • El método Init se llama al crear instancias o actualizar el código de código. Este método realiza la inicialización necesaria del estado del código de código. Es importante distinguir en el código del método si la llamada es una instanciación o una actualización, de modo que, por error, no inicialice (restablezca) los datos que ya han recibido un estado distinto de cero durante la operación del código de código.
  • Se llama al método Invoke cuando se accede a cualquier función del código de código. Este método funciona con el estado de los contratos inteligentes.

El chaincode se instala en los pares de la red blockchain. En el nivel del sistema, cada instancia del código corresponde a un contenedor acoplado separado conectado a un nodo de red específico, que realiza llamadas de despacho a la ejecución del código.
A diferencia de los contratos inteligentes de Ethereum, la lógica de encadenamiento se puede actualizar, pero esto requiere que todos los nodos que alojan el código de código instalen una versión actualizada.


En respuesta a una llamada a la función chaincode desde el exterior a través del SDK, el chaincode crea un cambio en el estado de la cadena de bloques (conjunto de lectura y escritura ), así como eventos. Un chaincode se refiere a un canal específico y puede cambiar datos en un solo canal. Al mismo tiempo, si el host en el que está instalado el código también tiene acceso a otros canales, en la lógica del código puede estar leyendo datos de estos canales.


Los códigos de cadena especiales para administrar varios aspectos de la operación de una red blockchain se denominan códigos de cadena del sistema.


Política de respaldo


Una política de aprobación define reglas de consenso a nivel de las transacciones generadas por un chaincode específico. La política establece las reglas que determinan qué nodos de canal deben crear una transacción. Para hacer esto, cada uno de los nodos especificados en la política de aprobación debe comenzar el método de encadenamiento (el paso "Ejecutar"), realizar una "simulación", después de lo cual los resultados firmados serán recopilados y verificados por el SDK que inició la transacción (todos los resultados de la simulación deben ser idénticos, las firmas de todos los nodos requeridos por la política deben estar presentes). A continuación, el SDK envía la transacción al orden, después de lo cual todos los nodos que tienen acceso al canal recibirán la transacción a través del orden y realizarán el paso "Validar". Es importante enfatizar que no todos los nodos del canal deben participar en el paso "Ejecutar".


La política de aprobación se determina en el momento de la instancia o actualización del código. En la versión 1.3, fue posible establecer políticas no solo a nivel de chaincode, sino también a nivel de claves de respaldo individuales basadas en el estado . Ejemplos de políticas de aprobación:


  • Nodos A, B, C, D
  • La mayoría de los nodos del canal
  • Al menos 3 nodos de A, B, C, D, E, F

El evento


Un evento es un conjunto de datos con nombre que le permite publicar un "feed de actualización" del estado de la cadena de blockchain. El conjunto de atributos de evento define el chaincode.


Infraestructura de red


Anfitrión (par)


Un host está conectado a un número arbitrario de canales para los cuales tiene derechos de acceso. El host mantiene su versión de la cadena de bloques y el estado de la cadena de bloques, y también proporciona un entorno para ejecutar códigos de cadena. Si el host no forma parte de la política de aprobación, no tiene que configurarse con códigos de cadena.


En el nivel del software host, el estado actual de la cadena de bloques (estado mundial) se puede almacenar en LevelDB o en CouchDB. La ventaja de CouchDB es su soporte para consultas enriquecidas usando la sintaxis MongoDB.


Orden


El servicio de gestión de transacciones acepta transacciones firmadas como entrada y garantiza que las transacciones se distribuyan entre los nodos de la red en el orden correcto.


Orderer no ejecuta contratos inteligentes y no contiene cadenas de bloques ni estados de cadenas de bloques. En este momento (1.3) hay dos implementaciones de orderer : un solo de desarrollo y una versión basada en Kafka que proporciona tolerancia a fallas de fallas. Se espera una implementación del pedido que respalde la resistencia al comportamiento incorrecto de una cierta fracción de participantes (tolerancia de falla bizantina) a fines de 2018.


Servicios de identidad


En una red de Hyperledger Fabric, todos los miembros tienen identidades conocidas por otros miembros (identidad). Para la identificación, se utiliza la infraestructura de clave pública (PKI), a través de la cual se crean certificados X.509 para organizaciones, elementos de infraestructura (nodo, orden), aplicaciones y usuarios finales. Como resultado, el acceso para leer y modificar datos puede controlarse mediante reglas de acceso a nivel de red, en un solo canal o en la lógica de un contrato inteligente. En la misma red blockchain, varios servicios de identificación de varios tipos pueden funcionar simultáneamente.


Implementación del código de código


Chaincode puede considerarse como un objeto que tiene métodos que implementan una lógica comercial específica. A diferencia de la OOP clásica, un chaincode no puede tener campos de atributo. Para trabajar con el estado, cuyo almacenamiento es proporcionado por la plataforma HLF blockchain, se usa la capa ChaincodeStubInterface , que se pasa cuando se invocan los métodos Init e Invoke . Proporciona la capacidad de recibir argumentos de llamada a funciones y realizar cambios en el estado de la cadena de bloques:


 type ChaincodeStubInterface interface { // GetArgs returns the arguments intended for the chaincode Init and Invoke GetArgs() [][]byte // InvokeChaincode locally calls the specified chaincode InvokeChaincode(chaincodeName string, args [][]byte, channel string) pb.Response // GetState returns the value of the specified `key` from the ledger. GetState(key string) ([]byte, error) // PutState puts the specified `key` and `value` into the transaction's writeset as a data-write proposal. PutState(key string, value []byte) error // DelState records the specified `key` to be deleted in the writeset of the transaction proposal. DelState(key string) error // GetStateByRange returns a range iterator over a set of keys in the ledger. GetStateByRange(startKey, endKey string) (StateQueryIteratorInterface, error) // CreateCompositeKey combines the given `attributes` to form a composite key. CreateCompositeKey(objectType string, attributes []string) (string, error) // GetCreator returns `SignatureHeader.Creator` (eg an identity of the agent (or user) submitting the transaction. GetCreator() ([]byte, error) // and many more methods } 

En el contrato inteligente Ethereum desarrollado en Solidity, cada método tiene una función pública. En el chaincode de Hyperledger Fabric en los métodos Init e Invoke utilizando la función ChaincodeStubInterface . GetArgs (), puede obtener los argumentos de la llamada a la función en forma de una matriz de matrices de bytes, mientras que el primer elemento de la matriz al llamar a Invoke contiene el nombre de la función chaincode. Porque La invocación de cualquier método chaincode pasa por el método Invoke; podemos decir que esta es una implementación del patrón del controlador frontal.


Por ejemplo, si consideramos la implementación de la interfaz estándar Ethereum para el token ERC-20 , el contrato inteligente debe implementar los métodos:


  • totalSupply ()
  • balanceOf (dirección _propietario)
  • transferencia (dirección _to, uint256 _valor)

y otros. En el caso de la implementación HLF, el código de función Invoke debe ser capaz de manejar casos donde el primer argumento para invocar llamadas contiene el nombre de los métodos esperados (por ejemplo, "totalSupply" o "balanceOf"). Aquí se puede ver un ejemplo de la implementación del estándar ERC-20.


Ejemplos de Chaincode


Además de la documentación de Hyperledger Fabric , hay algunos ejemplos más de códigos de cadena:



La implementación de los códigos de cadena en estos ejemplos es bastante detallada y contiene mucha lógica repetitiva para seleccionar las funciones de enrutamiento llamadas), verificando el número de argumentos, json marshalling / unmarshalling:


 func (t *SimpleChaincode) Invoke(stub shim.ChaincodeStubInterface) pb.Response { function, args := stub.GetFunctionAndParameters() fmt.Println("invoke is running " + function) // Handle different functions if function == "initMarble" { //create a new marble return t.initMarble(stub, args) } else if function == "transferMarble" { //change owner of a specific marble return t.transferMarble(stub, args) } else if function == "readMarble" { //read a marble return t.readMarble(stub, args) } else ... 

Dicha organización del código conduce a un deterioro en la legibilidad del código y posibles errores, como este , cuando simplemente se olvidó de ordenar los datos de entrada. Las presentaciones sobre los planes de desarrollo HLF mencionan una revisión del enfoque para el desarrollo de códigos de cadena, en particular la introducción de anotaciones en los códigos de cadena de Java, etc., sin embargo, los planes se relacionan con la versión que se espera solo en 2019. La experiencia de desarrollar contratos inteligentes ha llevado a la conclusión de que desarrollar y probar códigos de cadena será más fácil si selecciona la funcionalidad básica en una biblioteca separada.


CCKit: una biblioteca para desarrollar y probar códigos de cadena


La biblioteca CCKit resume la práctica de desarrollar y probar códigos de cadena. Como parte del desarrollo de extensiones de chaincode , se utilizó como ejemplo la biblioteca de extensiones OpenZeppelin para contratos inteligentes de Ethereum. CCKit utiliza las siguientes soluciones arquitectónicas:


Enrutamiento de llamadas a funciones de contrato inteligente


El enrutamiento se refiere al algoritmo por el cual la aplicación responde a una solicitud del cliente. Este enfoque se utiliza, por ejemplo, en casi todos los marcos http. El enrutador utiliza ciertas reglas para vincular la solicitud y el controlador de solicitud. En relación con un chaincode, esto es asociar el nombre de la función chaincode con la función del controlador.


En los últimos ejemplos de contratos inteligentes, por ejemplo, en la aplicación de seguros , esto utiliza la asignación entre el nombre de la función chaincode y la función en el código Golang del formulario:


 var bcFunctions = map[string]func(shim.ChaincodeStubInterface, []string) pb.Response{ // Insurance Peer "contract_type_ls": listContractTypes, "contract_type_create": createContractType, ... "theft_claim_process": processTheftClaim, } 

El enrutador CCKit utiliza un enfoque similar al enrutador http, así como la capacidad de usar el contexto de solicitud para la función chaincode y las funciones de middleware


El contexto de la llamada a la función del código.


De manera similar al contexto de solicitud http, que generalmente tiene acceso a los parámetros de solicitud http, el enrutador CCKit usa el contexto de la llamada a la función de contrato inteligente , que es una abstracción sobre shim.ChaincodeStubInterface . El contexto puede ser el único argumento para el manejador de la función de encadenamiento; a través de él, el manejador puede recibir los argumentos de la llamada a la función, así como el acceso a funcionalidades auxiliares para trabajar con el estado del contrato inteligente (Estado), creando respuestas (Respuesta), etc.


 Context interface { Stub() shim.ChaincodeStubInterface Client() (cid.ClientIdentity, error) Response() Response Logger() *shim.ChaincodeLogger Path() string State() State Time() (time.Time, error) Args() InterfaceMap Arg(string) interface{} ArgString(string) string ArgBytes(string) []byte SetArg(string, interface{}) Get(string) interface{} Set(string, interface{}) SetEvent(string, interface{}) error } 

Porque El contexto es una interfaz, en ciertos códigos de cadena se puede expandir.


Funciones de middleware


Las funciones de procesamiento intermedio (middleware) se invocan antes de la llamada del manejador del método del código, tienen acceso al contexto de la llamada al método del código y a la siguiente función intermedia o directamente al manejador del método del siguiente (siguiente). El middleware se puede usar para:


  • convertir datos de entrada (en el ejemplo a continuación, p.String y p.Struct son middleware)
  • restricciones de acceso a funciones (p . ej., propietario )
  • finalización del ciclo de procesamiento de solicitudes
  • llamando a la siguiente función de procesamiento intermedio desde la pila

Conversión de estructura de datos


La interfaz chaincode supone que se proporciona una matriz de conjuntos de bytes a la entrada, cada uno de cuyos elementos es un atributo de la función chaincode. Para evitar el cálculo manual de datos de la matriz de bytes al tipo de datos golang (int, cadena, estructura, matriz) de los argumentos de llamada de función en cada controlador de la función de encadenamiento, los tipos de datos esperados se establecen al momento de crear la regla de enrutamiento en el enrutador CCKit y el tipo se convierte automáticamente . En el ejemplo que sigue , la función carGet espera un argumento de tipo cadena y la función carRegister espera una estructura CarPayload . El argumento también se denomina, lo que permite al controlador obtener su valor del contexto por su nombre. Un ejemplo de un controlador se dará a continuación. Protobuf también se puede usar para describir el esquema de datos de encadenamiento.


 r.Group(`car`). Query(`List`, cars). // chain code method name is carList Query(`Get`, car, p.String(`id`)). // chain code method name is carGet, method has 1 string argument "id" Invoke(`Register`, carRegister, p.Struct(`car`, &CarPayload{}), // 1 struct argument owner.Only) // allow access to method only for chaincode owner (authority) 

Además, la conversión automática (cálculo de referencias) se utiliza al escribir datos en el estado de un contrato inteligente y al crear eventos (el tipo de golang se serializa en una matriz de bytes)


Herramientas para depurar y registrar códigos de cadena


Para depurar el código, puede usar la extensión de depuración , que implementa métodos de contrato inteligente que le permitirán inspeccionar la presencia de claves en el estado del contrato inteligente, así como también leer / cambiar / eliminar directamente el valor por clave.


Para iniciar sesión en el contexto de una llamada a una función chaincode, se puede utilizar el método Log (), que devuelve una instancia del registrador utilizado en HLF.


Métodos de contrato inteligentes métodos de control de acceso


Como parte de la extensión del propietario , se implementan primitivas básicas para almacenar información sobre el propietario del código de cadena instanciado y modificadores de acceso (middleware) para métodos de contrato inteligente.


Herramientas de prueba de contrato inteligente


Implementar la red blockchain, instalar e inicializar códigos de cadena es una configuración bastante complicada y un procedimiento largo. El tiempo para reinstalar / actualizar el código del contrato inteligente se puede reducir utilizando el modo DEV del contrato inteligente, sin embargo, el proceso de actualización del código seguirá siendo lento.


El paquete shim contiene una implementación de MockStub , que envuelve las llamadas al código para el código, simulando su funcionamiento en el entorno de cadena de bloques HLF. El uso de MockStub le permite obtener resultados de pruebas casi instantáneamente y le permite reducir el tiempo de desarrollo. Si consideramos el esquema general de operación del código en HLF, MockStub esencialmente reemplaza el SDK, lo que le permite realizar llamadas a las funciones del código y simula el entorno para iniciar el código en el host.



El MockStub de la entrega HLF contiene la implementación de casi todos los métodos de la interfaz shim.ChaincodeStubInterface , sin embargo, en la versión actual (1.3), carece de la implementación de algunos métodos importantes, como GetCreator. Porque el código de cadena puede usar este método para obtener un certificado de un creador de transacciones para el control de acceso, para una cobertura máxima en las pruebas, la capacidad de tener un trozo de este método es importante.


La biblioteca CCKit contiene una versión extendida de MockStub , que contiene la implementación de los métodos faltantes, así como los métodos para trabajar con canales de eventos, etc.


Ejemplo de Chaincode


Por ejemplo, creemos un chaincode simple para almacenar información sobre automóviles registrados


Modelo de datos


El estado del código de código es el almacenamiento de valores clave, en el que la clave es una cadena, el valor es una matriz de bytes. La práctica básica es almacenar instancias de estructuras de datos de golang jonalizadas como valores. En consecuencia, para trabajar con datos en el chaincode, después de leer el estado, debe desarmar la matriz de bytes.


Para grabar sobre el automóvil, utilizaremos el siguiente conjunto de atributos:


  • Identificador (número de carro)
  • Modelo de coche
  • Información del propietario del vehículo
  • Información de tiempo de cambio de datos

 // Car struct for chaincode state type Car struct { Id string Title string Owner string UpdatedAt time.Time // set by chaincode method } 

Para transferir datos al chaincode, cree una estructura separada que contenga solo los campos que provienen del exterior del chaincode:


 // CarPayload chaincode method argument type CarPayload struct { Id string Title string Owner string } 

Trabajar con llaves


Las claves de grabación en un estado de contrato inteligente son una cadena. También admite la capacidad de crear claves compuestas en las que partes de la clave están separadas por un byte cero ( U + 0000 )


 func CreateCompositeKey(objectType string, attributes []string) (string, error) 

En CCKit, las funciones de trabajar con el estado de un contrato inteligente pueden crear automáticamente claves para registros si las estructuras transferidas admiten la interfaz Keyer


 // Keyer interface for entity containing logic of its key creation type Keyer interface { Key() ([]string, error) } 

Para grabar un automóvil, la función de generación de claves será la siguiente:


 const CarEntity = `CAR` // Key for car entry in chaincode state func (c Car) Key() ([]string, error) { return []string{CarEntity, c.Id}, nil } 

Declaración de función de contrato inteligente (enrutamiento)


En el método constructor del chaincode, podemos definir las funciones del chaincode y sus argumentos. Habrá 3 funciones en el código de registro del automóvil


  • carList, devuelve una serie de estructuras de automóviles
  • carGet, acepta un identificador de automóvil y devuelve una estructura de automóvil
  • carRegister, acepta una instancia serializada de la estructura CarPayload y devuelve el resultado del registro. El acceso a este método solo es posible para el propietario del chaincode, que se guarda con el middleware del paquete del propietario

 func New() *router.Chaincode { r := router.New(`cars`) // also initialized logger with "cars" prefix r.Init(invokeInit) r.Group(`car`). Query(`List`, queryCars). // chain code method name is carList Query(`Get`, queryCar, p.String(`id`)). // chain code method name is carGet, method has 1 string argument "id" Invoke(`Register`, invokeCarRegister, p.Struct(`car`, &CarPayload{}), // 1 struct argument owner.Only) // allow access to method only for chaincode owner (authority) return router.NewChaincode(r) } 

El ejemplo anterior usa la estructura Chaincode en la cual el procesamiento de los métodos Init e Invoke se delega al enrutador:


 package router import ( "github.com/hyperledger/fabric/core/chaincode/shim" "github.com/hyperledger/fabric/protos/peer" ) // Chaincode default chaincode implementation with router type Chaincode struct { router *Group } // NewChaincode new default chaincode implementation func NewChaincode(r *Group) *Chaincode { return &Chaincode{r} } //======== Base methods ==================================== // // Init initializes chain code - sets chaincode "owner" func (cc *Chaincode) Init(stub shim.ChaincodeStubInterface) peer.Response { // delegate handling to router return cc.router.HandleInit(stub) } // Invoke - entry point for chain code invocations func (cc *Chaincode) Invoke(stub shim.ChaincodeStubInterface) peer.Response { // delegate handling to router return cc.router.Handle(stub) } 

El uso de un enrutador y la estructura básica de Chaincode permite la reutilización de las funciones del controlador. Por ejemplo, para implementar chaincode sin verificar el acceso a la función carRegister , será suficiente crear un nuevo método de construcción


Implementación de funciones de contrato inteligente


Funciones de Golang: los controladores de funciones de contrato inteligente en el enrutador CCKit pueden ser de tres tipos:


  • StubHandlerFunc : la interfaz del controlador estándar, acepta shim.ChaincodeStubInterface , devuelve el par de respuesta estándar. Respuesta
  • ContextHandlerFunc : toma un contexto y devuelve un par. Respuesta
  • HandlerFunc : toma un contexto, devuelve una interfaz y un error. Se puede devolver una matriz de bytes o cualquier tipo de golang que se convierta automáticamente en una matriz de bytes en función de qué peer.Response se crea. El estado de la respuesta será shim.Ok o shim.Error , dependiendo del error pasado.

 // StubHandlerFunc acts as raw chaincode invoke method, accepts stub and returns peer.Response StubHandlerFunc func(shim.ChaincodeStubInterface) peer.Response // ContextHandlerFunc use stub context as input parameter ContextHandlerFunc func(Context) peer.Response // HandlerFunc returns result as interface and error, this is converted to peer.Response via response.Create HandlerFunc func(Context) (interface{}, error) 

, , ( CarPayload)
State , ( )


 // car get info chaincode method handler func car(c router.Context) (interface{}, error) { return c.State().Get( // get state entry Key(c.ArgString(`id`)), // by composite key using CarKeyPrefix and car.Id &Car{}) // and unmarshal from []byte to Car struct } // cars car list chaincode method handler func cars(c router.Context) (interface{}, error) { return c.State().List( CarKeyPrefix, // get list of state entries of type CarKeyPrefix &Car{}) // unmarshal from []byte and append to []Car slice } // carRegister car register chaincode method handler func carRegister(c router.Context) (interface{}, error) { // arg name defined in router method definition p := c.Arg(`car`).(CarPayload) t, _ := c.Time() // tx time car := &Car{ // data for chaincode state Id: p.Id, Title: p.Title, Owner: p.Owner, UpdatedAt: t, } return car, // peer.Response payload will be json serialized car data c.State().Insert( //put json serialized data to state Key(car.Id), // create composite key using CarKeyPrefix and car.Id car) } 

-


- — , . BDD – Behavior Driven Development, .


, , - Ethereum ganache-cli truffle . golang - Mockstub.



, . .


Ginkgo , Go, go test . gomega expect , , .


  import ( "testing" . "github.com/onsi/ginkgo" . "github.com/onsi/gomega" examplecert "github.com/s7techlab/cckit/examples/cert" "github.com/s7techlab/cckit/extensions/owner" "github.com/s7techlab/cckit/identity" "github.com/s7techlab/cckit/state" testcc "github.com/s7techlab/cckit/testing" expectcc "github.com/s7techlab/cckit/testing/expect" ) func TestCars(t *testing.T) { RegisterFailHandler(Fail) RunSpecs(t, "Cars Suite") } 

, CarPayload :


 var Payloads = []*Car{{ Id: `A777MP77`, Title: `VAZ`, Owner: `victor`, }, { Id: `O888OO77`, Title: `YOMOBIL`, Owner: `alexander`, }, { Id: `O222OO177`, Title: `Lambo`, Owner: `hodl`, }} 

MockStub Cars.


 //Create chaincode mock cc := testcc.NewMockStub(`cars`, New()) 

Porque cars , .


 // load actor certificates actors, err := identity.ActorsFromPemFile(`SOME_MSP`, map[string]string{ `authority`: `s7techlab.pem`, `someone`: `victor-nosov.pem`}, examplecert.Content) 

BeforeSuite Car authority Init . , Cars Init Init , .


 BeforeSuite(func() { // init chaincode expectcc.ResponseOk(cc.From(actors[`authority`]).Init()) // init chaincode from authority }) 

. , CarRegister , .


 It("Allow authority to add information about car", func() { //invoke chaincode method from authority actor expectcc.ResponseOk(cc.From(actors[`authority`]).Invoke(`carRegister`, Payloads[0])) }) It("Disallow non authority to add information about car", func() { //invoke chaincode method from non authority actor expectcc.ResponseError( cc.From(actors[`someone`]).Invoke(`carRegister`, Payloads[0]), owner.ErrOwnerOnly) // expect "only owner" error }) 

:


 It("Disallow authority to add duplicate information about car", func() { expectcc.ResponseError( cc.From(actors[`authority`]).Invoke(`carRegister`, Payloads[0]), state.ErrKeyAlreadyExists) //expect car id already exists }) 

Conclusión


- HLF Go, Java, JavaScript, , , - (Solidity) / -. / .


HLF , , ( .). Hypeledger Fabric , .. .

Source: https://habr.com/ru/post/es426705/


All Articles