Skip to content

Commit 09402bc

Browse files
committed
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. Signed-off-by: Stanislav Jakuschevskij <stas@two-giants.com>
1 parent 30b6186 commit 09402bc

File tree

10 files changed

+406
-5
lines changed

10 files changed

+406
-5
lines changed

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
}

off_chain_data/README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ The client application provides several "commands" that can be invoked using the
2828

2929
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.
3030

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

3333
### Smart Contract
3434

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

113113
```
114114
./network.sh down
115-
```
115+
```
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
/*
2+
* Copyright 2024 IBM All Rights Reserved.
3+
*
4+
* SPDX-License-Identifier: Apache-2.0
5+
*/
6+
7+
package main
8+
9+
func main() {
10+
client := newGrpcConnection()
11+
defer client.Close()
12+
13+
getAllAssets(client)
14+
}
Lines changed: 142 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,142 @@
1+
/*
2+
* Copyright 2024 IBM All Rights Reserved.
3+
*
4+
* SPDX-License-Identifier: Apache-2.0
5+
*/
6+
7+
package main
8+
9+
import (
10+
"crypto/x509"
11+
"fmt"
12+
"os"
13+
"path"
14+
"time"
15+
16+
"github.com/hyperledger/fabric-gateway/pkg/client"
17+
"github.com/hyperledger/fabric-gateway/pkg/hash"
18+
"github.com/hyperledger/fabric-gateway/pkg/identity"
19+
"google.golang.org/grpc"
20+
"google.golang.org/grpc/credentials"
21+
)
22+
23+
const peerName = "peer0.org1.example.com"
24+
25+
var (
26+
channelName = envOrDefault("CHANNEL_NAME", "mychannel")
27+
chaincodeName = envOrDefault("CHAINCODE_NAME", "basic")
28+
mspID = envOrDefault("MSP_ID", "Org1MSP")
29+
30+
// Path to crypto materials.
31+
cryptoPath = envOrDefault("CRYPTO_PATH", "../../test-network/organizations/peerOrganizations/org1.example.com")
32+
33+
// Path to user private key directory.
34+
keyDirectoryPath = envOrDefault("KEY_DIRECTORY_PATH", cryptoPath+"/users/User1@org1.example.com/msp/keystore")
35+
36+
// Path to user certificate.
37+
certPath = envOrDefault("CERT_PATH", cryptoPath+"/users/User1@org1.example.com/msp/signcerts/cert.pem")
38+
39+
// Path to peer tls certificate.
40+
tlsCertPath = envOrDefault("TLS_CERT_PATH", cryptoPath+"/peers/peer0.org1.example.com/tls/ca.crt")
41+
42+
// Gateway peer endpoint.
43+
peerEndpoint = envOrDefault("PEER_ENDPOINT", "dns:///localhost:7051")
44+
45+
// Gateway peer SSL host name override.
46+
peerHostAlias = envOrDefault("PEER_HOST_ALIAS", peerName)
47+
)
48+
49+
func envOrDefault(key, defaultValue string) string {
50+
result := os.Getenv(key)
51+
if result == "" {
52+
return defaultValue
53+
}
54+
return result
55+
}
56+
57+
func newGrpcConnection() *grpc.ClientConn {
58+
certificatePEM, err := os.ReadFile(tlsCertPath)
59+
if err != nil {
60+
panic(fmt.Errorf("failed to read TLS certificate file: %w", err))
61+
}
62+
63+
certificate, err := identity.CertificateFromPEM(certificatePEM)
64+
if err != nil {
65+
panic(err)
66+
}
67+
68+
certPool := x509.NewCertPool()
69+
certPool.AddCert(certificate)
70+
transportCredentials := credentials.NewClientTLSFromCert(certPool, peerHostAlias)
71+
72+
connection, err := grpc.NewClient(peerEndpoint, grpc.WithTransportCredentials(transportCredentials))
73+
if err != nil {
74+
panic(fmt.Errorf("failed to create gRPC connection: %w", err))
75+
}
76+
77+
return connection
78+
}
79+
80+
func newConnectOptions(clientConnection *grpc.ClientConn) (identity.Identity, []client.ConnectOption) {
81+
return newIdentity(), []client.ConnectOption{
82+
client.WithSign(newSign()),
83+
client.WithHash(hash.SHA256),
84+
client.WithClientConnection(clientConnection),
85+
client.WithEvaluateTimeout(5 * time.Second),
86+
client.WithEndorseTimeout(15 * time.Second),
87+
client.WithSubmitTimeout(5 * time.Second),
88+
client.WithCommitStatusTimeout(1 * time.Minute),
89+
}
90+
}
91+
92+
func newIdentity() *identity.X509Identity {
93+
certificatePEM, err := os.ReadFile(certPath)
94+
if err != nil {
95+
panic(fmt.Errorf("failed to read certificate file: %w", err))
96+
}
97+
98+
certificate, err := identity.CertificateFromPEM(certificatePEM)
99+
if err != nil {
100+
panic(err)
101+
}
102+
103+
id, err := identity.NewX509Identity(mspID, certificate)
104+
if err != nil {
105+
panic(err)
106+
}
107+
108+
return id
109+
}
110+
111+
func newSign() identity.Sign {
112+
privateKeyPEM, err := readFirstFile(keyDirectoryPath)
113+
if err != nil {
114+
panic(fmt.Errorf("failed to read private key file: %w", err))
115+
}
116+
117+
privateKey, err := identity.PrivateKeyFromPEM(privateKeyPEM)
118+
if err != nil {
119+
panic(err)
120+
}
121+
122+
sign, err := identity.NewPrivateKeySign(privateKey)
123+
if err != nil {
124+
panic(err)
125+
}
126+
127+
return sign
128+
}
129+
130+
func readFirstFile(dirPath string) ([]byte, error) {
131+
dir, err := os.Open(dirPath)
132+
if err != nil {
133+
return nil, err
134+
}
135+
136+
fileNames, err := dir.Readdirnames(1)
137+
if err != nil {
138+
return nil, err
139+
}
140+
141+
return os.ReadFile(path.Join(dirPath, fileNames[0]))
142+
}
Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
/*
2+
* Copyright 2024 IBM All Rights Reserved.
3+
*
4+
* SPDX-License-Identifier: Apache-2.0
5+
*/
6+
7+
package main
8+
9+
import (
10+
"strconv"
11+
12+
"github.com/hyperledger/fabric-gateway/pkg/client"
13+
)
14+
15+
type asset struct {
16+
ID string
17+
Color string
18+
Size uint64
19+
Owner string
20+
AppraisedValue uint64
21+
}
22+
23+
type assetTransferBasic struct {
24+
contract *client.Contract
25+
}
26+
27+
func newAssetTransferBasic(contract *client.Contract) *assetTransferBasic {
28+
return &assetTransferBasic{contract}
29+
}
30+
31+
func (atb *assetTransferBasic) createAsset(anAsset asset) {
32+
if _, err := atb.contract.Submit(
33+
"CreateAsset",
34+
client.WithArguments(
35+
anAsset.ID,
36+
anAsset.Color,
37+
strconv.FormatUint(anAsset.Size, 10),
38+
anAsset.Owner,
39+
strconv.FormatUint(anAsset.AppraisedValue, 10),
40+
)); err != nil {
41+
panic(err)
42+
}
43+
}
44+
45+
func (atb *assetTransferBasic) transferAsset(id, newOwner string) string {
46+
result, err := atb.contract.Submit(
47+
"TransferAsset",
48+
client.WithArguments(
49+
id,
50+
newOwner,
51+
),
52+
)
53+
if err != nil {
54+
panic(err)
55+
}
56+
57+
return string(result)
58+
}
59+
60+
func (atb *assetTransferBasic) deleteAsset(id string) {
61+
if _, err := atb.contract.Submit(
62+
"DeleteAsset",
63+
client.WithArguments(
64+
id,
65+
),
66+
); err != nil {
67+
panic(err)
68+
}
69+
}
70+
71+
func (atb *assetTransferBasic) getAllAssets() []byte {
72+
result, err := atb.contract.Evaluate("GetAllAssets")
73+
if err != nil {
74+
panic(err)
75+
}
76+
return result
77+
}
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
/*
2+
* Copyright 2024 IBM All Rights Reserved.
3+
*
4+
* SPDX-License-Identifier: Apache-2.0
5+
*/
6+
7+
package main
8+
9+
import (
10+
"bytes"
11+
"encoding/json"
12+
"fmt"
13+
14+
"github.com/hyperledger/fabric-gateway/pkg/client"
15+
"google.golang.org/grpc"
16+
)
17+
18+
func getAllAssets(clientConnection *grpc.ClientConn) {
19+
id, options := newConnectOptions(clientConnection)
20+
gateway, err := client.Connect(id, options...)
21+
if err != nil {
22+
panic((err))
23+
}
24+
defer gateway.Close()
25+
26+
contract := gateway.GetNetwork(channelName).GetContract(chaincodeName)
27+
smartContract := newAssetTransferBasic(contract)
28+
assets := smartContract.getAllAssets()
29+
30+
fmt.Printf("%s\n", formatJSON(assets))
31+
}
32+
33+
func formatJSON(data []byte) string {
34+
var prettyJSON bytes.Buffer
35+
if err := json.Indent(&prettyJSON, data, "", " "); err != nil {
36+
panic(fmt.Errorf("failed to parse JSON: %w", err))
37+
}
38+
return prettyJSON.String()
39+
}
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
module offChainData
2+
3+
go 1.22.0
4+
5+
require (
6+
github.com/hyperledger/fabric-gateway v1.7.0
7+
google.golang.org/grpc v1.67.1
8+
)
9+
10+
require (
11+
github.com/google/uuid v1.6.0 // indirect
12+
github.com/hyperledger/fabric-protos-go-apiv2 v0.3.4 // indirect
13+
github.com/miekg/pkcs11 v1.1.1 // indirect
14+
golang.org/x/crypto v0.28.0 // indirect
15+
golang.org/x/net v0.28.0 // indirect
16+
golang.org/x/sys v0.26.0 // indirect
17+
golang.org/x/text v0.19.0 // indirect
18+
google.golang.org/genproto/googleapis/rpc v0.0.0-20240814211410-ddb44dafa142 // indirect
19+
google.golang.org/protobuf v1.35.1 // indirect
20+
)
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
2+
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
3+
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
4+
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
5+
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
6+
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
7+
github.com/hyperledger/fabric-gateway v1.7.0 h1:bd1quU8qYPYqYO69m1tPIDSjB+D+u/rBJfE1eWFcpjY=
8+
github.com/hyperledger/fabric-gateway v1.7.0/go.mod h1:TItDGnq71eJcgz5TW+m5Sq3kWGp0AEI1HPCNxj0Eu7k=
9+
github.com/hyperledger/fabric-protos-go-apiv2 v0.3.4 h1:YJrd+gMaeY0/vsN0aS0QkEKTivGoUnSRIXxGJ7KI+Pc=
10+
github.com/hyperledger/fabric-protos-go-apiv2 v0.3.4/go.mod h1:bau/6AJhvEcu9GKKYHlDXAxXKzYNfhP6xu2GXuxEcFk=
11+
github.com/miekg/pkcs11 v1.1.1 h1:Ugu9pdy6vAYku5DEpVWVFPYnzV+bxB+iRdbuFSu7TvU=
12+
github.com/miekg/pkcs11 v1.1.1/go.mod h1:XsNlhZGX73bx86s2hdc/FuaLm2CPZJemRLMA+WTFxgs=
13+
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
14+
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
15+
github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY=
16+
github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
17+
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
18+
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
19+
golang.org/x/crypto v0.28.0 h1:GBDwsMXVQi34v5CCYUm2jkJvu4cbtru2U4TN2PSyQnw=
20+
golang.org/x/crypto v0.28.0/go.mod h1:rmgy+3RHxRZMyY0jjAJShp2zgEdOqj2AO7U0pYmeQ7U=
21+
golang.org/x/net v0.28.0 h1:a9JDOJc5GMUJ0+UDqmLT86WiEy7iWyIhz8gz8E4e5hE=
22+
golang.org/x/net v0.28.0/go.mod h1:yqtgsTWOOnlGLG9GFRrK3++bGOUEkNBoHZc8MEDWPNg=
23+
golang.org/x/sys v0.26.0 h1:KHjCJyddX0LoSTb3J+vWpupP9p0oznkqVk/IfjymZbo=
24+
golang.org/x/sys v0.26.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
25+
golang.org/x/text v0.19.0 h1:kTxAhCbGbxhK0IwgSKiMO5awPoDQ0RpfiVYBfK860YM=
26+
golang.org/x/text v0.19.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY=
27+
google.golang.org/genproto/googleapis/rpc v0.0.0-20240814211410-ddb44dafa142 h1:e7S5W7MGGLaSu8j3YjdezkZ+m1/Nm0uRVRMEMGk26Xs=
28+
google.golang.org/genproto/googleapis/rpc v0.0.0-20240814211410-ddb44dafa142/go.mod h1:UqMtugtsSgubUsoxbuAoiCXvqvErP7Gf0so0mK9tHxU=
29+
google.golang.org/grpc v1.67.1 h1:zWnc1Vrcno+lHZCOofnIMvycFcc0QRGIzm9dhnDX68E=
30+
google.golang.org/grpc v1.67.1/go.mod h1:1gLDyUQU7CTLJI90u3nXZ9ekeghjeM7pTDZlqFNg2AA=
31+
google.golang.org/protobuf v1.35.1 h1:m3LfL6/Ca+fqnjnlqQXNpFPABW1UD7mjh8KO2mKFytA=
32+
google.golang.org/protobuf v1.35.1/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE=
33+
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
34+
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

0 commit comments

Comments
 (0)