From c4db079e0cfbabd96a0d86123c4c33ab4c202ed7 Mon Sep 17 00:00:00 2001 From: Stanislav Jakuschevskij Date: Mon, 30 Dec 2024 20:41:18 +0100 Subject: [PATCH] Implement caching Added caching util function with tests and applied in: - parser.Block.Transactions(), - parser.Payload.ChannelHeader(), - parser.Payload.SignatureHeader(), - parser.NamespaceReadWriteSet.ReadWriteSet(), - parser.EndorserTransaction.ReadWriteSets(), methods, as it was in the typescript sample. Corrected Println usage and added comments to util functions. Signed-off-by: Stanislav Jakuschevskij --- off_chain_data/application-go/app.go | 4 +- off_chain_data/application-go/getAllAssets.go | 2 +- off_chain_data/application-go/listen.go | 4 +- .../application-go/parser/blockParser.go | 65 +++++++++---------- .../application-go/parser/blockParser_test.go | 6 +- .../application-go/parser/payload.go | 30 +++++---- .../application-go/parser/readWriteSet.go | 15 +++-- .../application-go/parser/transaction.go | 17 +++-- off_chain_data/application-go/transact.go | 4 +- off_chain_data/application-go/utils/utils.go | 18 +++++ .../application-go/utils/utils_test.go | 64 ++++++++++++++++++ .../application-typescript/src/utils.ts | 2 +- 12 files changed, 158 insertions(+), 73 deletions(-) create mode 100644 off_chain_data/application-go/utils/utils_test.go diff --git a/off_chain_data/application-go/app.go b/off_chain_data/application-go/app.go index fd8fd079..07d46dd8 100644 --- a/off_chain_data/application-go/app.go +++ b/off_chain_data/application-go/app.go @@ -33,7 +33,7 @@ func main() { printUsage() panic(fmt.Errorf("unknown command: %s", name)) } - fmt.Printf("command: %s\n", name) + fmt.Println("command:", name) } client := newGrpcConnection() @@ -47,7 +47,7 @@ func main() { func printUsage() { fmt.Println("Arguments: [ ...]") - fmt.Printf("Available commands: %v\n", availableCommands()) + fmt.Println("Available commands:", availableCommands()) } func availableCommands() string { diff --git a/off_chain_data/application-go/getAllAssets.go b/off_chain_data/application-go/getAllAssets.go index a6aff5db..558b19ed 100644 --- a/off_chain_data/application-go/getAllAssets.go +++ b/off_chain_data/application-go/getAllAssets.go @@ -28,7 +28,7 @@ func getAllAssets(clientConnection *grpc.ClientConn) { smartContract := atb.NewAssetTransferBasic(contract) assets := smartContract.GetAllAssets() - fmt.Printf("%s\n", formatJSON(assets)) + fmt.Println(formatJSON(assets)) } func formatJSON(data []byte) string { diff --git a/off_chain_data/application-go/listen.go b/off_chain_data/application-go/listen.go index c25f64a7..80e6a268 100644 --- a/off_chain_data/application-go/listen.go +++ b/off_chain_data/application-go/listen.go @@ -108,8 +108,8 @@ func listen(clientConnection *grpc.ClientConn) { } defer checkpointer.Close() - fmt.Printf("Start event listening from block %d\n", checkpointer.BlockNumber()) - fmt.Printf("Last processed transaction ID within block: %s\n", checkpointer.TransactionID()) + fmt.Println("Start event listening from block", checkpointer.BlockNumber()) + fmt.Println("Last processed transaction ID within block:", checkpointer.TransactionID()) if simulatedFailureCount > 0 { fmt.Printf("Simulating a write failure every %d transactions\n", simulatedFailureCount) } diff --git a/off_chain_data/application-go/parser/blockParser.go b/off_chain_data/application-go/parser/blockParser.go index b9539ea3..7bd1c6e6 100644 --- a/off_chain_data/application-go/parser/blockParser.go +++ b/off_chain_data/application-go/parser/blockParser.go @@ -22,23 +22,38 @@ func (b *Block) Number() uint64 { return header.GetNumber() } -// TODO: needs cache func (b *Block) Transactions() []*Transaction { - envelopes := b.unmarshalEnvelopesFromBlockData() + return utils.Cache(func() []*Transaction { + envelopes := b.unmarshalEnvelopesFromBlockData() - commonPayloads := b.unmarshalPayloadsFrom(envelopes) + commonPayloads := b.unmarshalPayloadsFrom(envelopes) - payloads := b.parse(commonPayloads) + payloads := b.parse(commonPayloads) - result := b.createTransactionsFrom(payloads) + return b.createTransactionsFrom(payloads) + })() +} +func (b *Block) unmarshalEnvelopesFromBlockData() []*common.Envelope { + result := []*common.Envelope{} + for _, blockData := range b.block.GetData().GetData() { + envelope := &common.Envelope{} + if err := proto.Unmarshal(blockData, envelope); err != nil { + panic(err) + } + result = append(result, envelope) + } return result } -func (*Block) createTransactionsFrom(payloads []*PayloadImpl) []*Transaction { - result := []*Transaction{} - for _, payload := range payloads { - result = append(result, NewTransaction(payload)) +func (*Block) unmarshalPayloadsFrom(envelopes []*common.Envelope) []*common.Payload { + result := []*common.Payload{} + for _, envelope := range envelopes { + commonPayload := &common.Payload{} + if err := proto.Unmarshal(envelope.GetPayload(), commonPayload); err != nil { + panic(err) + } + result = append(result, commonPayload) } return result } @@ -62,30 +77,6 @@ func (b *Block) parse(commonPayloads []*common.Payload) []*PayloadImpl { return result } -func (*Block) unmarshalPayloadsFrom(envelopes []*common.Envelope) []*common.Payload { - result := []*common.Payload{} - for _, envelope := range envelopes { - commonPayload := &common.Payload{} - if err := proto.Unmarshal(envelope.GetPayload(), commonPayload); err != nil { - panic(err) - } - result = append(result, commonPayload) - } - return result -} - -func (b *Block) unmarshalEnvelopesFromBlockData() []*common.Envelope { - result := []*common.Envelope{} - for _, blockData := range b.block.GetData().GetData() { - envelope := &common.Envelope{} - if err := proto.Unmarshal(blockData, envelope); err != nil { - panic(err) - } - result = append(result, envelope) - } - return result -} - func (b *Block) extractTransactionValidationCodes() []byte { metadata := utils.AssertDefined( b.block.GetMetadata(), @@ -98,6 +89,14 @@ func (b *Block) extractTransactionValidationCodes() []byte { ) } +func (*Block) createTransactionsFrom(payloads []*PayloadImpl) []*Transaction { + result := []*Transaction{} + for _, payload := range payloads { + result = append(result, NewTransaction(payload)) + } + return result +} + // TODO remove unused? func (b *Block) ToProto() *common.Block { return b.block diff --git a/off_chain_data/application-go/parser/blockParser_test.go b/off_chain_data/application-go/parser/blockParser_test.go index 0432f720..80b5b1ea 100644 --- a/off_chain_data/application-go/parser/blockParser_test.go +++ b/off_chain_data/application-go/parser/blockParser_test.go @@ -14,7 +14,7 @@ import ( "google.golang.org/protobuf/reflect/protoreflect" ) -func TestGetReadWriteSetsFromEndorserTransaction(t *testing.T) { +func Test_GetReadWriteSetsFromEndorserTransaction(t *testing.T) { nsReadWriteSetFake, expectedNamespace, expectedAsset := nsReadWriteSetFake() transaction := &peer.Transaction{ @@ -73,7 +73,7 @@ func assertReadWriteSet( } } -func TestReadWriteSetWrapping(t *testing.T) { +func Test_ReadWriteSetWrapping(t *testing.T) { nsReadWriteSetFake, _, _ := nsReadWriteSetFake() txReadWriteSetFake := &rwset.TxReadWriteSet{ @@ -86,7 +86,7 @@ func TestReadWriteSetWrapping(t *testing.T) { } } -func TestNamespaceReadWriteSetParsing(t *testing.T) { +func Test_NamespaceReadWriteSetParsing(t *testing.T) { nsReadWriteSetFake, expectedNamespace, expectedAsset := nsReadWriteSetFake() parsedNsRwSet := parser.ParseNamespaceReadWriteSet(nsReadWriteSetFake) diff --git a/off_chain_data/application-go/parser/payload.go b/off_chain_data/application-go/parser/payload.go index b1588126..46d666db 100644 --- a/off_chain_data/application-go/parser/payload.go +++ b/off_chain_data/application-go/parser/payload.go @@ -30,15 +30,16 @@ func ParsePayload(payload *common.Payload, statusCode int32) *PayloadImpl { } func (p *PayloadImpl) ChannelHeader() *common.ChannelHeader { - header := utils.AssertDefined(p.payload.GetHeader(), "missing payload header") + return utils.Cache(func() *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) - } + result := &common.ChannelHeader{} + if err := proto.Unmarshal(header.GetChannelHeader(), result); err != nil { + panic(err) + } - return result + return result + })() } func (p *PayloadImpl) EndorserTransaction() EndorserTransaction { @@ -55,15 +56,16 @@ func (p *PayloadImpl) EndorserTransaction() EndorserTransaction { } func (p *PayloadImpl) SignatureHeader() *common.SignatureHeader { - header := utils.AssertDefined(p.payload.GetHeader(), "missing payload header") + return utils.Cache(func() *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) - } + result := &common.SignatureHeader{} + if err := proto.Unmarshal(header.GetSignatureHeader(), result); err != nil { + panic(err) + } - return result + return result + })() } func (p *PayloadImpl) TransactionValidationCode() int32 { diff --git a/off_chain_data/application-go/parser/readWriteSet.go b/off_chain_data/application-go/parser/readWriteSet.go index 359eef47..f16b1e97 100644 --- a/off_chain_data/application-go/parser/readWriteSet.go +++ b/off_chain_data/application-go/parser/readWriteSet.go @@ -1,6 +1,8 @@ package parser import ( + "offChainData/utils" + "github.com/hyperledger/fabric-protos-go-apiv2/ledger/rwset" "github.com/hyperledger/fabric-protos-go-apiv2/ledger/rwset/kvrwset" "google.golang.org/protobuf/proto" @@ -53,14 +55,15 @@ 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 utils.Cache(func() *kvrwset.KVRWSet { + result := kvrwset.KVRWSet{} + if err := proto.Unmarshal(p.nsReadWriteSet.GetRwset(), &result); err != nil { + panic(err) + } - return &result + return &result + })() } // TODO remove unused diff --git a/off_chain_data/application-go/parser/transaction.go b/off_chain_data/application-go/parser/transaction.go index a22ecd77..b2d3b1e1 100644 --- a/off_chain_data/application-go/parser/transaction.go +++ b/off_chain_data/application-go/parser/transaction.go @@ -71,21 +71,20 @@ func ParseEndorserTransaction(transaction *peer.Transaction) *EndorserTransactio return &EndorserTransactionImpl{transaction} } -// TODO add cache func (p *EndorserTransactionImpl) ReadWriteSets() []ReadWriteSet { - chaincodeActionPayloads := p.unmarshalChaincodeActionPayloads() + return utils.Cache(func() []ReadWriteSet { + chaincodeActionPayloads := p.unmarshalChaincodeActionPayloads() - chaincodeEndorsedActions := p.extractChaincodeEndorsedActionsFrom(chaincodeActionPayloads) + chaincodeEndorsedActions := p.extractChaincodeEndorsedActionsFrom(chaincodeActionPayloads) - proposalResponsePayloads := p.unmarshalProposalResponsePayloadsFrom(chaincodeEndorsedActions) + proposalResponsePayloads := p.unmarshalProposalResponsePayloadsFrom(chaincodeEndorsedActions) - chaincodeActions := p.unmarshalChaincodeActionsFrom(proposalResponsePayloads) + chaincodeActions := p.unmarshalChaincodeActionsFrom(proposalResponsePayloads) - txReadWriteSets := p.unmarshalTxReadWriteSetsFrom(chaincodeActions) + txReadWriteSets := p.unmarshalTxReadWriteSetsFrom(chaincodeActions) - parsedReadWriteSets := p.parseReadWriteSets(txReadWriteSets) - - return parsedReadWriteSets + return p.parseReadWriteSets(txReadWriteSets) + })() } func (p *EndorserTransactionImpl) unmarshalChaincodeActionPayloads() []*peer.ChaincodeActionPayload { diff --git a/off_chain_data/application-go/transact.go b/off_chain_data/application-go/transact.go index a13ae059..0735e5ae 100644 --- a/off_chain_data/application-go/transact.go +++ b/off_chain_data/application-go/transact.go @@ -44,7 +44,7 @@ func (t *transactApp) transact() { anAsset := atb.NewAsset() t.smartContract.CreateAsset(anAsset) - fmt.Printf("\nCreated asset %s\n", anAsset.ID) + fmt.Println("Created asset", anAsset.ID) // Transfer randomly 1 in 2 assets to a new owner. if utils.RandomInt(2) == 0 { @@ -56,6 +56,6 @@ func (t *transactApp) transact() { // Delete randomly 1 in 4 created assets. if utils.RandomInt(4) == 0 { t.smartContract.DeleteAsset(anAsset.ID) - fmt.Printf("Deleted asset %s\n", anAsset.ID) + fmt.Println("Deleted asset", anAsset.ID) } } diff --git a/off_chain_data/application-go/utils/utils.go b/off_chain_data/application-go/utils/utils.go index 315ff4d6..940f6b2c 100644 --- a/off_chain_data/application-go/utils/utils.go +++ b/off_chain_data/application-go/utils/utils.go @@ -6,11 +6,13 @@ import ( "math/big" ) +// Pick a random element from an array. func RandomElement(values []string) string { result := values[RandomInt(len(values))] return result } +// Generate a random integer in the range 0 to max - 1. func RandomInt(max int) int { result, err := rand.Int(rand.Reader, big.NewInt(int64(max))) if err != nil { @@ -20,6 +22,7 @@ func RandomInt(max int) int { return int(result.Int64()) } +// Pick a random element from an array, excluding the current value. func DifferentElement(values []string, currentValue string) string { candidateValues := []string{} for _, v := range values { @@ -30,6 +33,7 @@ func DifferentElement(values []string, currentValue string) string { return RandomElement(candidateValues) } +// Return the value if it is defined; otherwise panics with an error message. func AssertDefined[T any](value T, message string) T { if any(value) == any(nil) { panic(errors.New(message)) @@ -37,3 +41,17 @@ func AssertDefined[T any](value T, message string) T { return value } + +// Wrap a function call with a cache. On first call the wrapped function is invoked to +// obtain a result. Subsequent calls return the cached result. +func Cache[T any](f func() T) func() T { + value := any(nil) + + return func() T { + if value == nil { + value = f() + } + + return value.(T) + } +} diff --git a/off_chain_data/application-go/utils/utils_test.go b/off_chain_data/application-go/utils/utils_test.go new file mode 100644 index 00000000..2f8d4b38 --- /dev/null +++ b/off_chain_data/application-go/utils/utils_test.go @@ -0,0 +1,64 @@ +package utils_test + +import ( + "offChainData/utils" + "testing" +) + +func Test_cachePrimitiveFunctionResult(t *testing.T) { + counter := 0 + f := func() int { + counter++ + return 5 + } + + cachedFunc := utils.Cache(f) + result1 := cachedFunc() + result2 := cachedFunc() + + if counter != 1 { + t.Error("expected counter to be 1, but got", counter) + } + + if result1 != 5 || result2 != 5 { + t.Fatal("expected results to be 5, but got", result1, result2) + } +} + +func Test_cacheWrappedPrimitiveFunctionResult(t *testing.T) { + controlValue := 5 + multiplyControlValueBy := func(n int) int { controlValue *= n; return controlValue } + + cachedFunc := utils.Cache(func() int { return multiplyControlValueBy(5) }) + result1 := cachedFunc() + result2 := cachedFunc() + + if controlValue != 25 { + t.Error("expected control value to be 25, but got", controlValue) + } + + if result1 != 25 || result2 != 25 { + t.Fatal("expected cached results to be 25, but got", result1, result2) + } +} + +func Test_cacheWrappedDataStructureResult(t *testing.T) { + type GreetMe struct { + helloTo string + } + + controlStruct := &GreetMe{helloTo: "Hello "} + greet := func(name string) *GreetMe { controlStruct.helloTo += name; return controlStruct } + + cachedFunc := utils.Cache(func() *GreetMe { return greet("John Doe") }) + result1 := cachedFunc().helloTo + result2 := cachedFunc().helloTo + + if controlStruct.helloTo != "Hello John Doe" { + t.Error("expected control value to be 'Hello John Doe', but got", controlStruct) + } + + if result1 != "Hello John Doe" || result2 != "Hello John Doe" { + t.Fatal("expected cached results to be 'Hello John Doe', but got", result1, result2) + } +} diff --git a/off_chain_data/application-typescript/src/utils.ts b/off_chain_data/application-typescript/src/utils.ts index e798a201..756b2e3a 100644 --- a/off_chain_data/application-typescript/src/utils.ts +++ b/off_chain_data/application-typescript/src/utils.ts @@ -67,7 +67,7 @@ export function assertDefined(value: T | null | undefined, message: string): */ export function cache(f: () => T): () => T { let value: T | undefined; - return () => { + return (): T => { if (value === undefined) { value = f(); }