Escrevendo Chaincode em Golang – a maneira OOP

Vishal Blocked Unblock Seguir Seguindo 7 de dezembro de 2018

Chaincode (ou contrato inteligente) é um programa que lida com a lógica de negócios acordada pelos membros da rede de malha e gerencia o estado do razão compartilhada por meio de transações enviadas pelo aplicativo cliente.

A implementação de APIs chaincode do Hyperledger Fabric está disponível em Node.js, Go lang e Java . Embora o suporte a Java tenha sido introduzido recentemente, as APIs Node.js e Go lang estão em um nível maduro. O Node.js pode ser uma opção preferível para milhões, já que a curva de aprendizado não é tão íngreme quanto foi com o Go lang. No entanto, devo advertir que o Node.js não é uma das melhores ferramentas para projetar aplicativos de back-end para o setor financeiro, especialmente onde os números estão envolvidos. Chaincode programação muitas vezes lida com matemática e JavaScript não é muito justo com a matemática . Tente estes se você estiver em dúvida:

 0,1 + 0,3! = 0,4 // verdadeiro 
 // O professor vencedor do Prêmio Abel de Matemática provou estar errado !! (57055 * 57055 * 57055) + (339590 * 339590 * 339590) == (340126 * 340126 * 340126) // verdadeiro, deve ser falso - O último teorema de Fermat 

Eu hesitaria em usar uma linguagem fracamente tipada para cálculos sérios. As suposições auto-suficientes do compilador podem causar sérios erros.
Além disso, o primeiro idioma de chaincode totalmente suportado é o Go. Todos os novos recursos (APIs) são introduzidos pela primeira vez no Go. Outros desenvolvedores de idiomas precisam esperar. Considerando essas duas e muitas outras razões, o go lang é meu idioma preferido para escrever chaincodes.
Agora, escrever o primeiro chaincode em Go pode ser complicado. Mas acredite em mim, é apenas a semântica. Vá é fácil !!!

Nós estaremos escrevendo um chaincode (um programa Go) com uma pequena abordagem orientada a objetos . Nosso chaincode administraria a propriedade de um ativo, digamos um carro. Agora, no paradigma OOP, eu começaria com blueprints (classes) para o carro e o proprietário.

 classe carro { 
String modelName;
Cor da corda;
Cadeia serialNo;
Fabricante de cordas;
Proprietário do proprietário; //composição
}
class Owner {
Nome da cadeia;
String nationalIdentity;
String gênero;
Endereço de cadeia;
}

Go não tem aulas. Em vez disso, tem tipos e um desses tipos é structs – uma versão leve de classes. Um tipo de estrutura é uma coleção de campos e propriedades. Comportamentos (métodos) podem ser adicionados para ir tipos (struct passa a ser um deles).

Antes de avançarmos, deixe-me mencionar que a semântica do Go segue uma regra simples, escreva como você fala.

 var modelName string // => isso deve ser interpretado como -> uma variável modelName do tipo string 

A versão Go ficaria assim:

 tipo estrutura do carro { 
string modelName
corda de cor
cadeia serialNo
string do fabricante
proprietário Proprietário
// composição
}
 tipo Owner struct { 
nome string
string nationalIdentity
cadeia de gênero
string de endereço
}

Vamos definir um novo método changeOwner () no carro.

 / ** Versão Java ** / 
 classe pública Car { 
String modelName;
Cor da corda;
Cadeia serialNo;
Fabricante de cordas;
Proprietário do proprietário;

void changeOwner (Proprietário newOwner) {
this.owner = newOwner;
}
}

A versão Go seria:

 tipo estrutura do carro { 
string modelName
corda de cor
cadeia serialNo
string do fabricante
proprietário do proprietário
}
 tipo Owner struct { 
...
}
 // anexado por referência ==> chamado como receptor de ponteiro 
func (c * Car) changeOwner (proprietário do newOwner) {
c.owner = newOwner
}
 / ** anexado por valor ==> chamado como receptor de valor 
func (c Car) changeOwner (newOwner Owner) {
c.owner = newOwner
}

* /

Receptor de ponteiro deve ser usado quando as alterações feitas ao receptor (c * Car ) dentro do método (changeOwner) devem estar visíveis para o chamador (Car struct).

************************************************** ********

Vamos dar uma pausa aqui para entender as APIs chaincode do fabric. Os mais importantes são APIs de correção .

Shim : um pedaço de madeira ou metal que é inserido entre dois objetos para que eles se encaixem melhor.

Aqui, um shim é uma pequena biblioteca que funciona como um intermediário e intercepta de forma transparente uma API, altera os parâmetros passados, manipula a própria operação ou redireciona a operação para outro local.

As APIs de correção de chaincode possuem duas interfaces :

  • Interface chaincode
  • Interface ChaincodeStubInterface

Interface Chaincode:

Fabric impõe que cada chaincode deve implementar a interface Chaincode. Essa interface possui dois métodos: – Init e Invoke. Invoke é chamado toda vez que uma transação é submetida ao chaincode.

 tipo chaincode interface { 
// O método init aceita o stub do tipo ChaincodeStubInterface como
// argumento e retorna objeto de tipo peer.Response
Init (stub ChaincodeStubInterface) peer.Response
 Invoque (stub ChaincodeStubInterface) peer.Response 
}

Agora, o Go não tem classes para implementar interfaces. Em vez disso, tem tipos. Vamos declarar um CarChaincode tipo (de struct) .

 tipo CarChaincode struct { 
}

Agora, conforme exigido pelo fabric, o tipo CarChaincode precisa implementar a interface Chaincode.

 tipo CarChaincode struct { 
}


// Init implementado pelo CarChaincode
func (t * CarChaincode) Init (stub shim.ChaincodeStubInterface) pb.Response {
 } 
 // Invoke implementado por CarChaincode 
func (t * CarChaincode) Invoque (stub shim.ChaincodeStubInterface) pb.Response {
 } 

Interface ChaincodeStubInterface:

Essa interface possui métodos (cerca de 36 ao escrever este artigo), que são usados para acessar e modificar o razão. Essa interface é implementada pela estrutura ChaincodeStub . Em nosso chaincode, usaremos a implementação padrão fornecida pela struct ChaincodeStub em vez de escrever nossa própria implementação.

Agora, vamos juntos discutirmos até agora:

 // Definir um plano para o carro 
tipo estrutura do carro {
string modelName
corda de cor
cadeia serialNo
string do fabricante
proprietário do proprietário
}
 // Definir um plano para o proprietário 
tipo Owner struct {
nome string
string nationalIdentity
cadeia de gênero
string de endereço
}
 // Definir um método para alterar a propriedade 
func (c * Car) changeOwner (proprietário do newOwner) {
c.owner = newOwner
}
 // Definir referência de CarChaincode de chaincode 
tipo CarChaincode struct {
}
 // Implementa a interface Chaincode como obrigada pelo fabric 
func (t * CarChaincode) Init (stub shim.ChaincodeStubInterface) pb.Response {
 } 
func (t * CarChaincode) Invoque (stub shim.ChaincodeStubInterface) pb.Response {
 } 

Agora, vamos realizar algumas transações no razão. A inicialização é chamada sempre que um chaincode é inicializado. Portanto, este é o bom lugar para declarar um ativo do tipo Car e dois proprietários do tipo Proprietário e adicioná-los no razão.

 func (t * CarChaincode) Init (stub shim.ChaincodeStubInterface) pb.Response { 
 // Declara os proprietários do Owner struct 
tom: = Proprietário {nome: "Tom H", nationaIdentity: "ABCD33457", sexo: "M", endereço: "1, Tumbbad"}
bob: = Proprietário {name: "Bob M", nationaIdentity: "QWER33457", gênero: "M", endereço: "2, Tumbbad"}
 // Declaree carfrom Car struct 
car: = Carro {modelName: "Land Rover", cor: "branco", serialNo: "334712531234", fabricante: "Tata Motors", proprietário: tom}
 // converte tom Owner para [] byte 
tomAsJSONBytes, _: = json.Marshal (tom)
// Adicionar Tom ao ledger
err: = stub.PutState (tom.nationaIdentity, tomAsJSONBytes)
if err! = nil {
return shim.Error ("Falha ao criar recurso" + tom.name)
}
 // Adicionar Bob ao ledger 
bobAsJSONBytes, _: = json.Marshal (bob)
err = stub.PutState (bob.nationaIdentity, bobAsJSONBytes)
if err! = nil {
return shim.Error ("Falha ao criar ativo" + bob.name)
}
 // Adicionar carro ao ledger 
carAsJSONBytes, _: = json.Marshal (carro)
err = stub.PutState (car.serialNo, carAsJSONBytes)
if err! = nil {
return shim.Error ("Falha ao criar recurso" + car.serialNo)
}
 return shim.Success ([] byte ("Ativos criados com sucesso.")) 
}

Invoke é chamado sempre que uma transação é enviada para o chaincode. Vamos colocar lógica de transferência de propriedade aqui.

 func (c * CarChaincode) Invoque (stub shim.ChaincodeStubInterface) pb.Response { 
  // Leia args da proposta de transação. 
// fc => método para invocar
fc, args: = stub.GetFunctionAndParameters ()
if fc == "TransferirPropriedade" {
return c.TransferOwnership (stub, args)
}
return shim.Error ("A função chamada não está definida no chaincode")
}
 func (c * CarChaincode) TransferOwnership (stub shim.ChaincodeStubInterface, args [] string) pb.Response { 
// args [0] => serial de carro não
// args [1] ==> nova identidade nacional do proprietário
// Leia o ativo de carro existente
carAsBytes, _: = stub.GetState (args [0])
se carAsBytes == nil {
return shim.Error ("recurso do carro não encontrado")
}
 // Construir o carro struct 
car: = carro {}
_ = json.Unmarshal (carAsBytes, & car)
 // Leia newOwnerDetails 
ownerAsBytes, _: = stub.GetState (args [1])
se ownerAsBytes == nil {
return shim.Error ("propriedade do dono não encontrada")
}
 // Construir o proprietário struct 
newOwner: = Proprietário {}
_ = json.Unmarshal (ownerAsBytes, & newOwner)
 // Atualizar o proprietário 
car.changeOwner (newOwner)
 carAsJSONBytes, _: = json.Marshal (carro) 
 // Atualize a propriedade do carro no 
err: = stub.PutState (car.serialNo, carAsJSONBytes)
if err! = nil {
return shim.Error ("Falha ao criar recurso" + car.serialNo)
}
return shim.Success ([] byte ("Asset modified."))
}

Agora, precisamos iniciar o processo de chaincode para que ele possa ouvir as solicitações de aprovação de entrada. Nós faremos isso no método main () .

 func main () { 
 logger.SetLevel (shim.LogInfo) 
 // Iniciar o processo de chaincode 
err: = shim.Start (new (CarChaincode))
if err! = nil {
logger.Error ("Erro ao iniciar o PhantomChaincode -", err.Error ()
}
}

Agora, juntando tudo junto com dependências:

 pacote principal 
 importação ( 
"codificação / json"
 "github.com/hyperledger/fabric/core/chaincode/shim" 
pb "github.com/hyperledger/fabric/protos/peer"
)
 tipo estrutura do carro { 
string modelName
corda de cor
cadeia serialNo
string do fabricante
proprietário do proprietário
}
tipo Owner struct {
nome string
string nationAIdentity
cadeia de gênero
string de endereço
}
 func (c * Car) changeOwner (proprietário do newOwner) { 
c.owner = newOwner
}
 tipo CarChaincode struct { 
}
 func (c * CarChaincode) Init (stub shim.ChaincodeStubInterface) pb.Response { 
 // Declarar proprietários 
tom: = Proprietário {nome: "Tom H", nationaIdentity: "ABCD33457", sexo: "M", endereço: "1, Tumbbad"}
bob: = Proprietário {name: "Bob M", nationaIdentity: "QWER33457", gênero: "M", endereço: "2, Tumbbad"}
 // Carro de decalque 
car: = Carro {modelName: "Land Rover", cor: "branco", serialNo: "334712531234", fabricante: "Tata Motors", proprietário: tom}
 // converte tom Owner para [] byte 
tomAsJSONBytes, _: = json.Marshal (tom)
// Adicionar Tom ao ledger
err: = stub.PutState (tom.nationaIdentity, tomAsJSONBytes)
if err! = nil {
return shim.Error ("Falha ao criar recurso" + tom.name)
}
 // Adicionar Bob ao ledger 
bobAsJSONBytes, _: = json.Marshal (bob)
err = stub.PutState (bob.nationaIdentity, bobAsJSONBytes)
if err! = nil {
return shim.Error ("Falha ao criar ativo" + bob.name)
}
 // Adicionar carro ao ledger 
carAsJSONBytes, _: = json.Marshal (carro)
err = stub.PutState (car.serialNo, carAsJSONBytes)
if err! = nil {
return shim.Error ("Falha ao criar recurso" + car.serialNo)
}
return shim.Success ([] byte ("Ativos criados com sucesso."))
}

func (c * CarChaincode) Invoque (stub shim.ChaincodeStubInterface) pb.Response {
 // Leia args da proposta de transação. 
// fc => método para invocar
fc, args: = stub.GetFunctionAndParameters ()
if fc == "TransferirPropriedade" {
return c.TransferOwnership (stub, args)
}
return shim.Error ("Função chamada não está definida no chaincode")
}
 func (c * CarChaincode) TransferOwnership (stub shim.ChaincodeStubInterface, args [] string) pb.Response { 
// args [0] => serial de carro não
// args [1] ==> nova identidade nacional do proprietário
// Leia o ativo do carro
carAsBytes, _: = stub.GetState (args [0])
se carAsBytes == nil {
return shim.Error ("recurso do carro não encontrado")
}
car: = carro {}
_ = json.Unmarshal (carAsBytes, & car)
 // Leia newOwnerDetails 
ownerAsBytes, _: = stub.GetState (args [1])
se ownerAsBytes == nil {
return shim.Error ("propriedade do dono não encontrada")
}
newOwner: = Proprietário {}
_ = json.Unmarshal (ownerAsBytes, & newOwner)
 car.changeOwner (newOwner) 
carAsJSONBytes, _: = json.Marshal (carro)
err: = stub.PutState (car.serialNo, carAsJSONBytes)
if err! = nil {
return shim.Error ("Falha ao criar recurso" + car.serialNo)
}
 return shim.Success ([] byte ("Asset modified.")) 
}
 func main () { 
 logger.SetLevel (shim.LogInfo) 
 // Iniciar o processo de chaincode 
err: = shim.Start (new (CarChaincode))
if err! = nil {
logger.Error ("Erro ao iniciar o PhantomChaincode -", err.Error ()
}
}

PS: Eu usei o identificador em branco (_) em muitos lugares para descartar o valor de erro para manter este artigo menos detalhado; esta é uma prática terrível. Sempre verifique as devoluções de erros; eles são fornecidos por um motivo.

Se você gostou deste artigo, ? mostre seu apoio.

Vishal

Texto original em inglês.