Model perangkat lunak baru untuk kode rantai Kain Hyperledger



Belum lama berselang, rilis pertama fabric-contract-api-go dirilis - implementasi model perangkat lunak baru untuk chaincode RFC 0001 . Mari kita lihat apa itu dan bagaimana menggunakannya.

Di sini saya menyiapkan repositori dengan jaringan Fabric sederhana, di mana rekan-rekan berjalan dalam mode dev. Ikuti instruksi dari repositori untuk memulai jaringan dan kembali (tidak akan lebih dari 5 menit).

Sekarang setelah jaringan berjalan dan chaincode diinstal, mari kita lihat bagian dalam chaincode yang bekerja pada model baru.

Di SimpleContract.go, kami mengimpor modul dengan API baru:

github.com/hyperledger/fabric-contract-api-go/contractapi

Selanjutnya, kami menjelaskan kontrak kami menggunakan struktur SimpleContract di mana struktur Kontrak disematkan :

type SimpleContract struct {
	contractapi.Contract
}


Sangat penting untuk membangun dalam Kontrak sehingga kontrak kami memenuhi antarmuka ContractInterface . Di sini Anda harus membuat reservasi dan mengatakan bahwa kontrak! = Chaincode. Chaincode adalah wadah dengan jumlah kontrak yang tidak terbatas. Chaincode menyimpan kontraknya di peta, seperti yang terlihat dalam daftar ini:


type ContractChaincode struct {
	DefaultContract       string
	contracts             map[string]contractChaincodeContract
	metadata              metadata.ContractChaincodeMetadata
	Info                  metadata.InfoMetadata
	TransactionSerializer serializer.TransactionSerializer
}

Peta kontrak yang digunakan secara internal oleh Invoke permintaan dengan rute:

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

Jadi kembali ke SimpleContract. Semua metode harus memiliki parameter ctx yang memenuhi antarmuka TransactionContextInterface . Secara default, semua metode mendapatkan TransactionContext standar , yang dalam kebanyakan kasus sudah cukup.

Konteks ini memungkinkan kami untuk bekerja dengan ClientIdentity , misalnya, seperti ini:

func (sc *SimpleContract) Whois(ctx contractapi.TransactionContextInterface) (string, error) {
	return ctx.GetClientIdentity().GetID()
}

Atau dapatkan rintisan yang sudah dikenal (shim.ChaincodeStubInterface) untuk melakukan semua tindakan biasa untuk berinteraksi dengan buku besar:

func (sc *SimpleContract) Write(ctx contractapi.TransactionContextInterface, key string, value []byte) error {
	return ctx.GetStub().PutState(key, value)
}

Tapi! Dalam kode repositori demo kami, Anda dapat melihat konteks yang sangat berbeda dalam metode:

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
}

Ini adalah konteks khusus. Itu dibuat sangat sederhana. Perhatikan context.go dari repositori kami:

1. Kami mendeklarasikan antarmuka yang kompatibel dengan contractapi.TransactionContextInterface

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

2. Struktur tempat kami menanamkan contractapi.TransactionContext

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

3. Kami menerapkan metode yang dinyatakan

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

Sekarang, ketika menginisialisasi kontrak, kami hanya meneruskan struktur ini sebagai penangan:

simpleContract := new(SimpleContract)

simpleContract.TransactionContextHandler = new(CustomTransactionContext)

Dan semua metode kontrak kami sekarang bukan ctx contractapi.TransactionContextInterface menerima ctx CustomTransactionContextInterface .

Diperlukan konteks khusus untuk mendorong keadaan melalui kait transaksional . Kait transaksional adalah nama yang indah untuk middleware yang menyala sebelum atau setelah memanggil metode kontrak.

Contoh hook yang, sebelum memanggil metode, mengekstrak dari buku besar nilai kunci yang dilewatkan oleh parameter pertama dalam transaksi:

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

Sekarang kita bisa mendapatkan nilai kunci yang diminta dalam metode yang sedikit lebih ringkas:

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
}

Hook setelah pemanggilan metode hampir identik, kecuali bahwa selain konteks, ia menerima antarmuka kosong (mengapa kita membutuhkannya, kita akan mengetahuinya nanti):

YetAnotherContract.go
func After(ctx contractapi.TransactionContextInterface, beforeValue interface{}) error {
	fmt.Println(ctx.GetStub().GetTxID())
	fmt.Println("beforeValue", beforeValue)
	return nil
}

Hook ini menampilkan id transaksi dan nilai yang dikembalikan metode sebelum hook . Untuk memeriksa posthook ini, Anda dapat masuk ke wadah CLI dan memanggil metode kontrak: Beralih ke terminal tempat kode berjalan, output akan terlihat seperti ini:

docker exec -it cli sh
peer chaincode query -n mycc -c '{"Args":["YetAnotherContract:SayHi"]}' -C myc




e503e98e4c71285722f244a481fbcbf0ff4120adcd2f9067089104e5c3ed0efe # txid
beforeValue Hi there # value dari metode sebelumnya

Bagaimana jika kita ingin memproses permintaan dengan nama fungsi yang tidak ada? Untuk ini, kontrak apapun memiliki UnknownTransaction bidang :

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

Ini juga dapat diverifikasi melalui CLI: Kesimpulan:

docker exec -it cli sh
peer chaincode query -n mycc -c '{"Args":["BadRequest", "BadKey"]}' -C myc




Kesalahan: kegagalan dukungan selama permintaan. response: status: 500 message: "Fungsi tidak valid, BadRequest diteruskan dengan args [BadKey]"

Agar kode dapat mulai di peer, kita harus memanggil metode Start () seperti sebelumnya, sebelum mentransfer semua kontrak kami ke kode :

main.go
cc, err := contractapi.NewChaincode(simpleContract, yetAnotherContract)

	if err != nil {
		panic(err.Error())
	}

	if err := cc.Start(); err != nil {
		panic(err.Error())
	}

Total


Model baru dari codecode memecahkan masalah perutean, middleware, serialisasi nilai kembali, deserialisasi argumen string (semua jenis kecuali antarmuka {} dapat digunakan ). Sekarang tinggal menunggu implementasi model baru untuk Go SDK.

Terimakasih atas perhatiannya.

Bagian kedua

All Articles