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()
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: <command1> [<command2> ...]")
fmt.Printf("Available commands: %v\n", availableCommands())
fmt.Println("Available commands:", availableCommands())
}
func availableCommands() string {

View file

@ -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 {

View file

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

View file

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

View file

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

View file

@ -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 {

View file

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

View file

@ -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 {

View file

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

View file

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

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 {
let value: T | undefined;
return () => {
return (): T => {
if (value === undefined) {
value = f();
}