-
Notifications
You must be signed in to change notification settings - Fork 3.4k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add off-chain-data go client application
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
Showing
12 changed files
with
851 additions
and
5 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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, ", ") | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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", | ||
) | ||
} |
Oops, something went wrong.