Skip to content

Commit

Permalink
Add off-chain-data go client application
Browse files Browse the repository at this point in the history
Created project structure, fixed typos. Implemented connect.go and
getAllAssets.go. The latter uses an assetTransferBasic struct which
provides a simple API for basic asset operations like create, transfer,
etc.

Added transact.go with some util functions. Using google uuid package to
generate random UUIDs for the transactions.

Implemented pretty printing of JSON results.

Implemented app.go entry point with error handling. The existing
commands are getAllAssets, transact and listen. They can be called from
the command line via: "go run . <command> <command> ...". They will be
executed in order and if a command is not known an the application
panics and aborts before executing any of the commands.

Implementing listen.go. Added checkpointer, context setups, call to
BlockEvents and all the interfaces needed for parsing. Started
implementing the interfaces needed to represent a block bottom up in
structs. Finished NamespaceReadWriteSet, ReadWriteSet and
EndorserTransaction.

Signed-off-by: Stanislav Jakuschevskij <[email protected]>
  • Loading branch information
twoGiants committed Nov 30, 2024
1 parent 30b6186 commit c21a925
Show file tree
Hide file tree
Showing 12 changed files with 851 additions and 5 deletions.
6 changes: 3 additions & 3 deletions asset-transfer-private-data/application-gateway-go/connect.go
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
Copyright 2022 IBM All Rights Reserved.
Copyright 2024 IBM All Rights Reserved.
SPDX-License-Identifier: Apache-2.0
*/
Expand Down Expand Up @@ -77,8 +77,8 @@ func newIdentity(certDirectoryPath, mspId string) *identity.X509Identity {
}

// newSign creates a function that generates a digital signature from a message digest using a private key.
func newSign(keyDirectoryPash string) identity.Sign {
privateKeyPEM, err := readFirstFile(keyDirectoryPash)
func newSign(keyDirectoryPath string) identity.Sign {
privateKeyPEM, err := readFirstFile(keyDirectoryPath)
if err != nil {
panic(fmt.Errorf("failed to read private key file: %w", err))
}
Expand Down
4 changes: 2 additions & 2 deletions off_chain_data/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ The client application provides several "commands" that can be invoked using the

To keep the sample code concise, the **listen** command writes ledger updates to an output file named `store.log` in the current working directory (which for the Java sample is the `application-java/app` directory). A real implementation could write ledger updates directly to an off-chain data store of choice. You can inspect the information captured in this file as you run the sample.

Note that the **listen** command is is restartable and will resume event listening after the last successfully processed block / transaction. This is achieved using a checkpointer to persist the current listening position. Checkpoint state is persisted to a file named `checkpoint.json` in the current working directory. If no checkpoint state is present, event listening begins from the start of the ledger (block number zero).
Note that the **listen** command is restartable and will resume event listening after the last successfully processed block / transaction. This is achieved using a checkpointer to persist the current listening position. Checkpoint state is persisted to a file named `checkpoint.json` in the current working directory. If no checkpoint state is present, event listening begins from the start of the ledger (block number zero).

### Smart Contract

Expand Down Expand Up @@ -112,4 +112,4 @@ When you are finished, you can bring down the test network (from the `test-netwo

```
./network.sh down
```
```
62 changes: 62 additions & 0 deletions off_chain_data/application-go/app.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
/*
* Copyright 2024 IBM All Rights Reserved.
*
* SPDX-License-Identifier: Apache-2.0
*/

package main

import (
"errors"
"fmt"
"os"
"strings"

"google.golang.org/grpc"
)

var allCommands = map[string]func(clientConnection *grpc.ClientConn){
"getAllAssets": getAllAssets,
"transact": transact,
"listen": listen,
}

func main() {
commands := os.Args[1:]
if len(commands) == 0 {
printUsage()
panic(errors.New("missing command"))
}

for _, name := range commands {
if _, exists := allCommands[name]; !exists {
printUsage()
panic(fmt.Errorf("unknown command: %s", name))
}
fmt.Printf("command: %s\n", name)
}

client := newGrpcConnection()
defer client.Close()

for _, name := range commands {
command := allCommands[name]
command(client)
}
}

func printUsage() {
fmt.Println("Arguments: <command1> [<command2> ...]")
fmt.Printf("Available commands: %v\n", availableCommands())
}

func availableCommands() string {
result := make([]string, len(allCommands))
i := 0
for command := range allCommands {
result[i] = command
i++
}

return strings.Join(result, ", ")
}
277 changes: 277 additions & 0 deletions off_chain_data/application-go/blockParser.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,277 @@
package main

import (
"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/peer"
"google.golang.org/protobuf/proto"
)

type Block interface {
GetNumber() uint64
GetTransactions() []Transaction
ToProto() *common.Block
}

type Transaction interface {
GetChannelHeader() *common.ChannelHeader
GetCreator() identity.Identity
GetValidationCode() uint64
IsValid() bool
GetNamespaceReadWriteSets() []NamespaceReadWriteSet
ToProto() *common.Payload
}

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
}

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() uint64
IsEndorserTransaction() bool
IsValid() bool
ToProto() *common.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",
)
}
Loading

0 comments on commit c21a925

Please sign in to comment.