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 {
- 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 {
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)
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{
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).
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
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:
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
Para gravar um carro, a função de geração de chaves será a seguinte:
const CarEntity = `CAR`
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`)
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" )
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.
, , ( CarPayload)
State , ( )
-
- — , . 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.
Porque cars , .
BeforeSuite Car authority Init . , Cars Init Init , .
BeforeSuite(func() {
. , CarRegister , .
It("Allow authority to add information about car", func() {
:
It("Disallow authority to add duplicate information about car", func() { expectcc.ResponseError( cc.From(actors[`authority`]).Invoke(`carRegister`, Payloads[0]), state.ErrKeyAlreadyExists)
Conclusão
- HLF Go, Java, JavaScript, , , - (Solidity) / -. / .
HLF , , ( .). Hypeledger Fabric , .. .