Vor nicht allzu langer Zeit wurde die erste Version von Fabric-Contract-API-Go veröffentlicht - die Implementierung des neuen Softwaremodells für den RFC 0001- Kettencode . Mal sehen, was es ist und wie man es benutzt.Hier habe ich ein Repository mit einem einfachen Fabric-Netzwerk vorbereitet, in dem Peers im Dev-Modus ausgeführt werden. Befolgen Sie die Anweisungen aus dem Repository, um das Netzwerk zu starten und zurückzukehren (es dauert nicht länger als 5 Minuten).Nachdem das Netzwerk ausgeführt und der Kettencode installiert wurde, sehen wir uns die Innenseiten des Kettencodes an, der im neuen Modell funktioniert.In SimpleContract.go importieren wir das Modul mit der neuen API:github.com/hyperledger/fabric-contract-api-go/contractapi
Als Nächstes beschreiben wir unseren Vertrag mithilfe der SimpleContract- Struktur, in die die Vertragsstruktur eingebettet ist :type SimpleContract struct {
contractapi.Contract
}
Es ist unbedingt erforderlich, einen Vertrag einzubauen, damit unser Vertrag die ContractInterface- Schnittstelle erfüllt . Hier sollten Sie eine Reservierung vornehmen und sagen, dass der Vertrag! = Chaincode. Ein Kettencode ist ein Container mit einer unbestimmten Anzahl von Verträgen. Chaincode speichert seine Verträge in der Karte, wie in dieser Liste gezeigt:
type ContractChaincode struct {
DefaultContract string
contracts map[string]contractChaincodeContract
metadata metadata.ContractChaincodeMetadata
Info metadata.InfoMetadata
TransactionSerializer serializer.TransactionSerializer
}
Karte Verträge werden intern durch verwendet Invoke , um Anfragen:func (cc *ContractChaincode) Invoke(stub shim.ChaincodeStubInterface) peer.Response {
nsFcn, params := stub.GetFunctionAndParameters()
li := strings.LastIndex(nsFcn, ":")
var ns string
var fn string
if li == -1 {
ns = cc.DefaultContract
fn = nsFcn
} else {
ns = nsFcn[:li]
fn = nsFcn[li+1:]
}
...
nsContract := cc.contracts[ns]
...
successReturn, successIFace, errorReturn = nsContract.functions[fn].Call(ctx, transactionSchema, &cc.metadata.Components, serializer, params...)
...
return shim.Success([]byte(successReturn))
}
Also zurück zu SimpleContract. Alle Methoden müssen einen ctx- Parameter haben , der die TransactionContextInterface- Schnittstelle erfüllt . Standardmäßig erhalten alle Methoden einen Standard- TransactionContext , was in den meisten Fällen ausreicht.In diesem Kontext können wir beispielsweise wie folgt mit ClientIdentity arbeiten :func (sc *SimpleContract) Whois(ctx contractapi.TransactionContextInterface) (string, error) {
return ctx.GetClientIdentity().GetID()
}
Oder lassen Sie den vertrauten Stub (shim.ChaincodeStubInterface) alle üblichen Aktionen für die Interaktion mit dem Hauptbuch ausführen:func (sc *SimpleContract) Write(ctx contractapi.TransactionContextInterface, key string, value []byte) error {
return ctx.GetStub().PutState(key, value)
}
Aber! Im Code unseres Demo-Repositorys sehen Sie in den Methoden einen völlig anderen Kontext:func (sc *SimpleContract) Create(ctx CustomTransactionContextInterface, key string, value string) error {
existing := ctx.GetData()
if existing != nil {
return fmt.Errorf("Cannot create world state pair with key %s. Already exists", key)
}
err := ctx.GetStub().PutState(key, []byte(value))
if err != nil {
return errors.New("Unable to interact with world state")
}
return nil
}
Dies ist ein benutzerdefinierter Kontext. Es ist sehr einfach erstellt. Achten Sie auf context.go aus unserem Repository:1. Wir erklären eine Schnittstelle kompatibel mit contractapi.TransactionContextInterfacetype CustomTransactionContextInterface interface {
contractapi.TransactionContextInterface
GetData() []byte
SetData([]byte)
}
2. Die Struktur, in die wir contractapi.TransactionContext einbettentype CustomTransactionContext struct {
contractapi.TransactionContext
data []byte
}
3. Wir implementieren die deklarierten Methoden
func (ctc *CustomTransactionContext) GetData() []byte {
return ctc.data
}
func (ctc *CustomTransactionContext) SetData(data []byte) {
ctc.data = data
}
Bei der Initialisierung des Vertrags übergeben wir diese Struktur einfach als Handler:simpleContract := new(SimpleContract)
simpleContract.TransactionContextHandler = new(CustomTransactionContext)
Und alle Methoden unseres Vertrags akzeptieren jetzt anstelle von ctx contractapi.TransactionContextInterface ctx CustomTransactionContextInterface .Ein benutzerdefinierter Kontext ist erforderlich, um einen Status durch Transaktions-Hooks zu verschieben . Transaktions-Hooks sind ein schöner Name für Middleware, die vor oder nach dem Aufruf der Vertragsmethode ausgelöst wird.Ein Beispiel für einen Hook, der vor dem Aufrufen einer Methode den Wert des Schlüssels aus dem Hauptbuch extrahiert, der vom ersten Parameter in der Transaktion übergeben wurde:SimpleContract.gofunc GetWorldState(ctx CustomTransactionContextInterface) error {
_, params := ctx.GetStub().GetFunctionAndParameters()
if len(params) < 1 {
return errors.New("Missing key for world state")
}
existing, err := ctx.GetStub().GetState(params[0])
if err != nil {
return errors.New("Unable to interact with world state")
}
ctx.SetData(existing)
return nil
}
main.gosimpleContract.BeforeTransaction = GetWorldState
Jetzt können wir den Wert des angeforderten Schlüssels in den Methoden etwas präziser abrufen :SimpleContract.gofunc (sc *SimpleContract) Read(ctx CustomTransactionContextInterface, key string) (string, error) {
existing := ctx.GetData()
if existing == nil {
return "", fmt.Errorf("Cannot read world state pair with key %s. Does not exist", key)
}
return string(existing), nil
}
Der Hook nach dem Methodenaufruf ist fast identisch, außer dass er zusätzlich zum Kontext eine leere Schnittstelle akzeptiert (warum wird er benötigt, wir werden es später herausfinden):YetAnotherContract.gofunc After(ctx contractapi.TransactionContextInterface, beforeValue interface{}) error {
fmt.Println(ctx.GetStub().GetTxID())
fmt.Println("beforeValue", beforeValue)
return nil
}
Dieser Hook zeigt die Transaktions-ID und den Wert an, den die Methode vor dem Hook zurückgegeben hat . Um diesen Posthook zu überprüfen, können Sie in den CLI-Container die Vertragsmethode aufrufen: Wechseln Sie zu dem Terminal, auf dem der Code ausgeführt wird. Die Ausgabe sieht ungefähr so aus:docker exec -it cli sh
peer chaincode query -n mycc -c '{"Args":["YetAnotherContract:SayHi"]}' -C myc
e503e98e4c71285722f244a481fbcbf0ff4120adcd2f9067089104e5c3ed0efe # txid
beforeValue Hallo, # Wert von der vorherigen Methode
Was ist, wenn wir Anforderungen mit einem nicht vorhandenen Funktionsnamen verarbeiten möchten? Dazu muss jeder Vertrag ein UnknownTransaction Feld :unknown_handler.gofunc UnknownTransactionHandler(ctx CustomTransactionContextInterface) error {
fcn, args := ctx.GetStub().GetFunctionAndParameters()
return fmt.Errorf("Invalid function %s passed with args %v", fcn, args)
}
main.gosimpleContract.UnknownTransaction = UnknownTransactionHandler
Dies kann auch über die CLI überprüft werden: Schlussfolgerung:docker exec -it cli sh
peer chaincode query -n mycc -c '{"Args":["BadRequest", "BadKey"]}' -C myc
Fehler: Endorsement-Fehler während der Abfrage. Antwort: Status: 500 Nachricht: "Ungültige Funktion BadRequest mit Argumenten übergeben [BadKey]"
Damit der Chaincode auf dem Peer gestartet werden kann, müssen wir die Start () -Methode wie zuvor aufrufen , bevor wir alle unsere Verträge auf den Chaincode übertragen :main.gocc, err := contractapi.NewChaincode(simpleContract, yetAnotherContract)
if err != nil {
panic(err.Error())
}
if err := cc.Start(); err != nil {
panic(err.Error())
}
Gesamt
Das neue Modell des Codecodes löste das Problem von Routing, Middleware, Serialisierung von Rückgabewerten und Deserialisierung von Zeichenfolgenargumenten (alle Typen außer Schnittstelle {} können verwendet werden ). Jetzt muss noch auf die Implementierung des neuen Modells für das Go SDK gewartet werden.Vielen Dank für Ihre Aufmerksamkeit.Zweiter Teil