Neues Softwaremodell für den Hyperledger Fabric-Kettencode



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.TransactionContextInterface

type CustomTransactionContextInterface interface {
	contractapi.TransactionContextInterface
	GetData() []byte
	SetData([]byte)
}

2. Die Struktur, in die wir contractapi.TransactionContext einbetten

type CustomTransactionContext struct {
	contractapi.TransactionContext
	data []byte
}

3. Wir implementieren die deklarierten Methoden

// GetData return set data
func (ctc *CustomTransactionContext) GetData() []byte {
	return ctc.data
}

// SetData provide a value for 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.go
func 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.go
simpleContract.BeforeTransaction = GetWorldState

Jetzt können wir den Wert des angeforderten Schlüssels in den Methoden etwas präziser abrufen :

SimpleContract.go
func (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.go
func 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.go
func UnknownTransactionHandler(ctx CustomTransactionContextInterface) error {
	fcn, args := ctx.GetStub().GetFunctionAndParameters()
	return fmt.Errorf("Invalid function %s passed with args %v", fcn, args)
}

main.go
simpleContract.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.go
cc, 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

All Articles