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 <stas@two-giants.com>
This commit is contained in:
Stanislav Jakuschevskij 2024-12-30 20:41:18 +01:00
parent 095bf304b4
commit c4db079e0c
No known key found for this signature in database
GPG key ID: 78195D2E6998E2EB
12 changed files with 158 additions and 73 deletions

View file

@ -33,7 +33,7 @@ func main() {
printUsage() printUsage()
panic(fmt.Errorf("unknown command: %s", name)) panic(fmt.Errorf("unknown command: %s", name))
} }
fmt.Printf("command: %s\n", name) fmt.Println("command:", name)
} }
client := newGrpcConnection() client := newGrpcConnection()
@ -47,7 +47,7 @@ func main() {
func printUsage() { func printUsage() {
fmt.Println("Arguments: <command1> [<command2> ...]") fmt.Println("Arguments: <command1> [<command2> ...]")
fmt.Printf("Available commands: %v\n", availableCommands()) fmt.Println("Available commands:", availableCommands())
} }
func availableCommands() string { func availableCommands() string {

View file

@ -28,7 +28,7 @@ func getAllAssets(clientConnection *grpc.ClientConn) {
smartContract := atb.NewAssetTransferBasic(contract) smartContract := atb.NewAssetTransferBasic(contract)
assets := smartContract.GetAllAssets() assets := smartContract.GetAllAssets()
fmt.Printf("%s\n", formatJSON(assets)) fmt.Println(formatJSON(assets))
} }
func formatJSON(data []byte) string { func formatJSON(data []byte) string {

View file

@ -108,8 +108,8 @@ func listen(clientConnection *grpc.ClientConn) {
} }
defer checkpointer.Close() defer checkpointer.Close()
fmt.Printf("Start event listening from block %d\n", checkpointer.BlockNumber()) fmt.Println("Start event listening from block", checkpointer.BlockNumber())
fmt.Printf("Last processed transaction ID within block: %s\n", checkpointer.TransactionID()) fmt.Println("Last processed transaction ID within block:", checkpointer.TransactionID())
if simulatedFailureCount > 0 { if simulatedFailureCount > 0 {
fmt.Printf("Simulating a write failure every %d transactions\n", simulatedFailureCount) fmt.Printf("Simulating a write failure every %d transactions\n", simulatedFailureCount)
} }

View file

@ -22,23 +22,38 @@ func (b *Block) Number() uint64 {
return header.GetNumber() return header.GetNumber()
} }
// TODO: needs cache
func (b *Block) Transactions() []*Transaction { 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 return result
} }
func (*Block) createTransactionsFrom(payloads []*PayloadImpl) []*Transaction { func (*Block) unmarshalPayloadsFrom(envelopes []*common.Envelope) []*common.Payload {
result := []*Transaction{} result := []*common.Payload{}
for _, payload := range payloads { for _, envelope := range envelopes {
result = append(result, NewTransaction(payload)) commonPayload := &common.Payload{}
if err := proto.Unmarshal(envelope.GetPayload(), commonPayload); err != nil {
panic(err)
}
result = append(result, commonPayload)
} }
return result return result
} }
@ -62,30 +77,6 @@ func (b *Block) parse(commonPayloads []*common.Payload) []*PayloadImpl {
return result 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 { func (b *Block) extractTransactionValidationCodes() []byte {
metadata := utils.AssertDefined( metadata := utils.AssertDefined(
b.block.GetMetadata(), 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? // TODO remove unused?
func (b *Block) ToProto() *common.Block { func (b *Block) ToProto() *common.Block {
return b.block return b.block

View file

@ -14,7 +14,7 @@ import (
"google.golang.org/protobuf/reflect/protoreflect" "google.golang.org/protobuf/reflect/protoreflect"
) )
func TestGetReadWriteSetsFromEndorserTransaction(t *testing.T) { func Test_GetReadWriteSetsFromEndorserTransaction(t *testing.T) {
nsReadWriteSetFake, expectedNamespace, expectedAsset := nsReadWriteSetFake() nsReadWriteSetFake, expectedNamespace, expectedAsset := nsReadWriteSetFake()
transaction := &peer.Transaction{ transaction := &peer.Transaction{
@ -73,7 +73,7 @@ func assertReadWriteSet(
} }
} }
func TestReadWriteSetWrapping(t *testing.T) { func Test_ReadWriteSetWrapping(t *testing.T) {
nsReadWriteSetFake, _, _ := nsReadWriteSetFake() nsReadWriteSetFake, _, _ := nsReadWriteSetFake()
txReadWriteSetFake := &rwset.TxReadWriteSet{ 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() nsReadWriteSetFake, expectedNamespace, expectedAsset := nsReadWriteSetFake()
parsedNsRwSet := parser.ParseNamespaceReadWriteSet(nsReadWriteSetFake) parsedNsRwSet := parser.ParseNamespaceReadWriteSet(nsReadWriteSetFake)

View file

@ -30,15 +30,16 @@ func ParsePayload(payload *common.Payload, statusCode int32) *PayloadImpl {
} }
func (p *PayloadImpl) ChannelHeader() *common.ChannelHeader { 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{}
result := &common.ChannelHeader{} if err := proto.Unmarshal(header.GetChannelHeader(), result); err != nil {
if err := proto.Unmarshal(header.GetChannelHeader(), result); err != nil { panic(err)
panic(err) }
}
return result return result
})()
} }
func (p *PayloadImpl) EndorserTransaction() EndorserTransaction { func (p *PayloadImpl) EndorserTransaction() EndorserTransaction {
@ -55,15 +56,16 @@ func (p *PayloadImpl) EndorserTransaction() EndorserTransaction {
} }
func (p *PayloadImpl) SignatureHeader() *common.SignatureHeader { 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{}
result := &common.SignatureHeader{} if err := proto.Unmarshal(header.GetSignatureHeader(), result); err != nil {
if err := proto.Unmarshal(header.GetSignatureHeader(), result); err != nil { panic(err)
panic(err) }
}
return result return result
})()
} }
func (p *PayloadImpl) TransactionValidationCode() int32 { func (p *PayloadImpl) TransactionValidationCode() int32 {

View file

@ -1,6 +1,8 @@
package parser package parser
import ( import (
"offChainData/utils"
"github.com/hyperledger/fabric-protos-go-apiv2/ledger/rwset" "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/ledger/rwset/kvrwset"
"google.golang.org/protobuf/proto" "google.golang.org/protobuf/proto"
@ -53,14 +55,15 @@ func (p *NamespaceReadWriteSetImpl) Namespace() string {
return p.nsReadWriteSet.GetNamespace() return p.nsReadWriteSet.GetNamespace()
} }
// TODO add cache
func (p *NamespaceReadWriteSetImpl) ReadWriteSet() *kvrwset.KVRWSet { func (p *NamespaceReadWriteSetImpl) ReadWriteSet() *kvrwset.KVRWSet {
result := kvrwset.KVRWSet{} return utils.Cache(func() *kvrwset.KVRWSet {
if err := proto.Unmarshal(p.nsReadWriteSet.GetRwset(), &result); err != nil { result := kvrwset.KVRWSet{}
panic(err) if err := proto.Unmarshal(p.nsReadWriteSet.GetRwset(), &result); err != nil {
} panic(err)
}
return &result return &result
})()
} }
// TODO remove unused // TODO remove unused

View file

@ -71,21 +71,20 @@ func ParseEndorserTransaction(transaction *peer.Transaction) *EndorserTransactio
return &EndorserTransactionImpl{transaction} return &EndorserTransactionImpl{transaction}
} }
// TODO add cache
func (p *EndorserTransactionImpl) ReadWriteSets() []ReadWriteSet { 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 p.parseReadWriteSets(txReadWriteSets)
})()
return parsedReadWriteSets
} }
func (p *EndorserTransactionImpl) unmarshalChaincodeActionPayloads() []*peer.ChaincodeActionPayload { func (p *EndorserTransactionImpl) unmarshalChaincodeActionPayloads() []*peer.ChaincodeActionPayload {

View file

@ -44,7 +44,7 @@ func (t *transactApp) transact() {
anAsset := atb.NewAsset() anAsset := atb.NewAsset()
t.smartContract.CreateAsset(anAsset) 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. // Transfer randomly 1 in 2 assets to a new owner.
if utils.RandomInt(2) == 0 { if utils.RandomInt(2) == 0 {
@ -56,6 +56,6 @@ func (t *transactApp) transact() {
// Delete randomly 1 in 4 created assets. // Delete randomly 1 in 4 created assets.
if utils.RandomInt(4) == 0 { if utils.RandomInt(4) == 0 {
t.smartContract.DeleteAsset(anAsset.ID) t.smartContract.DeleteAsset(anAsset.ID)
fmt.Printf("Deleted asset %s\n", anAsset.ID) fmt.Println("Deleted asset", anAsset.ID)
} }
} }

View file

@ -6,11 +6,13 @@ import (
"math/big" "math/big"
) )
// Pick a random element from an array.
func RandomElement(values []string) string { func RandomElement(values []string) string {
result := values[RandomInt(len(values))] result := values[RandomInt(len(values))]
return result return result
} }
// Generate a random integer in the range 0 to max - 1.
func RandomInt(max int) int { func RandomInt(max int) int {
result, err := rand.Int(rand.Reader, big.NewInt(int64(max))) result, err := rand.Int(rand.Reader, big.NewInt(int64(max)))
if err != nil { if err != nil {
@ -20,6 +22,7 @@ func RandomInt(max int) int {
return int(result.Int64()) return int(result.Int64())
} }
// Pick a random element from an array, excluding the current value.
func DifferentElement(values []string, currentValue string) string { func DifferentElement(values []string, currentValue string) string {
candidateValues := []string{} candidateValues := []string{}
for _, v := range values { for _, v := range values {
@ -30,6 +33,7 @@ func DifferentElement(values []string, currentValue string) string {
return RandomElement(candidateValues) 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 { func AssertDefined[T any](value T, message string) T {
if any(value) == any(nil) { if any(value) == any(nil) {
panic(errors.New(message)) panic(errors.New(message))
@ -37,3 +41,17 @@ func AssertDefined[T any](value T, message string) T {
return value 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)
}
}

View file

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

View file

@ -67,7 +67,7 @@ export function assertDefined<T>(value: T | null | undefined, message: string):
*/ */
export function cache<T>(f: () => T): () => T { export function cache<T>(f: () => T): () => T {
let value: T | undefined; let value: T | undefined;
return () => { return (): T => {
if (value === undefined) { if (value === undefined) {
value = f(); value = f();
} }