Desenvolvimento e teste inteligentes de contratos do Hyperledger Fabric

O Hyperledger Fabric (HLF) é uma plataforma de código aberto que usa a tecnologia de contabilidade distribuída (DLT), projetada para desenvolver aplicativos que funcionam em um ambiente de rede comercial criado e controlado por um consórcio de organizações usando regras de acesso (com permissão).


A plataforma suporta contratos inteligentes, em termos de HLF - códigos de cadeia criados em linguagens gerais como Golang, JavaScript, Java, ao contrário de, por exemplo, o Ethereum, que usa uma funcionalidade limitada orientada a contratos e linguagem Solidity (LLL, Viper, etc.).



O desenvolvimento e teste de códigos de cadeia, devido à necessidade de implantar um número significativo de componentes da rede blockchain, pode ser um processo bastante demorado, com alto tempo gasto em testes de alterações. Este artigo discute uma abordagem para o rápido desenvolvimento e teste de contratos inteligentes da HLF Golang usando a biblioteca CCKit .


Aplicativo baseado em HLF


Do ponto de vista do desenvolvedor, o aplicativo blockchain consiste em duas partes principais:


  • On-chain - contratos inteligentes (programas) operando em um ambiente isolado da rede blockchain que determina as regras para criação e composição dos atributos de transação. Em um contrato inteligente, as principais ações são a leitura, atualização e exclusão de dados do estado da rede blockchain. Deve-se enfatizar que a exclusão de dados de um estado deixa informações de que esses dados estavam presentes.
  • Fora da cadeia é um aplicativo (por exemplo, uma API) que interage com o ambiente blockchain por meio do SDK. A interação é entendida como chamar funções de contrato inteligente e monitorar eventos de contrato inteligente - eventos externos podem causar alterações de dados no contrato inteligente, enquanto eventos no contrato inteligente podem desencadear ações em sistemas externos.

Os dados geralmente são lidos através do nó da rede blockchain "residencial". Para registrar dados, o aplicativo envia solicitações aos nós das organizações participantes da "política de aprovação" de um contrato inteligente específico.


Para desenvolver código fora da cadeia (API etc.), é usado um SDK especializado que encapsula a interação com os nós da blockchain, coletando respostas etc. Para HLF, existem implementações de SDK para Go ( 1 , 2 ), Node.Js e Java


Componentes de malha do Hyperledger


Canal


Um canal é uma sub-rede separada de nós que suporta uma cadeia de blocos isolada (ledger), bem como o estado atual (valor-chave) da cadeia de blocos ( estado mundial ) usado para operar contratos inteligentes. Um host pode ter acesso a um número arbitrário de canais.


Transação


Uma transação no Hyperledger Fabric é uma atualização atômica do estado de uma cadeia de blocos, resultado da execução do método chaincode. Uma transação consiste em uma solicitação para chamar um método chaincode com alguns argumentos (Proposta de Transação) assinados pelo nó de chamada e um conjunto de respostas (Resposta da Proposta de Transação) dos nós nos quais a transação foi "confirmada" (Endosso). As respostas contêm informações sobre os pares de valores-chave alterados do status da cadeia de blocos do conjunto de leitura e gravação e informações de serviço (assinaturas e certificados de nós que confirmam a transação). Porque cadeias de blocos de canais individuais são fisicamente separadas, uma transação pode ser realizada apenas no contexto de um canal.


As plataformas blockchain "clássicas", como Bitcoin e Ethereum , usam o ciclo de transações Ordering-Execution executado por todos os nós, o que limita a escalabilidade da rede blockchain.



O Hyperledger Fabric usa uma arquitetura de execução e distribuição de transações que possui 3 operações principais:


  • Execução ( execução ) - criação por um contrato inteligente em execução em um ou vários nós da rede, transações - alterações atômicas no estado de um registro distribuído ( endosso )


  • Pedido - pedido e agrupamento de transações em blocos pelo serviço especializado de pedidos , usando um algoritmo de consenso conectável.


  • Validar - verificação por nós da rede de transações provenientes do solicitante antes de colocar as informações deles em sua cópia do registro distribuído




Essa abordagem permite executar a fase de execução da transação antes que ela entre na rede blockchain, além de dimensionar horizontalmente a operação dos nós da rede.


Chaincode


Um código de código, que também pode ser chamado de contrato inteligente, é um programa escrito em Golang, JavaScript (HLF 1.1+) ou Java (HLF 1.3+), que define as regras para criar transações que alteram o estado de uma cadeia de blocos. O programa é executado simultaneamente em vários nós independentes de uma rede distribuída de nós blockchain, o que cria um ambiente neutro para a execução de contratos inteligentes, reconciliando os resultados da execução do programa em todos os nós necessários para a "confirmação" da transação.


O código deve implementar uma interface que consiste em 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 } 

  • O método Init é chamado na instanciação ou atualização do código de código. Este método executa a inicialização necessária do estado do código de código. É importante distinguir no código do método se a chamada é uma instanciação ou uma atualização, para que, por engano, você não inicialize (redefina) os dados que já receberam um estado diferente de zero durante a operação do código de código.
  • O método Invoke é chamado quando qualquer função do código de código é acessada. Este método funciona com o status de contratos inteligentes.

O chaincode é instalado nos pares da rede blockchain. No nível do sistema, cada instância do código corresponde a um docker-container separado conectado a um nó de rede específico, que executa o envio de chamadas para a execução do código.
Diferentemente dos contratos inteligentes do Ethereum, a lógica de encadeamento pode ser atualizada, mas isso exige que todos os nós que hospedam o código instalem uma versão atualizada.


Em resposta a uma chamada para a função chaincode do lado de fora via SDK, o chaincode cria uma alteração no estado da cadeia de blocos (conjunto de leitura e gravação ), além de eventos. Um código de chamada refere-se a um canal específico e pode alterar dados em apenas um canal. Ao mesmo tempo, se o host no qual o código está instalado também tiver acesso a outros canais, na lógica do código poderá ler dados desses canais.


Os códigos de cadeia especiais para gerenciar vários aspectos da operação de uma rede blockchain são chamados de códigos de cadeia do sistema.


Política de Endosso


Uma política de aprovação define regras de consenso no nível de transações geradas por um código de código específico. A política define as regras que determinam quais nós do canal devem criar uma transação. Para fazer isso, cada um dos nós especificados na política de aprovação deve iniciar o método de encadeamento (a etapa "Executar"), executar uma "simulação", após a qual os resultados assinados serão coletados e verificados pelo SDK que iniciou a transação (todos os resultados da simulação devem ser idênticos, assinaturas de todos os nós exigidos pela política). Em seguida, o SDK envia a transação ao ordenador , após o qual todos os nós que têm acesso ao canal receberão a transação através do ordenador e executarão a etapa "Validar". É importante enfatizar que nem todos os nós do canal devem participar da etapa "Executar".


A política de aprovação é determinada no momento da instanciação ou atualização do código. Na versão 1.3, tornou-se possível definir políticas não apenas no nível do código de código, mas também no nível de chaves de endosso individuais baseadas em estado . Exemplos de políticas de aprovação:


  • Nós A, B, C, D
  • A maioria dos nós do canal
  • Pelo menos 3 nós de A, B, C, D, E, F

Evento


Um evento é um conjunto de dados nomeado que permite publicar um "feed de atualização" do estado da cadeia de blockchain. O conjunto de atributos do evento define o chaincode.


Infraestrutura de rede


Anfitrião (Par)


Um host está conectado a um número arbitrário de canais para os quais possui direitos de acesso. O host mantém sua versão da cadeia de blocos e o estado da cadeia de blocos e também fornece um ambiente para a execução de códigos de cadeia. Se o host não fizer parte da política de aprovação, ele não precisará ser configurado com códigos de cadeia.


No nível do software host, o estado atual da cadeia de blocos (estado mundial) pode ser armazenado no LevelDB ou no CouchDB. A vantagem do CouchDB é o suporte a consultas avançadas usando a sintaxe do MongoDB.


Orderer


O serviço de gerenciamento de transações aceita transações assinadas como uma entrada e garante que as transações sejam distribuídas pelos nós da rede na ordem correta.


O ordenador não executa contratos inteligentes e não contém cadeias de blocos e estados de cadeia de blocos. No momento (1.3), existem duas implementações do ordenador - um solo de desenvolvimento e uma versão baseada no Kafka que fornece tolerância a falhas de falha. Espera-se uma implementação do ordenador que suporte a resistência ao comportamento incorreto de uma certa fração de participantes (tolerância a falhas bizantinas) no final de 2018.


Serviços de identidade


Em uma rede Hyperledger Fabric, todos os membros têm identidades conhecidas por outros membros (identidade). Para identificação, é usada a infraestrutura de chave pública (PKI), através da qual os certificados X.509 são criados para organizações, elementos de infraestrutura (nó, ordenador), aplicativos e usuários finais. Como resultado, o acesso à leitura e modificação de dados pode ser controlado pelas regras de acesso no nível da rede, em um único canal ou na lógica de um contrato inteligente. Na mesma rede blockchain, vários serviços de identificação de vários tipos podem funcionar simultaneamente.


Implementação do código de código


O código de código pode ser considerado como um objeto que possui métodos que implementam uma lógica de negócios específica. Diferentemente do OOP clássico, um código de chatice não pode ter campos de atributo. Para trabalhar com o estado, cujo armazenamento é fornecido pela plataforma blockchain HLF, é usada a camada ChaincodeStubInterface , que é transmitida quando os métodos Init e Invoke são chamados. Ele fornece a capacidade de receber argumentos de chamada de função e fazer alterações no estado da cadeia de blocos:


 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 } 

No contrato inteligente da Ethereum desenvolvido no Solidity, cada método tem uma função pública. No código de chamada Hyperledger Fabric, nos métodos Init e Invoke , usando a função ChaincodeStubInterface . GetArgs (), você pode obter os argumentos da chamada de função na forma de uma matriz de matrizes de bytes, enquanto o primeiro elemento da matriz ao chamar Invoke contém o nome da função chaincode. Porque Invoke de qualquer método chaincode passa pelo método Invoke; podemos dizer que esta é uma implementação do padrão do controlador frontal.


Por exemplo, se considerarmos a implementação da interface Ethereum padrão para o token ERC-20 , o contrato inteligente deve implementar os métodos:


  • totalSupply ()
  • balanceOf (proprietário _ do endereço)
  • transferência (endereço _to, uint256 _value)

No caso da implementação do HLF, o código da função Invoke deve poder lidar com casos em que o primeiro argumento para chamadas de Invocação contém o nome dos métodos esperados (por exemplo, "totalSupply" ou "balanceOf"). Um exemplo da implementação do padrão ERC-20 pode ser visto aqui .


Exemplos de Chaincode


Além da documentação do Hyperledger Fabric , existem mais alguns exemplos de códigos de cadeia:



A implementação dos códigos de cadeia nesses exemplos é bastante detalhada e contém muita lógica repetida para selecionar as funções de roteamento chamadas), verificando o 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 ... 

Essa organização do código leva a uma deterioração da legibilidade do código e a possíveis erros, como esse , quando você simplesmente se esquece de desorganizar os dados de entrada. As apresentações sobre os planos de desenvolvimento do HLF mencionam uma revisão da abordagem para o desenvolvimento de códigos de cadeia, em particular a introdução de anotações em códigos de cadeia Java, etc. A experiência no desenvolvimento de contratos inteligentes levou à conclusão de que o desenvolvimento e o teste de códigos de cadeia serão mais fáceis se você selecionar a funcionalidade básica em uma biblioteca separada.


CCKit - uma biblioteca para desenvolvimento e teste de códigos de cadeia


A biblioteca do CCKit resume a prática de desenvolvimento e teste de códigos de cadeia. Como parte do desenvolvimento de extensões de código de código , a biblioteca de extensões OpenZeppelin para contratos inteligentes Ethereum foi usada como exemplo. O CCKit usa as seguintes soluções arquiteturais:


Encaminhamento de chamadas para funções inteligentes de contrato


Roteamento refere-se ao algoritmo pelo qual o aplicativo responde a uma solicitação do cliente. Essa abordagem é usada, por exemplo, em quase todas as estruturas http. O roteador usa certas regras para vincular a solicitação e o manipulador de solicitação. Em relação a um chaincode, isso é para associar o nome da função chaincode à função manipulador.


Nos exemplos mais recentes de contratos inteligentes, por exemplo, no aplicativo Insurance , isso usa o mapeamento entre o nome da função chaincode e a função no código Golang do formulário:


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

O roteador CCKit usa uma abordagem semelhante ao roteador http, bem como a capacidade de usar o contexto de solicitação para a função chaincode e as funções de middleware


O contexto da chamada para a função do código


Semelhante ao contexto de solicitação http, que geralmente tem acesso aos parâmetros de solicitação http, o roteador CCKit usa o contexto da chamada para a função de contrato inteligente , que é uma abstração sobre shim.ChaincodeStubInterface . O contexto pode ser o único argumento para o manipulador da função encadeamento; por meio dele, o manipulador pode receber os argumentos da chamada de função, além de acessar funcionalidades auxiliares para trabalhar com o estado do contrato inteligente (State), criar respostas (Response) 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 O contexto é uma interface, em certos códigos de cadeia pode ser expandido.


Funções de Middleware


As funções de processamento intermediário (middleware) são chamadas antes da chamada do manipulador do método do código, têm acesso ao contexto da chamada para o método do código e à próxima função intermediária ou diretamente ao manipulador do método do próximo (próximo). O middleware pode ser usado para:


  • conversão de dados de entrada (no exemplo abaixo, p.String e p.Struct são middleware)
  • restrições no acesso à função (por exemplo, owner.Only )
  • conclusão do ciclo de processamento da solicitação
  • chamando a próxima função de processamento intermediário da pilha

Conversão da estrutura de dados


A interface chaincode pressupõe que uma matriz de matrizes de bytes seja fornecida à entrada, cujos elementos são um atributo da função chaincode. Para impedir o empacotamento manual de dados da matriz de bytes para o tipo de dados golang (int, string, estrutura, matriz) dos argumentos de chamada da função em cada manipulador da função de encadeamento, os tipos de dados esperados são definidos no momento da criação da regra de roteamento no roteador CCKit e o tipo é convertido automaticamente . No exemplo a seguir , a função carGet espera um argumento do tipo string e a função carRegister espera uma estrutura CarPayload . O argumento também é nomeado, o que permite que o manipulador obtenha seu valor do contexto pelo nome. Um exemplo de manipulador será fornecido abaixo. Protobuf também pode ser usado para descrever o esquema de dados de encadeamento.


 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) 

Além disso, a conversão automática (empacotamento) é usada ao gravar dados no estado de um contrato inteligente e ao criar eventos (o tipo golang é serializado em uma matriz de bytes)


Ferramentas para depuração e registro de códigos de cadeia


Para depurar o código, você pode usar a extensão de depuração , que implementa métodos de contrato inteligente que permitem inspecionar a presença de chaves no estado do contrato inteligente, além de ler / alterar / excluir diretamente o valor por chave.


Para efetuar logon no contexto de uma chamada para uma função chaincode, o método Log () pode ser usado, que retorna uma instância do logger usado no HLF.


Métodos de contrato inteligentes métodos de controle de acesso


Como parte da extensão do proprietário , são implementadas primitivas básicas para armazenar informações sobre o proprietário do código de cadeia instanciado e modificadores de acesso (middleware) para métodos de contrato inteligentes.


Ferramentas de Teste de Contrato Inteligente


A implantação da rede blockchain, a instalação e a inicialização de códigos de cadeia é uma configuração bastante complicada e um longo procedimento. O tempo para reinstalar / atualizar o código do contrato inteligente pode ser reduzido usando o modo DEV do contrato inteligente; no entanto, o processo de atualização do código ainda será lento.


O pacote shim contém uma implementação do MockStub , que envolve chamadas para o código, simulando sua operação no ambiente blockchain HLF. O uso do MockStub permite obter resultados de teste quase instantaneamente e reduzir o tempo de desenvolvimento. Se considerarmos o esquema geral de operação do código no HLF, o MockStub basicamente substitui o SDK, permitindo fazer chamadas para as funções do código e imita o ambiente de inicialização do código no host.



O MockStub da entrega HLF contém a implementação de quase todos os métodos da interface shim.ChaincodeStubInterface , no entanto, na versão atual (1.3), falta a implementação de alguns métodos importantes, como GetCreator. Porque O código da cadeia pode usar esse método para obter um certificado de um criador da transação para controle de acesso, para obter cobertura máxima em testes, é importante a capacidade de ter um esboço desse método.


A biblioteca do CCKit contém uma versão estendida do MockStub , que contém a implementação dos métodos ausentes, bem como métodos para trabalhar com canais de eventos, etc.


Exemplo de Chaincode


Por exemplo, vamos criar um código de código simples para armazenar informações sobre carros registrados


Modelo de dados


O estado do código de código é o armazenamento do valor-chave, no qual a chave é uma sequência, o valor é uma matriz de bytes. A prática básica é armazenar instâncias de estruturas de dados golang jonalizadas como valores. Portanto, para trabalhar com dados no código de código, após a leitura do estado, é necessário desmarcar a matriz de bytes.


Para gravar sobre o carro, usaremos o seguinte conjunto de atributos:


  • Identificador (número do carro)
  • Modelo de carro
  • Informações sobre o proprietário do veículo
  • Informações sobre horário de alteração de dados

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

Para transferir dados para o código de código, crie uma estrutura separada contendo apenas os campos provenientes de fora do código de código:


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

Trabalhar com chaves


Chaves de registro em um estado de contrato inteligente é uma sequência. Ele também suporta a capacidade de criar chaves compostas nas quais partes da chave são separadas por um byte zero ( U + 0000 )


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

No CCKit, as funções de trabalhar com o estado de um contrato inteligente podem criar automaticamente chaves para registros se as estruturas transferidas suportarem a interface Keyer


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

Para gravar um carro, a função de geração de chaves será a seguinte:


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

Declaração de função de contrato inteligente (roteiro)


No método construtor do chaincode, podemos definir as funções do chaincode e seus argumentos. Haverá 3 funções no código de registro do carro


  • carList, retorna uma matriz de estruturas Car
  • carGet, aceita um identificador de carro e retorna uma estrutura de carro
  • carRegister, aceita uma instância serializada da estrutura CarPayload e retorna o resultado do registro. O acesso a esse método é possível apenas para o proprietário do código de código, que é salvo usando o middleware do pacote do proprietário.

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

O exemplo acima usa a estrutura Chaincode na qual o processamento dos métodos Init e Invoke é delegado ao roteador:


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

O uso de um roteador e a estrutura básica do Chaincode permite a reutilização de funções do manipulador. Por exemplo, para implementar o código de código sem verificar o acesso à função carRegister , será suficiente criar um novo método construtor


Implementação das funções de um contrato inteligente


Funções Golang - manipuladores de funções de contrato inteligentes no roteador CCKit podem ser de três tipos:


  • StubHandlerFunc - a interface do manipulador padrão, aceita shim.ChaincodeStubInterface , retorna a resposta padrão peer.Response
  • ContextHandlerFunc - pega um contexto e retorna peer.Response
  • HandlerFunc - pega um contexto, retorna uma interface e um erro. Uma matriz de bytes pode ser retornada ou qualquer tipo de golang que é automaticamente convertido em uma matriz de bytes com base na qual peer.Response é criada. O status da resposta será shim.Ok ou shim.Error , dependendo do erro passado.

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

Conclusão


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


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

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


All Articles