Skip to content

Commit 6a5e5dd

Browse files
authored
Add off-chain-data go client application (#1269)
Signed-off-by: Stanislav Jakuschevskij <[email protected]>
1 parent aa0c9d3 commit 6a5e5dd

File tree

24 files changed

+1434
-10
lines changed

24 files changed

+1434
-10
lines changed

asset-transfer-basic/chaincode-javascript/lib/assetTransfer.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -79,9 +79,9 @@ class AssetTransfer extends Contract {
7979
const asset = {
8080
ID: id,
8181
Color: color,
82-
Size: size,
82+
Size: Number(size),
8383
Owner: owner,
84-
AppraisedValue: appraisedValue,
84+
AppraisedValue: Number(appraisedValue),
8585
};
8686
// we insert data in alphabetic order using 'json-stringify-deterministic' and 'sort-keys-recursive'
8787
await ctx.stub.putState(id, Buffer.from(stringify(sortKeysRecursive(asset))));

asset-transfer-events/application-gateway-go/connect.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ const (
3131
func newGrpcConnection() *grpc.ClientConn {
3232
certificatePEM, err := os.ReadFile(tlsCertPath)
3333
if err != nil {
34-
panic(fmt.Errorf("failed to read TLS certifcate file: %w", err))
34+
panic(fmt.Errorf("failed to read TLS certificate file: %w", err))
3535
}
3636

3737
certificate, err := identity.CertificateFromPEM(certificatePEM)

asset-transfer-private-data/application-gateway-go/connect.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
Copyright 2022 IBM All Rights Reserved.
2+
Copyright 2024 IBM All Rights Reserved.
33
44
SPDX-License-Identifier: Apache-2.0
55
*/
@@ -77,8 +77,8 @@ func newIdentity(certDirectoryPath, mspId string) *identity.X509Identity {
7777
}
7878

7979
// newSign creates a function that generates a digital signature from a message digest using a private key.
80-
func newSign(keyDirectoryPash string) identity.Sign {
81-
privateKeyPEM, err := readFirstFile(keyDirectoryPash)
80+
func newSign(keyDirectoryPath string) identity.Sign {
81+
privateKeyPEM, err := readFirstFile(keyDirectoryPath)
8282
if err != nil {
8383
panic(fmt.Errorf("failed to read private key file: %w", err))
8484
}

ci/scripts/run-test-network-off-chain.sh

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,11 +41,22 @@ print "Initializing Typescript off-chain data application"
4141
pushd ../off_chain_data/application-typescript
4242
rm -f checkpoint.json store.log
4343
npm install
44-
print "Running the output app"
44+
print "Running the Typescript app"
4545
SIMULATED_FAILURE_COUNT=1 npm start getAllAssets transact getAllAssets listen
4646
SIMULATED_FAILURE_COUNT=1 npm start listen
4747
popd
4848

49+
# Run off-chain data Go application
50+
export CHAINCODE_NAME=go_off_chain_data
51+
deployChaincode
52+
print "Initializing Go off-chain data application"
53+
pushd ../off_chain_data/application-go
54+
rm -f checkpoint.json store.log
55+
print "Running the Go app"
56+
SIMULATED_FAILURE_COUNT=1 go run . getAllAssets transact getAllAssets listen
57+
SIMULATED_FAILURE_COUNT=1 go run . listen
58+
popd
59+
4960
# Run off-chain data Java application
5061
#createNetwork
5162
export CHAINCODE_NAME=off_chain_data

off_chain_data/README.md

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,16 +19,19 @@ The client application provides several "commands" that can be invoked using the
1919
- **getAllAssets**: Retrieve the current details of all assets recorded on the ledger. See:
2020
- TypeScript: [application-typescript/src/getAllAssets.ts](application-typescript/src/getAllAssets.ts)
2121
- Java: [application-java/app/src/main/java/GetAllAssets.java](application-java/app/src/main/java/GetAllAssets.java)
22+
- Go: [application-go/getAllAssets.go](application-go/getAllAssets.go)
2223
- **listen**: Listen for block events, and use them to replicate ledger updates in an off-chain data store. See:
2324
- TypeScript: [application-typescript/src/listen.ts](application-typescript/src/listen.ts)
2425
- Java: [application-java/app/src/main/java/Listen.java](application-java/app/src/main/java/Listen.java)
26+
- Go: [application-go/listen.go](application-go/listen.go)
2527
- **transact**: Submit a set of transactions to create, modify and delete assets. See:
2628
- TypeScript: [application-typescript/src/transact.ts](application-typescript/src/transact.ts)
2729
- Java: [application-java/app/src/main/java/Transact.java](application-java/app/src/main/java/Transact.java)
30+
- Go: [application-go/transact.go](application-go/transact.go)
2831

2932
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.
3033

31-
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).
34+
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).
3235

3336
### Smart Contract
3437

@@ -65,6 +68,10 @@ The Fabric test network is used to deploy and run this sample. Follow these step
6568
npm install
6669
npm start transact listen
6770

71+
# To run the Go sample application
72+
cd application-go
73+
go run . transact listen
74+
6875
# To run the Java sample application
6976
cd application-java
7077
./gradlew run --quiet --args='transact listen'
@@ -79,6 +86,10 @@ The Fabric test network is used to deploy and run this sample. Follow these step
7986
cd application-typescript
8087
npm --silent start getAllAssets
8188

89+
# To run the Go sample application
90+
cd application-go
91+
go run . getAllAssets
92+
8293
# To run the Java sample application
8394
cd application-java
8495
./gradlew run --quiet --args=getAllAssets
@@ -93,6 +104,12 @@ The Fabric test network is used to deploy and run this sample. Follow these step
93104
SIMULATED_FAILURE_COUNT=5 npm start listen
94105
npm start listen
95106

107+
# To run the Go sample application
108+
cd application-go
109+
go run . transact
110+
SIMULATED_FAILURE_COUNT=5 go run . listen
111+
go run . listen
112+
96113
# To run the Java sample application
97114
cd application-java
98115
./gradlew run --quiet --args=transact
@@ -112,4 +129,4 @@ When you are finished, you can bring down the test network (from the `test-netwo
112129

113130
```
114131
./network.sh down
115-
```
132+
```
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
package main
2+
3+
import (
4+
"errors"
5+
"fmt"
6+
"os"
7+
"strings"
8+
9+
"google.golang.org/grpc"
10+
)
11+
12+
type command func(grpc.ClientConnInterface) error
13+
14+
var allCommands = map[string]command{
15+
"getAllAssets": getAllAssets,
16+
"transact": transact,
17+
"listen": listen,
18+
}
19+
20+
func main() {
21+
commands := os.Args[1:]
22+
if len(commands) == 0 {
23+
printUsage()
24+
panic(errors.New("missing command"))
25+
}
26+
27+
for _, name := range commands {
28+
if _, exists := allCommands[name]; !exists {
29+
printUsage()
30+
panic(fmt.Errorf("unknown command: %s", name))
31+
}
32+
fmt.Println("command:", name)
33+
}
34+
35+
client := newGrpcConnection()
36+
defer client.Close()
37+
38+
for _, name := range commands {
39+
command := allCommands[name]
40+
41+
if err := command(client); err != nil {
42+
if errors.Is(err, errExpected) {
43+
fmt.Println(err)
44+
return
45+
}
46+
47+
panic(err)
48+
}
49+
}
50+
}
51+
52+
func printUsage() {
53+
fmt.Println("Arguments: <command1> [<command2> ...]")
54+
fmt.Println("Available commands:", availableCommands())
55+
}
56+
57+
func availableCommands() string {
58+
result := make([]string, len(allCommands))
59+
i := 0
60+
for command := range allCommands {
61+
result[i] = command
62+
i++
63+
}
64+
65+
return strings.Join(result, ", ")
66+
}
Lines changed: 128 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,128 @@
1+
package main
2+
3+
import (
4+
"crypto/x509"
5+
"fmt"
6+
"os"
7+
"path"
8+
"time"
9+
10+
"github.com/hyperledger/fabric-gateway/pkg/client"
11+
"github.com/hyperledger/fabric-gateway/pkg/hash"
12+
"github.com/hyperledger/fabric-gateway/pkg/identity"
13+
"google.golang.org/grpc"
14+
"google.golang.org/grpc/credentials"
15+
)
16+
17+
const peerName = "peer0.org1.example.com"
18+
19+
var (
20+
channelName = envOrDefault("CHANNEL_NAME", "mychannel")
21+
chaincodeName = envOrDefault("CHAINCODE_NAME", "basic")
22+
mspID = envOrDefault("MSP_ID", "Org1MSP")
23+
24+
// Path to crypto materials.
25+
cryptoPath = envOrDefault("CRYPTO_PATH", "../../test-network/organizations/peerOrganizations/org1.example.com")
26+
27+
// Path to user private key directory.
28+
keyDirectoryPath = envOrDefault("KEY_DIRECTORY_PATH", cryptoPath+"/users/[email protected]/msp/keystore")
29+
30+
// Path to user certificate.
31+
certPath = envOrDefault("CERT_PATH", cryptoPath+"/users/[email protected]/msp/signcerts/cert.pem")
32+
33+
// Path to peer tls certificate.
34+
tlsCertPath = envOrDefault("TLS_CERT_PATH", cryptoPath+"/peers/peer0.org1.example.com/tls/ca.crt")
35+
36+
// Gateway peer endpoint.
37+
peerEndpoint = envOrDefault("PEER_ENDPOINT", "dns:///localhost:7051")
38+
39+
// Gateway peer SSL host name override.
40+
peerHostAlias = envOrDefault("PEER_HOST_ALIAS", peerName)
41+
)
42+
43+
func newGrpcConnection() *grpc.ClientConn {
44+
certificatePEM, err := os.ReadFile(tlsCertPath)
45+
if err != nil {
46+
panic(fmt.Errorf("failed to read TLS certificate file: %w", err))
47+
}
48+
49+
certificate, err := identity.CertificateFromPEM(certificatePEM)
50+
if err != nil {
51+
panic(err)
52+
}
53+
54+
certPool := x509.NewCertPool()
55+
certPool.AddCert(certificate)
56+
transportCredentials := credentials.NewClientTLSFromCert(certPool, peerHostAlias)
57+
58+
connection, err := grpc.NewClient(peerEndpoint, grpc.WithTransportCredentials(transportCredentials))
59+
if err != nil {
60+
panic(fmt.Errorf("failed to create gRPC connection: %w", err))
61+
}
62+
63+
return connection
64+
}
65+
66+
func newConnectOptions(clientConnection grpc.ClientConnInterface) (identity.Identity, []client.ConnectOption) {
67+
return newIdentity(), []client.ConnectOption{
68+
client.WithSign(newSign()),
69+
client.WithHash(hash.SHA256),
70+
client.WithClientConnection(clientConnection),
71+
client.WithEvaluateTimeout(5 * time.Second),
72+
client.WithEndorseTimeout(15 * time.Second),
73+
client.WithSubmitTimeout(5 * time.Second),
74+
client.WithCommitStatusTimeout(1 * time.Minute),
75+
}
76+
}
77+
78+
func newIdentity() *identity.X509Identity {
79+
certificatePEM, err := os.ReadFile(certPath)
80+
if err != nil {
81+
panic(fmt.Errorf("failed to read certificate file: %w", err))
82+
}
83+
84+
certificate, err := identity.CertificateFromPEM(certificatePEM)
85+
if err != nil {
86+
panic(err)
87+
}
88+
89+
id, err := identity.NewX509Identity(mspID, certificate)
90+
if err != nil {
91+
panic(err)
92+
}
93+
94+
return id
95+
}
96+
97+
func newSign() identity.Sign {
98+
privateKeyPEM, err := readFirstFile(keyDirectoryPath)
99+
if err != nil {
100+
panic(fmt.Errorf("failed to read private key file: %w", err))
101+
}
102+
103+
privateKey, err := identity.PrivateKeyFromPEM(privateKeyPEM)
104+
if err != nil {
105+
panic(err)
106+
}
107+
108+
sign, err := identity.NewPrivateKeySign(privateKey)
109+
if err != nil {
110+
panic(err)
111+
}
112+
113+
return sign
114+
}
115+
116+
func readFirstFile(dirPath string) ([]byte, error) {
117+
dir, err := os.Open(dirPath)
118+
if err != nil {
119+
return nil, err
120+
}
121+
122+
fileNames, err := dir.Readdirnames(1)
123+
if err != nil {
124+
return nil, err
125+
}
126+
127+
return os.ReadFile(path.Join(dirPath, fileNames[0]))
128+
}
Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
package contract
2+
3+
import (
4+
"encoding/json"
5+
"strconv"
6+
7+
"github.com/hyperledger/fabric-gateway/pkg/client"
8+
)
9+
10+
type AssetTransferBasic struct {
11+
contract *client.Contract
12+
}
13+
14+
func NewAssetTransferBasic(contract *client.Contract) *AssetTransferBasic {
15+
return &AssetTransferBasic{contract}
16+
}
17+
18+
func (atb *AssetTransferBasic) CreateAsset(anAsset Asset) error {
19+
if _, err := atb.contract.Submit(
20+
"CreateAsset",
21+
client.WithArguments(
22+
anAsset.ID,
23+
anAsset.Color,
24+
strconv.FormatUint(anAsset.Size, 10),
25+
anAsset.Owner,
26+
strconv.FormatUint(anAsset.AppraisedValue, 10),
27+
)); err != nil {
28+
return err
29+
}
30+
return nil
31+
}
32+
33+
func (atb *AssetTransferBasic) TransferAsset(id, newOwner string) (string, error) {
34+
result, err := atb.contract.Submit(
35+
"TransferAsset",
36+
client.WithArguments(
37+
id,
38+
newOwner,
39+
),
40+
)
41+
if err != nil {
42+
return "", err
43+
}
44+
45+
return string(result), nil
46+
}
47+
48+
func (atb *AssetTransferBasic) DeleteAsset(id string) error {
49+
if _, err := atb.contract.Submit(
50+
"DeleteAsset",
51+
client.WithArguments(
52+
id,
53+
),
54+
); err != nil {
55+
return err
56+
}
57+
return nil
58+
}
59+
60+
func (atb *AssetTransferBasic) GetAllAssets() ([]Asset, error) {
61+
assetsRaw, err := atb.contract.Evaluate("GetAllAssets")
62+
if err != nil {
63+
return nil, err
64+
}
65+
66+
if len(assetsRaw) == 0 {
67+
return nil, nil
68+
}
69+
70+
assets := []Asset{}
71+
if err := json.Unmarshal(assetsRaw, &assets); err != nil {
72+
return nil, err
73+
}
74+
75+
return assets, nil
76+
}

0 commit comments

Comments
 (0)