From 2c43e03591033012e7c3ff689462b89abf5c13c0 Mon Sep 17 00:00:00 2001 From: Stanislav Jakuschevskij Date: Sat, 7 Dec 2024 15:20:07 +0100 Subject: [PATCH] Encapsulate block parser in a package Created parser, contract and utils packages and extracted each piece of functionality into its own files. Removed "Get" prefix from methods and changed return values from interfaces to structs. Signed-off-by: Stanislav Jakuschevskij --- off_chain_data/application-go/blockParser.go | 397 ------------------ off_chain_data/application-go/contract.go | 93 ---- .../application-go/contract/asset.go | 45 ++ .../application-go/contract/contract.go | 68 +++ off_chain_data/application-go/getAllAssets.go | 5 +- off_chain_data/application-go/go.mod | 6 +- off_chain_data/application-go/go.sum | 6 +- .../application-go/parser/blockParser.go | 87 ++++ .../{ => parser}/blockParser_test.go | 38 +- .../application-go/parser/identity.go | 16 + .../application-go/parser/payload.go | 82 ++++ .../application-go/parser/readWriteSet.go | 65 +++ .../application-go/parser/transaction.go | 169 ++++++++ off_chain_data/application-go/transact.go | 33 +- .../application-go/{ => utils}/utils.go | 14 +- 15 files changed, 581 insertions(+), 543 deletions(-) delete mode 100644 off_chain_data/application-go/blockParser.go delete mode 100644 off_chain_data/application-go/contract.go create mode 100644 off_chain_data/application-go/contract/asset.go create mode 100644 off_chain_data/application-go/contract/contract.go create mode 100644 off_chain_data/application-go/parser/blockParser.go rename off_chain_data/application-go/{ => parser}/blockParser_test.go (75%) create mode 100644 off_chain_data/application-go/parser/identity.go create mode 100644 off_chain_data/application-go/parser/payload.go create mode 100644 off_chain_data/application-go/parser/readWriteSet.go create mode 100644 off_chain_data/application-go/parser/transaction.go rename off_chain_data/application-go/{ => utils}/utils.go (59%) diff --git a/off_chain_data/application-go/blockParser.go b/off_chain_data/application-go/blockParser.go deleted file mode 100644 index 55f895fa..00000000 --- a/off_chain_data/application-go/blockParser.go +++ /dev/null @@ -1,397 +0,0 @@ -package main - -import ( - "fmt" - - "github.com/hyperledger/fabric-gateway/pkg/identity" - "github.com/hyperledger/fabric-protos-go-apiv2/common" - "github.com/hyperledger/fabric-protos-go-apiv2/ledger/rwset" - "github.com/hyperledger/fabric-protos-go-apiv2/ledger/rwset/kvrwset" - "github.com/hyperledger/fabric-protos-go-apiv2/msp" - "github.com/hyperledger/fabric-protos-go-apiv2/peer" - "google.golang.org/protobuf/proto" -) - -type Block interface { - GetNumber() uint64 - GetTransactions() []Transaction - ToProto() *common.Block -} - -type ParsedBlock struct { - block *common.Block - validationCodes []byte - transactions []Transaction -} - -func NewParsedBlock(block *common.Block) Block { - validationCodes := getTransactionValidationCodes(block) - - return &ParsedBlock{block, validationCodes, nil} -} - -func (pb *ParsedBlock) GetNumber() uint64 { - header := assertDefined(pb.block.GetHeader(), "missing block header") - return header.GetNumber() -} - -// TODO: needs cache, getPayloads, parsePayload -func (pb *ParsedBlock) GetTransactions() []Transaction { - return nil -} - -func (pb *ParsedBlock) ToProto() *common.Block { - return nil -} - -// Implements identity.Identity Interface -type IdentityImpl struct { - creator *msp.SerializedIdentity -} - -func (i *IdentityImpl) MspID() string { - return i.creator.GetMspid() -} - -func (i *IdentityImpl) Credentials() []byte { - return i.creator.GetIdBytes() -} - -type Transaction interface { - GetChannelHeader() *common.ChannelHeader - GetCreator() identity.Identity - GetValidationCode() int32 - IsValid() bool - GetNamespaceReadWriteSets() []NamespaceReadWriteSet - ToProto() *common.Payload -} - -type TransactionImpl struct { - payload Payload -} - -func NewTransaction(payload Payload) Transaction { - return &TransactionImpl{payload} -} - -func (t *TransactionImpl) GetChannelHeader() *common.ChannelHeader { - return t.payload.GetChannelHeader() -} - -func (t *TransactionImpl) GetCreator() identity.Identity { - creator := &msp.SerializedIdentity{} - if err := proto.Unmarshal(t.payload.GetSignatureHeader().GetCreator(), creator); err != nil { - panic(err) - } - - return &IdentityImpl{creator} -} - -func (t *TransactionImpl) GetNamespaceReadWriteSets() []NamespaceReadWriteSet { - result := []NamespaceReadWriteSet{} - for _, readWriteSet := range t.payload.GetEndorserTransaction().GetReadWriteSets() { - result = append(result, readWriteSet.GetNamespaceReadWriteSets()...) - } - - return result -} - -func (t *TransactionImpl) GetValidationCode() int32 { - return t.payload.GetTransactionValidationCode() -} - -func (t *TransactionImpl) IsValid() bool { - return t.payload.IsValid() -} - -func (t *TransactionImpl) ToProto() *common.Payload { - return t.payload.ToProto() -} - -type EndorserTransaction interface { - GetReadWriteSets() []ReadWriteSet - ToProto() *peer.Transaction -} - -type ParsedEndorserTransaction struct { - transaction *peer.Transaction -} - -func NewParsedEndorserTransaction(transaction *peer.Transaction) EndorserTransaction { - return &ParsedEndorserTransaction{transaction} -} - -// TODO add cache -func (p *ParsedEndorserTransaction) GetReadWriteSets() []ReadWriteSet { - chaincodeActionPayloads := p.getChaincodeActionPayloads() - - chaincodeEndorsedActions := p.getChaincodeEndorsedActions(chaincodeActionPayloads) - - proposalResponsePayloads := p.getProposalResponsePayloads(chaincodeEndorsedActions) - - chaincodeActions := p.getChaincodeActions(proposalResponsePayloads) - - txReadWriteSets := p.getTxReadWriteSets(chaincodeActions) - - parsedReadWriteSets := p.parseReadWriteSets(txReadWriteSets) - - return parsedReadWriteSets -} - -func (p *ParsedEndorserTransaction) getChaincodeActionPayloads() []*peer.ChaincodeActionPayload { - result := []*peer.ChaincodeActionPayload{} - for _, transactionAction := range p.transaction.GetActions() { - chaincodeActionPayload := &peer.ChaincodeActionPayload{} - if err := proto.Unmarshal(transactionAction.GetPayload(), chaincodeActionPayload); err != nil { - panic(err) - } - - result = append(result, chaincodeActionPayload) - } - return result -} - -func (*ParsedEndorserTransaction) getChaincodeEndorsedActions(chaincodeActionPayloads []*peer.ChaincodeActionPayload) []*peer.ChaincodeEndorsedAction { - result := []*peer.ChaincodeEndorsedAction{} - for _, payload := range chaincodeActionPayloads { - result = append( - result, - assertDefined( - payload.GetAction(), - "missing chaincode endorsed action", - ), - ) - } - return result -} - -func (*ParsedEndorserTransaction) getProposalResponsePayloads(chaincodeEndorsedActions []*peer.ChaincodeEndorsedAction) []*peer.ProposalResponsePayload { - result := []*peer.ProposalResponsePayload{} - for _, endorsedAction := range chaincodeEndorsedActions { - proposalResponsePayload := &peer.ProposalResponsePayload{} - if err := proto.Unmarshal(endorsedAction.GetProposalResponsePayload(), proposalResponsePayload); err != nil { - panic(err) - } - result = append(result, proposalResponsePayload) - } - return result -} - -func (*ParsedEndorserTransaction) getChaincodeActions(proposalResponsePayloads []*peer.ProposalResponsePayload) []*peer.ChaincodeAction { - result := []*peer.ChaincodeAction{} - for _, proposalResponsePayload := range proposalResponsePayloads { - chaincodeAction := &peer.ChaincodeAction{} - if err := proto.Unmarshal(proposalResponsePayload.GetExtension(), chaincodeAction); err != nil { - panic(err) - } - result = append(result, chaincodeAction) - } - return result -} - -func (*ParsedEndorserTransaction) getTxReadWriteSets(chaincodeActions []*peer.ChaincodeAction) []*rwset.TxReadWriteSet { - result := []*rwset.TxReadWriteSet{} - for _, chaincodeAction := range chaincodeActions { - txReadWriteSet := &rwset.TxReadWriteSet{} - if err := proto.Unmarshal(chaincodeAction.GetResults(), txReadWriteSet); err != nil { - continue - } - result = append(result, txReadWriteSet) - } - return result -} - -func (*ParsedEndorserTransaction) parseReadWriteSets(txReadWriteSets []*rwset.TxReadWriteSet) []ReadWriteSet { - result := []ReadWriteSet{} - for _, txReadWriteSet := range txReadWriteSets { - parsedReadWriteSet := NewParsedReadWriteSet(txReadWriteSet) - result = append(result, parsedReadWriteSet) - } - return result -} - -func (p *ParsedEndorserTransaction) ToProto() *peer.Transaction { - return p.transaction -} - -type ReadWriteSet interface { - GetNamespaceReadWriteSets() []NamespaceReadWriteSet - ToProto() *rwset.TxReadWriteSet -} - -type ParsedReadWriteSet struct { - readWriteSet *rwset.TxReadWriteSet -} - -func NewParsedReadWriteSet(rwSet *rwset.TxReadWriteSet) ReadWriteSet { - return &ParsedReadWriteSet{rwSet} -} - -func (p *ParsedReadWriteSet) GetNamespaceReadWriteSets() []NamespaceReadWriteSet { - result := []NamespaceReadWriteSet{} - for _, nsReadWriteSet := range p.readWriteSet.GetNsRwset() { - parsedNamespaceReadWriteSet := NewParsedNamespaceReadWriteSet(nsReadWriteSet) - result = append(result, parsedNamespaceReadWriteSet) - } - return result -} - -func (p *ParsedReadWriteSet) ToProto() *rwset.TxReadWriteSet { - return p.readWriteSet -} - -type NamespaceReadWriteSet interface { - GetNamespace() string - GetReadWriteSet() *kvrwset.KVRWSet - ToProto() *rwset.NsReadWriteSet -} - -type ParsedNamespaceReadWriteSet struct { - nsReadWriteSet *rwset.NsReadWriteSet -} - -func NewParsedNamespaceReadWriteSet(nsRwSet *rwset.NsReadWriteSet) NamespaceReadWriteSet { - return &ParsedNamespaceReadWriteSet{nsRwSet} -} - -func (p *ParsedNamespaceReadWriteSet) GetNamespace() string { - return p.nsReadWriteSet.GetNamespace() -} - -// TODO add cache -func (p *ParsedNamespaceReadWriteSet) GetReadWriteSet() *kvrwset.KVRWSet { - result := kvrwset.KVRWSet{} - if err := proto.Unmarshal(p.nsReadWriteSet.GetRwset(), &result); err != nil { - panic(err) - } - - return &result -} - -func (p *ParsedNamespaceReadWriteSet) ToProto() *rwset.NsReadWriteSet { - return p.nsReadWriteSet -} - -func (pb *ParsedBlock) payloads() []*common.Payload { - var payloads []*common.Payload - - for _, envelopeBytes := range pb.block.GetData().GetData() { - envelope := &common.Envelope{} - if err := proto.Unmarshal(envelopeBytes, envelope); err != nil { - panic(err) - } - - payload := &common.Payload{} - if err := proto.Unmarshal(envelope.Payload, payload); err != nil { - panic(err) - } - - payloads = append(payloads, payload) - } - - return payloads -} - -// TODO not sure about this -func (pb *ParsedBlock) statusCode(txIndex int) peer.TxValidationCode { - blockMetadata := assertDefined( - pb.block.GetMetadata(), - "missing block metadata", - ) - - metadata := blockMetadata.GetMetadata() - if int(common.BlockMetadataIndex_TRANSACTIONS_FILTER) >= len(metadata) { - return peer.TxValidationCode_INVALID_OTHER_REASON - } - - statusCodes := metadata[common.BlockMetadataIndex_TRANSACTIONS_FILTER] - if txIndex >= len(statusCodes) { - return peer.TxValidationCode_INVALID_OTHER_REASON - } - - return peer.TxValidationCode(statusCodes[txIndex]) -} - -type Payload interface { - GetChannelHeader() *common.ChannelHeader - GetEndorserTransaction() EndorserTransaction - GetSignatureHeader() *common.SignatureHeader - GetTransactionValidationCode() int32 - IsEndorserTransaction() bool - IsValid() bool - ToProto() *common.Payload -} - -type PayloadImpl struct { - payload *common.Payload - statusCode int32 -} - -func NewPayloadImpl(payload *common.Payload, statusCode int32) Payload { - return &PayloadImpl{payload, statusCode} -} - -func (p *PayloadImpl) GetChannelHeader() *common.ChannelHeader { - header := assertDefined(p.payload.GetHeader(), "missing payload header") - - // TODO add cache, return cachedChannelHeader like in blockParser.ts:77 - result := &common.ChannelHeader{} - if err := proto.Unmarshal(header.GetChannelHeader(), result); err != nil { - panic(err) - } - - return result -} - -func (p *PayloadImpl) GetEndorserTransaction() EndorserTransaction { - if !p.IsEndorserTransaction() { - panic(fmt.Errorf("unexpected payload type: %d", p.GetChannelHeader().GetType())) - } - - result := &peer.Transaction{} - if err := proto.Unmarshal(p.payload.GetData(), result); err != nil { - panic(err) - } - - return NewParsedEndorserTransaction(result) -} - -func (p *PayloadImpl) GetSignatureHeader() *common.SignatureHeader { - header := assertDefined(p.payload.GetHeader(), "missing payload header") - - // TODO add cache, return cachedSignatureHeader like in blockParser.ts:77 - result := &common.SignatureHeader{} - if err := proto.Unmarshal(header.GetSignatureHeader(), result); err != nil { - panic(err) - } - - return result -} - -func (p *PayloadImpl) GetTransactionValidationCode() int32 { - return p.statusCode -} - -func (p *PayloadImpl) IsEndorserTransaction() bool { - return p.GetChannelHeader().GetType() == int32(common.HeaderType_ENDORSER_TRANSACTION) -} - -func (p *PayloadImpl) IsValid() bool { - return p.statusCode == int32(peer.TxValidationCode_VALID) -} - -func (p *PayloadImpl) ToProto() *common.Payload { - return p.payload -} - -func getTransactionValidationCodes(block *common.Block) []byte { - metadata := assertDefined( - block.GetMetadata(), - "missing block metadata", - ) - - return assertDefined( - metadata.GetMetadata()[common.BlockMetadataIndex_TRANSACTIONS_FILTER], - "missing transaction validation code", - ) -} diff --git a/off_chain_data/application-go/contract.go b/off_chain_data/application-go/contract.go deleted file mode 100644 index 2eb5b360..00000000 --- a/off_chain_data/application-go/contract.go +++ /dev/null @@ -1,93 +0,0 @@ -/* - * Copyright 2024 IBM All Rights Reserved. - * - * SPDX-License-Identifier: Apache-2.0 - */ - -package main - -import ( - "strconv" - - "github.com/google/uuid" - "github.com/hyperledger/fabric-gateway/pkg/client" -) - -type Asset struct { - ID string - Color string - Size uint64 - Owner string - AppraisedValue uint64 -} - -func NewAsset() Asset { - id, err := uuid.NewRandom() - if err != nil { - panic(err) - } - - return Asset{ - ID: id.String(), - Color: randomElement(colors), - Size: uint64(randomInt(maxInitialSize) + 1), - Owner: randomElement(owners), - AppraisedValue: uint64(randomInt(maxInitialValue) + 1), - } -} - -type assetTransferBasic struct { - contract *client.Contract -} - -func newAssetTransferBasic(contract *client.Contract) *assetTransferBasic { - return &assetTransferBasic{contract} -} - -func (atb *assetTransferBasic) createAsset(anAsset Asset) { - if _, err := atb.contract.Submit( - "CreateAsset", - client.WithArguments( - anAsset.ID, - anAsset.Color, - strconv.FormatUint(anAsset.Size, 10), - anAsset.Owner, - strconv.FormatUint(anAsset.AppraisedValue, 10), - )); err != nil { - panic(err) - } -} - -func (atb *assetTransferBasic) transferAsset(id, newOwner string) string { - result, err := atb.contract.Submit( - "TransferAsset", - client.WithArguments( - id, - newOwner, - ), - ) - if err != nil { - panic(err) - } - - return string(result) -} - -func (atb *assetTransferBasic) deleteAsset(id string) { - if _, err := atb.contract.Submit( - "DeleteAsset", - client.WithArguments( - id, - ), - ); err != nil { - panic(err) - } -} - -func (atb *assetTransferBasic) getAllAssets() []byte { - result, err := atb.contract.Evaluate("GetAllAssets") - if err != nil { - panic(err) - } - return result -} diff --git a/off_chain_data/application-go/contract/asset.go b/off_chain_data/application-go/contract/asset.go new file mode 100644 index 00000000..70c4e688 --- /dev/null +++ b/off_chain_data/application-go/contract/asset.go @@ -0,0 +1,45 @@ +/* + * Copyright 2024 IBM All Rights Reserved. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package contract + +import ( + "offChainData/utils" + + "github.com/google/uuid" +) + +var ( + colors = []string{"red", "green", "blue"} + Owners = []string{"alice", "bob", "charlie"} +) + +const ( + maxInitialValue = 1000 + maxInitialSize = 10 +) + +type Asset struct { + ID string `json:"ID"` + Color string `json:"Color"` + Size uint64 `json:"Size"` + Owner string `json:"Owner"` + AppraisedValue uint64 `json:"AppraisedValue"` +} + +func NewAsset() Asset { + id, err := uuid.NewRandom() + if err != nil { + panic(err) + } + + return Asset{ + ID: id.String(), + Color: utils.RandomElement(colors), + Size: uint64(utils.RandomInt(maxInitialSize) + 1), + Owner: utils.RandomElement(Owners), + AppraisedValue: uint64(utils.RandomInt(maxInitialValue) + 1), + } +} diff --git a/off_chain_data/application-go/contract/contract.go b/off_chain_data/application-go/contract/contract.go new file mode 100644 index 00000000..e672620d --- /dev/null +++ b/off_chain_data/application-go/contract/contract.go @@ -0,0 +1,68 @@ +/* + * Copyright 2024 IBM All Rights Reserved. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package contract + +import ( + "strconv" + + "github.com/hyperledger/fabric-gateway/pkg/client" +) + +type AssetTransferBasic struct { + contract *client.Contract +} + +func NewAssetTransferBasic(contract *client.Contract) *AssetTransferBasic { + return &AssetTransferBasic{contract} +} + +func (atb *AssetTransferBasic) CreateAsset(anAsset Asset) { + if _, err := atb.contract.Submit( + "CreateAsset", + client.WithArguments( + anAsset.ID, + anAsset.Color, + strconv.FormatUint(anAsset.Size, 10), + anAsset.Owner, + strconv.FormatUint(anAsset.AppraisedValue, 10), + )); err != nil { + panic(err) + } +} + +func (atb *AssetTransferBasic) TransferAsset(id, newOwner string) string { + result, err := atb.contract.Submit( + "TransferAsset", + client.WithArguments( + id, + newOwner, + ), + ) + if err != nil { + panic(err) + } + + return string(result) +} + +func (atb *AssetTransferBasic) DeleteAsset(id string) { + if _, err := atb.contract.Submit( + "DeleteAsset", + client.WithArguments( + id, + ), + ); err != nil { + panic(err) + } +} + +func (atb *AssetTransferBasic) GetAllAssets() []byte { + result, err := atb.contract.Evaluate("GetAllAssets") + if err != nil { + panic(err) + } + return result +} diff --git a/off_chain_data/application-go/getAllAssets.go b/off_chain_data/application-go/getAllAssets.go index 9c6643da..a6aff5db 100644 --- a/off_chain_data/application-go/getAllAssets.go +++ b/off_chain_data/application-go/getAllAssets.go @@ -10,6 +10,7 @@ import ( "bytes" "encoding/json" "fmt" + atb "offChainData/contract" "github.com/hyperledger/fabric-gateway/pkg/client" "google.golang.org/grpc" @@ -24,8 +25,8 @@ func getAllAssets(clientConnection *grpc.ClientConn) { defer gateway.Close() contract := gateway.GetNetwork(channelName).GetContract(chaincodeName) - smartContract := newAssetTransferBasic(contract) - assets := smartContract.getAllAssets() + smartContract := atb.NewAssetTransferBasic(contract) + assets := smartContract.GetAllAssets() fmt.Printf("%s\n", formatJSON(assets)) } diff --git a/off_chain_data/application-go/go.mod b/off_chain_data/application-go/go.mod index d73c77b2..83adb07d 100644 --- a/off_chain_data/application-go/go.mod +++ b/off_chain_data/application-go/go.mod @@ -1,14 +1,12 @@ module offChainData -go 1.22.7 - -toolchain go1.23.0 +go 1.22.0 require ( github.com/google/uuid v1.6.0 github.com/hyperledger/fabric-gateway v1.7.0 github.com/hyperledger/fabric-protos-go-apiv2 v0.3.4 - google.golang.org/grpc v1.68.0 + google.golang.org/grpc v1.68.0-dev google.golang.org/protobuf v1.35.2 ) diff --git a/off_chain_data/application-go/go.sum b/off_chain_data/application-go/go.sum index ba6230a4..e3d2874e 100644 --- a/off_chain_data/application-go/go.sum +++ b/off_chain_data/application-go/go.sum @@ -1,7 +1,5 @@ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= -github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= @@ -28,8 +26,8 @@ golang.org/x/text v0.20.0 h1:gK/Kv2otX8gz+wn7Rmb3vT96ZwuoxnQlY+HlJVj7Qug= golang.org/x/text v0.20.0/go.mod h1:D4IsuqiFMhST5bX19pQ9ikHC2GsaKyk/oF+pn3ducp4= google.golang.org/genproto/googleapis/rpc v0.0.0-20241118233622-e639e219e697 h1:LWZqQOEjDyONlF1H6afSWpAL/znlREo2tHfLoe+8LMA= google.golang.org/genproto/googleapis/rpc v0.0.0-20241118233622-e639e219e697/go.mod h1:5uTbfoYQed2U9p3KIj2/Zzm02PYhndfdmML0qC3q3FU= -google.golang.org/grpc v1.68.0 h1:aHQeeJbo8zAkAa3pRzrVjZlbz6uSfeOXlJNQM0RAbz0= -google.golang.org/grpc v1.68.0/go.mod h1:fmSPC5AsjSBCK54MyHRx48kpOti1/jRfOlwEWywNjWA= +google.golang.org/grpc v1.68.0-dev h1:Qao/m2HpklhJt2QbpdRutxyNfRuwM8nGPpmi2UkuEHw= +google.golang.org/grpc v1.68.0-dev/go.mod h1:1gLDyUQU7CTLJI90u3nXZ9ekeghjeM7pTDZlqFNg2AA= google.golang.org/protobuf v1.35.2 h1:8Ar7bF+apOIoThw1EdZl0p1oWvMqTHmpA2fRTyZO8io= google.golang.org/protobuf v1.35.2/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= diff --git a/off_chain_data/application-go/parser/blockParser.go b/off_chain_data/application-go/parser/blockParser.go new file mode 100644 index 00000000..c12f3b25 --- /dev/null +++ b/off_chain_data/application-go/parser/blockParser.go @@ -0,0 +1,87 @@ +package parser + +import ( + "offChainData/utils" + + "github.com/hyperledger/fabric-protos-go-apiv2/common" + "github.com/hyperledger/fabric-protos-go-apiv2/peer" + "google.golang.org/protobuf/proto" +) + +type Block struct { + block *common.Block + validationCodes []byte + transactions []Transaction +} + +func ParseBlock(block *common.Block) Block { + validationCodes := extractTransactionValidationCodes(block) + + return Block{block, validationCodes, nil} +} + +func (b *Block) Number() uint64 { + header := utils.AssertDefined(b.block.GetHeader(), "missing block header") + return header.GetNumber() +} + +// TODO: needs cache, getPayloads, parsePayload +func (b *Block) Transactions() []Transaction { + return nil +} + +func (b *Block) ToProto() *common.Block { + return nil +} + +func (b *Block) payloads() []*common.Payload { + var payloads []*common.Payload + + for _, envelopeBytes := range b.block.GetData().GetData() { + envelope := &common.Envelope{} + if err := proto.Unmarshal(envelopeBytes, envelope); err != nil { + panic(err) + } + + payload := &common.Payload{} + if err := proto.Unmarshal(envelope.Payload, payload); err != nil { + panic(err) + } + + payloads = append(payloads, payload) + } + + return payloads +} + +// TODO not sure about this +func (pb *Block) statusCode(txIndex int) peer.TxValidationCode { + blockMetadata := utils.AssertDefined( + pb.block.GetMetadata(), + "missing block metadata", + ) + + metadata := blockMetadata.GetMetadata() + if int(common.BlockMetadataIndex_TRANSACTIONS_FILTER) >= len(metadata) { + return peer.TxValidationCode_INVALID_OTHER_REASON + } + + statusCodes := metadata[common.BlockMetadataIndex_TRANSACTIONS_FILTER] + if txIndex >= len(statusCodes) { + return peer.TxValidationCode_INVALID_OTHER_REASON + } + + return peer.TxValidationCode(statusCodes[txIndex]) +} + +func extractTransactionValidationCodes(block *common.Block) []byte { + metadata := utils.AssertDefined( + block.GetMetadata(), + "missing block metadata", + ) + + return utils.AssertDefined( + metadata.GetMetadata()[common.BlockMetadataIndex_TRANSACTIONS_FILTER], + "missing transaction validation code", + ) +} diff --git a/off_chain_data/application-go/blockParser_test.go b/off_chain_data/application-go/parser/blockParser_test.go similarity index 75% rename from off_chain_data/application-go/blockParser_test.go rename to off_chain_data/application-go/parser/blockParser_test.go index a90bdc71..0432f720 100644 --- a/off_chain_data/application-go/blockParser_test.go +++ b/off_chain_data/application-go/parser/blockParser_test.go @@ -1,10 +1,11 @@ -package main_test +package parser_test import ( "encoding/json" "testing" - ocd "offChainData" + atb "offChainData/contract" + "offChainData/parser" "github.com/hyperledger/fabric-protos-go-apiv2/ledger/rwset" "github.com/hyperledger/fabric-protos-go-apiv2/ledger/rwset/kvrwset" @@ -34,25 +35,30 @@ func TestGetReadWriteSetsFromEndorserTransaction(t *testing.T) { }, } - parsedEndorserTransaction := ocd.NewParsedEndorserTransaction(transaction) - if len(parsedEndorserTransaction.GetReadWriteSets()) != 1 { - t.Fatal("expected 1 ReadWriteSet, got", len(parsedEndorserTransaction.GetReadWriteSets())) + parsedEndorserTransaction := parser.ParseEndorserTransaction(transaction) + if len(parsedEndorserTransaction.ReadWriteSets()) != 1 { + t.Fatal("expected 1 ReadWriteSet, got", len(parsedEndorserTransaction.ReadWriteSets())) } assertReadWriteSet( - parsedEndorserTransaction.GetReadWriteSets()[0].GetNamespaceReadWriteSets()[0], + parsedEndorserTransaction.ReadWriteSets()[0].NamespaceReadWriteSets()[0], expectedNamespace, expectedAsset, t, ) } -func assertReadWriteSet(parsedNsRwSet ocd.NamespaceReadWriteSet, expectedNamespace string, expectedAsset ocd.Asset, t *testing.T) { - if parsedNsRwSet.GetNamespace() != expectedNamespace { - t.Errorf("expected namespace %s, got %s", expectedNamespace, parsedNsRwSet.GetNamespace()) +func assertReadWriteSet( + parsedNsRwSet parser.NamespaceReadWriteSet, + expectedNamespace string, + expectedAsset atb.Asset, + t *testing.T, +) { + if parsedNsRwSet.Namespace() != expectedNamespace { + t.Errorf("expected namespace %s, got %s", expectedNamespace, parsedNsRwSet.Namespace()) } - actualKVRWSet := parsedNsRwSet.GetReadWriteSet() + actualKVRWSet := parsedNsRwSet.ReadWriteSet() if len(actualKVRWSet.Writes) != 1 { t.Fatal("expected 1 write, got", len(actualKVRWSet.Writes)) } @@ -74,16 +80,16 @@ func TestReadWriteSetWrapping(t *testing.T) { NsRwset: []*rwset.NsReadWriteSet{nsReadWriteSetFake}, } - parsedRwSet := ocd.NewParsedReadWriteSet(txReadWriteSetFake) - if len(parsedRwSet.GetNamespaceReadWriteSets()) != 1 { - t.Fatalf("Expected 1 NamespaceReadWriteSet, got %d", len(parsedRwSet.GetNamespaceReadWriteSets())) + parsedRwSet := parser.ParseReadWriteSet(txReadWriteSetFake) + if len(parsedRwSet.NamespaceReadWriteSets()) != 1 { + t.Fatalf("Expected 1 NamespaceReadWriteSet, got %d", len(parsedRwSet.NamespaceReadWriteSets())) } } func TestNamespaceReadWriteSetParsing(t *testing.T) { nsReadWriteSetFake, expectedNamespace, expectedAsset := nsReadWriteSetFake() - parsedNsRwSet := ocd.NewParsedNamespaceReadWriteSet(nsReadWriteSetFake) + parsedNsRwSet := parser.ParseNamespaceReadWriteSet(nsReadWriteSetFake) assertReadWriteSet( parsedNsRwSet, expectedNamespace, @@ -92,9 +98,9 @@ func TestNamespaceReadWriteSetParsing(t *testing.T) { ) } -func nsReadWriteSetFake() (*rwset.NsReadWriteSet, string, ocd.Asset) { +func nsReadWriteSetFake() (*rwset.NsReadWriteSet, string, atb.Asset) { expectedNamespace := "basic" - expectedAsset := ocd.NewAsset() + expectedAsset := atb.NewAsset() result := &rwset.NsReadWriteSet{ Namespace: expectedNamespace, diff --git a/off_chain_data/application-go/parser/identity.go b/off_chain_data/application-go/parser/identity.go new file mode 100644 index 00000000..85c9ecb7 --- /dev/null +++ b/off_chain_data/application-go/parser/identity.go @@ -0,0 +1,16 @@ +package parser + +import "github.com/hyperledger/fabric-protos-go-apiv2/msp" + +// Implements identity.Identity Interface +type identityImpl struct { + creator *msp.SerializedIdentity +} + +func (i *identityImpl) MspID() string { + return i.creator.GetMspid() +} + +func (i *identityImpl) Credentials() []byte { + return i.creator.GetIdBytes() +} diff --git a/off_chain_data/application-go/parser/payload.go b/off_chain_data/application-go/parser/payload.go new file mode 100644 index 00000000..a6f95246 --- /dev/null +++ b/off_chain_data/application-go/parser/payload.go @@ -0,0 +1,82 @@ +package parser + +import ( + "fmt" + "offChainData/utils" + + "github.com/hyperledger/fabric-protos-go-apiv2/common" + "github.com/hyperledger/fabric-protos-go-apiv2/peer" + "google.golang.org/protobuf/proto" +) + +type Payload interface { + ChannelHeader() *common.ChannelHeader + EndorserTransaction() EndorserTransaction + SignatureHeader() *common.SignatureHeader + TransactionValidationCode() int32 + IsEndorserTransaction() bool + IsValid() bool + ToProto() *common.Payload +} + +type PayloadImpl struct { + payload *common.Payload + statusCode int32 +} + +func ParsePayload(payload *common.Payload, statusCode int32) *PayloadImpl { + return &PayloadImpl{payload, statusCode} +} + +func (p *PayloadImpl) ChannelHeader() *common.ChannelHeader { + header := utils.AssertDefined(p.payload.GetHeader(), "missing payload header") + + // TODO add cache, return cachedChannelHeader like in blockParser.ts:77 + result := &common.ChannelHeader{} + if err := proto.Unmarshal(header.GetChannelHeader(), result); err != nil { + panic(err) + } + + return result +} + +func (p *PayloadImpl) EndorserTransaction() EndorserTransaction { + if !p.IsEndorserTransaction() { + panic(fmt.Errorf("unexpected payload type: %d", p.ChannelHeader().GetType())) + } + + result := &peer.Transaction{} + if err := proto.Unmarshal(p.payload.GetData(), result); err != nil { + panic(err) + } + + return ParseEndorserTransaction(result) +} + +func (p *PayloadImpl) SignatureHeader() *common.SignatureHeader { + header := utils.AssertDefined(p.payload.GetHeader(), "missing payload header") + + // TODO add cache, return cachedSignatureHeader like in blockParser.ts:77 + result := &common.SignatureHeader{} + if err := proto.Unmarshal(header.GetSignatureHeader(), result); err != nil { + panic(err) + } + + return result +} + +func (p *PayloadImpl) TransactionValidationCode() int32 { + return p.statusCode +} + +func (p *PayloadImpl) IsEndorserTransaction() bool { + return p.ChannelHeader().GetType() == int32(common.HeaderType_ENDORSER_TRANSACTION) +} + +func (p *PayloadImpl) IsValid() bool { + return p.statusCode == int32(peer.TxValidationCode_VALID) +} + +func (p *PayloadImpl) ToProto() *common.Payload { + return p.payload +} diff --git a/off_chain_data/application-go/parser/readWriteSet.go b/off_chain_data/application-go/parser/readWriteSet.go new file mode 100644 index 00000000..9bb25ac0 --- /dev/null +++ b/off_chain_data/application-go/parser/readWriteSet.go @@ -0,0 +1,65 @@ +package parser + +import ( + "github.com/hyperledger/fabric-protos-go-apiv2/ledger/rwset" + "github.com/hyperledger/fabric-protos-go-apiv2/ledger/rwset/kvrwset" + "google.golang.org/protobuf/proto" +) + +type ReadWriteSet interface { + NamespaceReadWriteSets() []NamespaceReadWriteSet + ToProto() *rwset.TxReadWriteSet +} + +type ReadWriteSetImpl struct { + readWriteSet *rwset.TxReadWriteSet +} + +func ParseReadWriteSet(rwSet *rwset.TxReadWriteSet) *ReadWriteSetImpl { + return &ReadWriteSetImpl{rwSet} +} + +func (p *ReadWriteSetImpl) NamespaceReadWriteSets() []NamespaceReadWriteSet { + result := []NamespaceReadWriteSet{} + for _, nsReadWriteSet := range p.readWriteSet.GetNsRwset() { + parsedNamespaceReadWriteSet := ParseNamespaceReadWriteSet(nsReadWriteSet) + result = append(result, parsedNamespaceReadWriteSet) + } + return result +} + +func (p *ReadWriteSetImpl) ToProto() *rwset.TxReadWriteSet { + return p.readWriteSet +} + +type NamespaceReadWriteSet interface { + Namespace() string + ReadWriteSet() *kvrwset.KVRWSet + ToProto() *rwset.NsReadWriteSet +} + +type NamespaceReadWriteSetImpl struct { + nsReadWriteSet *rwset.NsReadWriteSet +} + +func ParseNamespaceReadWriteSet(nsRwSet *rwset.NsReadWriteSet) *NamespaceReadWriteSetImpl { + return &NamespaceReadWriteSetImpl{nsRwSet} +} + +func (p *NamespaceReadWriteSetImpl) Namespace() string { + return p.nsReadWriteSet.GetNamespace() +} + +// TODO add cache +func (p *NamespaceReadWriteSetImpl) ReadWriteSet() *kvrwset.KVRWSet { + result := kvrwset.KVRWSet{} + if err := proto.Unmarshal(p.nsReadWriteSet.GetRwset(), &result); err != nil { + panic(err) + } + + return &result +} + +func (p *NamespaceReadWriteSetImpl) ToProto() *rwset.NsReadWriteSet { + return p.nsReadWriteSet +} diff --git a/off_chain_data/application-go/parser/transaction.go b/off_chain_data/application-go/parser/transaction.go new file mode 100644 index 00000000..1ba31697 --- /dev/null +++ b/off_chain_data/application-go/parser/transaction.go @@ -0,0 +1,169 @@ +package parser + +import ( + "offChainData/utils" + + "github.com/hyperledger/fabric-gateway/pkg/identity" + "github.com/hyperledger/fabric-protos-go-apiv2/common" + "github.com/hyperledger/fabric-protos-go-apiv2/ledger/rwset" + "github.com/hyperledger/fabric-protos-go-apiv2/msp" + "github.com/hyperledger/fabric-protos-go-apiv2/peer" + "google.golang.org/protobuf/proto" +) + +type Transaction interface { + ChannelHeader() *common.ChannelHeader + Creator() identity.Identity + ValidationCode() int32 + IsValid() bool + NamespaceReadWriteSets() []NamespaceReadWriteSet + ToProto() *common.Payload +} + +type TransactionImpl struct { + payload Payload +} + +func NewTransactionImpl(payload Payload) *TransactionImpl { + return &TransactionImpl{payload} +} + +func (t *TransactionImpl) ChannelHeader() *common.ChannelHeader { + return t.payload.ChannelHeader() +} + +func (t *TransactionImpl) Creator() identity.Identity { + creator := &msp.SerializedIdentity{} + if err := proto.Unmarshal(t.payload.SignatureHeader().GetCreator(), creator); err != nil { + panic(err) + } + + return &identityImpl{creator} +} + +func (t *TransactionImpl) NamespaceReadWriteSets() []NamespaceReadWriteSet { + result := []NamespaceReadWriteSet{} + for _, readWriteSet := range t.payload.EndorserTransaction().ReadWriteSets() { + result = append(result, readWriteSet.NamespaceReadWriteSets()...) + } + + return result +} + +func (t *TransactionImpl) ValidationCode() int32 { + return t.payload.TransactionValidationCode() +} + +func (t *TransactionImpl) IsValid() bool { + return t.payload.IsValid() +} + +func (t *TransactionImpl) ToProto() *common.Payload { + return t.payload.ToProto() +} + +type EndorserTransaction interface { + ReadWriteSets() []ReadWriteSet + ToProto() *peer.Transaction +} + +type EndorserTransactionImpl struct { + transaction *peer.Transaction +} + +func ParseEndorserTransaction(transaction *peer.Transaction) *EndorserTransactionImpl { + return &EndorserTransactionImpl{transaction} +} + +// TODO add cache +func (p *EndorserTransactionImpl) ReadWriteSets() []ReadWriteSet { + chaincodeActionPayloads := p.unmarshalChaincodeActionPayloads() + + chaincodeEndorsedActions := p.extractChaincodeEndorsedActionsFrom(chaincodeActionPayloads) + + proposalResponsePayloads := p.unmarshalProposalResponsePayloadsFrom(chaincodeEndorsedActions) + + chaincodeActions := p.unmarshalChaincodeActionsFrom(proposalResponsePayloads) + + txReadWriteSets := p.unmarshalTxReadWriteSetsFrom(chaincodeActions) + + parsedReadWriteSets := p.parseReadWriteSets(txReadWriteSets) + + return parsedReadWriteSets +} + +func (p *EndorserTransactionImpl) unmarshalChaincodeActionPayloads() []*peer.ChaincodeActionPayload { + result := []*peer.ChaincodeActionPayload{} + for _, transactionAction := range p.transaction.GetActions() { + chaincodeActionPayload := &peer.ChaincodeActionPayload{} + if err := proto.Unmarshal(transactionAction.GetPayload(), chaincodeActionPayload); err != nil { + panic(err) + } + + result = append(result, chaincodeActionPayload) + } + return result +} + +func (*EndorserTransactionImpl) extractChaincodeEndorsedActionsFrom(chaincodeActionPayloads []*peer.ChaincodeActionPayload) []*peer.ChaincodeEndorsedAction { + result := []*peer.ChaincodeEndorsedAction{} + for _, payload := range chaincodeActionPayloads { + result = append( + result, + utils.AssertDefined( + payload.GetAction(), + "missing chaincode endorsed action", + ), + ) + } + return result +} + +func (*EndorserTransactionImpl) unmarshalProposalResponsePayloadsFrom(chaincodeEndorsedActions []*peer.ChaincodeEndorsedAction) []*peer.ProposalResponsePayload { + result := []*peer.ProposalResponsePayload{} + for _, endorsedAction := range chaincodeEndorsedActions { + proposalResponsePayload := &peer.ProposalResponsePayload{} + if err := proto.Unmarshal(endorsedAction.GetProposalResponsePayload(), proposalResponsePayload); err != nil { + panic(err) + } + result = append(result, proposalResponsePayload) + } + return result +} + +func (*EndorserTransactionImpl) unmarshalChaincodeActionsFrom(proposalResponsePayloads []*peer.ProposalResponsePayload) []*peer.ChaincodeAction { + result := []*peer.ChaincodeAction{} + for _, proposalResponsePayload := range proposalResponsePayloads { + chaincodeAction := &peer.ChaincodeAction{} + if err := proto.Unmarshal(proposalResponsePayload.GetExtension(), chaincodeAction); err != nil { + panic(err) + } + result = append(result, chaincodeAction) + } + return result +} + +func (*EndorserTransactionImpl) unmarshalTxReadWriteSetsFrom(chaincodeActions []*peer.ChaincodeAction) []*rwset.TxReadWriteSet { + result := []*rwset.TxReadWriteSet{} + for _, chaincodeAction := range chaincodeActions { + txReadWriteSet := &rwset.TxReadWriteSet{} + if err := proto.Unmarshal(chaincodeAction.GetResults(), txReadWriteSet); err != nil { + continue + } + result = append(result, txReadWriteSet) + } + return result +} + +func (*EndorserTransactionImpl) parseReadWriteSets(txReadWriteSets []*rwset.TxReadWriteSet) []ReadWriteSet { + result := []ReadWriteSet{} + for _, txReadWriteSet := range txReadWriteSets { + parsedReadWriteSet := ParseReadWriteSet(txReadWriteSet) + result = append(result, parsedReadWriteSet) + } + return result +} + +func (p *EndorserTransactionImpl) ToProto() *peer.Transaction { + return p.transaction +} diff --git a/off_chain_data/application-go/transact.go b/off_chain_data/application-go/transact.go index b872b836..93ca3398 100644 --- a/off_chain_data/application-go/transact.go +++ b/off_chain_data/application-go/transact.go @@ -3,6 +3,9 @@ package main import ( "fmt" + atb "offChainData/contract" + "offChainData/utils" + "github.com/hyperledger/fabric-gateway/pkg/client" "google.golang.org/grpc" ) @@ -17,30 +20,20 @@ func transact(clientConnection *grpc.ClientConn) { contract := gateway.GetNetwork(channelName).GetContract(chaincodeName) - smartContract := newAssetTransferBasic(contract) + smartContract := atb.NewAssetTransferBasic(contract) app := newTransactApp(smartContract) app.run() } type transactApp struct { - smartContract *assetTransferBasic + smartContract *atb.AssetTransferBasic batchSize uint } -func newTransactApp(smartContract *assetTransferBasic) *transactApp { +func newTransactApp(smartContract *atb.AssetTransferBasic) *transactApp { return &transactApp{smartContract, 10} } -var ( - colors = []string{"red", "green", "blue"} - owners = []string{"alice", "bob", "charlie"} -) - -const ( - maxInitialValue = 1000 - maxInitialSize = 10 -) - func (t *transactApp) run() { for i := 0; i < int(t.batchSize); i++ { go t.transact() @@ -48,21 +41,21 @@ func (t *transactApp) run() { } func (t *transactApp) transact() { - anAsset := NewAsset() + anAsset := atb.NewAsset() - t.smartContract.createAsset(anAsset) + t.smartContract.CreateAsset(anAsset) fmt.Printf("\nCreated asset %s\n", anAsset.ID) // Transfer randomly 1 in 2 assets to a new owner. - if randomInt(2) == 0 { - newOwner := differentElement(owners, anAsset.Owner) - oldOwner := t.smartContract.transferAsset(anAsset.ID, newOwner) + if utils.RandomInt(2) == 0 { + newOwner := utils.DifferentElement(atb.Owners, anAsset.Owner) + oldOwner := t.smartContract.TransferAsset(anAsset.ID, newOwner) fmt.Printf("Transferred asset %s from %s to %s\n", anAsset.ID, oldOwner, newOwner) } // Delete randomly 1 in 4 created assets. - if randomInt(4) == 0 { - t.smartContract.deleteAsset(anAsset.ID) + if utils.RandomInt(4) == 0 { + t.smartContract.DeleteAsset(anAsset.ID) fmt.Printf("Deleted asset %s\n", anAsset.ID) } } diff --git a/off_chain_data/application-go/utils.go b/off_chain_data/application-go/utils/utils.go similarity index 59% rename from off_chain_data/application-go/utils.go rename to off_chain_data/application-go/utils/utils.go index c0c77af2..315ff4d6 100644 --- a/off_chain_data/application-go/utils.go +++ b/off_chain_data/application-go/utils/utils.go @@ -1,4 +1,4 @@ -package main +package utils import ( "crypto/rand" @@ -6,12 +6,12 @@ import ( "math/big" ) -func randomElement(values []string) string { - result := values[randomInt(len(values))] +func RandomElement(values []string) string { + result := values[RandomInt(len(values))] return result } -func randomInt(max int) int { +func RandomInt(max int) int { result, err := rand.Int(rand.Reader, big.NewInt(int64(max))) if err != nil { panic(err) @@ -20,17 +20,17 @@ func randomInt(max int) int { return int(result.Int64()) } -func differentElement(values []string, currentValue string) string { +func DifferentElement(values []string, currentValue string) string { candidateValues := []string{} for _, v := range values { if v != currentValue { candidateValues = append(candidateValues, v) } } - return randomElement(candidateValues) + return RandomElement(candidateValues) } -func assertDefined[T any](value T, message string) T { +func AssertDefined[T any](value T, message string) T { if any(value) == any(nil) { panic(errors.New(message)) }