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.

Implemented pretty printing of JSON results.

Signed-off-by: Stanislav Jakuschevskij <stas@two-giants.com>
  • Loading branch information
twoGiants committed Nov 12, 2024
1 parent 30b6186 commit 45a2aac
Show file tree
Hide file tree
Showing 8 changed files with 328 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
```
```
14 changes: 14 additions & 0 deletions off_chain_data/application-go/app.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
/*
* Copyright 2024 IBM All Rights Reserved.
*
* SPDX-License-Identifier: Apache-2.0
*/

package main

func main() {
client := newGrpcConnection()
defer client.Close()

getAllAssets(client)
}
142 changes: 142 additions & 0 deletions off_chain_data/application-go/connect.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
/*
* Copyright 2024 IBM All Rights Reserved.
*
* SPDX-License-Identifier: Apache-2.0
*/

package main

import (
"crypto/x509"
"fmt"
"os"
"path"
"time"

"github.com/hyperledger/fabric-gateway/pkg/client"
"github.com/hyperledger/fabric-gateway/pkg/hash"
"github.com/hyperledger/fabric-gateway/pkg/identity"
"google.golang.org/grpc"
"google.golang.org/grpc/credentials"
)

const peerName = "peer0.org1.example.com"

var (
channelName = envOrDefault("CHANNEL_NAME", "mychannel")
chaincodeName = envOrDefault("CHAINCODE_NAME", "basic")
mspID = envOrDefault("MSP_ID", "Org1MSP")

// Path to crypto materials.
cryptoPath = envOrDefault("CRYPTO_PATH", "../../test-network/organizations/peerOrganizations/org1.example.com")

// Path to user private key directory.
keyDirectoryPath = envOrDefault("KEY_DIRECTORY_PATH", cryptoPath+"/users/User1@org1.example.com/msp/keystore")

// Path to user certificate.
certPath = envOrDefault("CERT_PATH", cryptoPath+"/users/User1@org1.example.com/msp/signcerts/cert.pem")

// Path to peer tls certificate.
tlsCertPath = envOrDefault("TLS_CERT_PATH", cryptoPath+"/peers/peer0.org1.example.com/tls/ca.crt")

// Gateway peer endpoint.
peerEndpoint = envOrDefault("PEER_ENDPOINT", "dns:///localhost:7051")

// Gateway peer SSL host name override.
peerHostAlias = envOrDefault("PEER_HOST_ALIAS", peerName)
)

func envOrDefault(key, defaultValue string) string {
result := os.Getenv(key)
if result == "" {
return defaultValue
}
return result
}

func newGrpcConnection() *grpc.ClientConn {
certificatePEM, err := os.ReadFile(tlsCertPath)
if err != nil {
panic(fmt.Errorf("failed to read TLS certificate file: %w", err))
}

certificate, err := identity.CertificateFromPEM(certificatePEM)
if err != nil {
panic(err)
}

certPool := x509.NewCertPool()
certPool.AddCert(certificate)
transportCredentials := credentials.NewClientTLSFromCert(certPool, peerHostAlias)

connection, err := grpc.NewClient(peerEndpoint, grpc.WithTransportCredentials(transportCredentials))
if err != nil {
panic(fmt.Errorf("failed to create gRPC connection: %w", err))
}

return connection
}

func newConnectOptions(clientConnection *grpc.ClientConn) (identity.Identity, []client.ConnectOption) {
return newIdentity(), []client.ConnectOption{
client.WithSign(newSign()),
client.WithHash(hash.SHA256),
client.WithClientConnection(clientConnection),
client.WithEvaluateTimeout(5 * time.Second),
client.WithEndorseTimeout(15 * time.Second),
client.WithSubmitTimeout(5 * time.Second),
client.WithCommitStatusTimeout(1 * time.Minute),
}
}

func newIdentity() *identity.X509Identity {
certificatePEM, err := os.ReadFile(certPath)
if err != nil {
panic(fmt.Errorf("failed to read certificate file: %w", err))
}

certificate, err := identity.CertificateFromPEM(certificatePEM)
if err != nil {
panic(err)
}

id, err := identity.NewX509Identity(mspID, certificate)
if err != nil {
panic(err)
}

return id
}

func newSign() identity.Sign {
privateKeyPEM, err := readFirstFile(keyDirectoryPath)
if err != nil {
panic(fmt.Errorf("failed to read private key file: %w", err))
}

privateKey, err := identity.PrivateKeyFromPEM(privateKeyPEM)
if err != nil {
panic(err)
}

sign, err := identity.NewPrivateKeySign(privateKey)
if err != nil {
panic(err)
}

return sign
}

func readFirstFile(dirPath string) ([]byte, error) {
dir, err := os.Open(dirPath)
if err != nil {
return nil, err
}

fileNames, err := dir.Readdirnames(1)
if err != nil {
return nil, err
}

return os.ReadFile(path.Join(dirPath, fileNames[0]))
}
77 changes: 77 additions & 0 deletions off_chain_data/application-go/contract.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
/*
* Copyright 2024 IBM All Rights Reserved.
*
* SPDX-License-Identifier: Apache-2.0
*/

package main

import (
"strconv"

"github.com/hyperledger/fabric-gateway/pkg/client"
)

type asset struct {
ID string
Color string
Size uint64
Owner string
AppraisedValue uint64
}

type assetTransferBasic struct {
contract *client.Contract
}

func newAssetTransferBasic(contract *client.Contract) *assetTransferBasic {
return &assetTransferBasic{contract}
}

func (atb *assetTransferBasic) createAsset(anAsset asset) {
if _, err := atb.contract.Submit(
"CreateAsset",
client.WithArguments(
anAsset.ID,
anAsset.Color,
strconv.FormatUint(anAsset.Size, 10),
anAsset.Owner,
strconv.FormatUint(anAsset.AppraisedValue, 10),
)); err != nil {
panic(err)
}
}

func (atb *assetTransferBasic) transferAsset(id, newOwner string) string {
result, err := atb.contract.Submit(
"TransferAsset",
client.WithArguments(
id,
newOwner,
),
)
if err != nil {
panic(err)
}

return string(result)
}

func (atb *assetTransferBasic) deleteAsset(id string) {
if _, err := atb.contract.Submit(
"DeleteAsset",
client.WithArguments(
id,
),
); err != nil {
panic(err)
}
}

func (atb *assetTransferBasic) getAllAssets() []byte {
result, err := atb.contract.Evaluate("GetAllAssets")
if err != nil {
panic(err)
}
return result
}
39 changes: 39 additions & 0 deletions off_chain_data/application-go/getAllAssets.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
/*
* Copyright 2024 IBM All Rights Reserved.
*
* SPDX-License-Identifier: Apache-2.0
*/

package main

import (
"bytes"
"encoding/json"
"fmt"

"github.com/hyperledger/fabric-gateway/pkg/client"
"google.golang.org/grpc"
)

func getAllAssets(clientConnection *grpc.ClientConn) {
id, options := newConnectOptions(clientConnection)
gateway, err := client.Connect(id, options...)
if err != nil {
panic((err))
}
defer gateway.Close()

contract := gateway.GetNetwork(channelName).GetContract(chaincodeName)
smartContract := newAssetTransferBasic(contract)
assets := smartContract.getAllAssets()

fmt.Printf("%s\n", formatJSON(assets))
}

func formatJSON(data []byte) string {
var prettyJSON bytes.Buffer
if err := json.Indent(&prettyJSON, data, "", " "); err != nil {
panic(fmt.Errorf("failed to parse JSON: %w", err))
}
return prettyJSON.String()
}
19 changes: 19 additions & 0 deletions off_chain_data/application-go/go.mod
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
module offChainData

go 1.22.0

require (
github.com/hyperledger/fabric-gateway v1.7.0
google.golang.org/grpc v1.67.1
)

require (
github.com/hyperledger/fabric-protos-go-apiv2 v0.3.4 // indirect
github.com/miekg/pkcs11 v1.1.1 // indirect
golang.org/x/crypto v0.28.0 // indirect
golang.org/x/net v0.28.0 // indirect
golang.org/x/sys v0.26.0 // indirect
golang.org/x/text v0.19.0 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20240814211410-ddb44dafa142 // indirect
google.golang.org/protobuf v1.35.1 // indirect
)
32 changes: 32 additions & 0 deletions off_chain_data/application-go/go.sum
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/hyperledger/fabric-gateway v1.7.0 h1:bd1quU8qYPYqYO69m1tPIDSjB+D+u/rBJfE1eWFcpjY=
github.com/hyperledger/fabric-gateway v1.7.0/go.mod h1:TItDGnq71eJcgz5TW+m5Sq3kWGp0AEI1HPCNxj0Eu7k=
github.com/hyperledger/fabric-protos-go-apiv2 v0.3.4 h1:YJrd+gMaeY0/vsN0aS0QkEKTivGoUnSRIXxGJ7KI+Pc=
github.com/hyperledger/fabric-protos-go-apiv2 v0.3.4/go.mod h1:bau/6AJhvEcu9GKKYHlDXAxXKzYNfhP6xu2GXuxEcFk=
github.com/miekg/pkcs11 v1.1.1 h1:Ugu9pdy6vAYku5DEpVWVFPYnzV+bxB+iRdbuFSu7TvU=
github.com/miekg/pkcs11 v1.1.1/go.mod h1:XsNlhZGX73bx86s2hdc/FuaLm2CPZJemRLMA+WTFxgs=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY=
github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
golang.org/x/crypto v0.28.0 h1:GBDwsMXVQi34v5CCYUm2jkJvu4cbtru2U4TN2PSyQnw=
golang.org/x/crypto v0.28.0/go.mod h1:rmgy+3RHxRZMyY0jjAJShp2zgEdOqj2AO7U0pYmeQ7U=
golang.org/x/net v0.28.0 h1:a9JDOJc5GMUJ0+UDqmLT86WiEy7iWyIhz8gz8E4e5hE=
golang.org/x/net v0.28.0/go.mod h1:yqtgsTWOOnlGLG9GFRrK3++bGOUEkNBoHZc8MEDWPNg=
golang.org/x/sys v0.26.0 h1:KHjCJyddX0LoSTb3J+vWpupP9p0oznkqVk/IfjymZbo=
golang.org/x/sys v0.26.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/text v0.19.0 h1:kTxAhCbGbxhK0IwgSKiMO5awPoDQ0RpfiVYBfK860YM=
golang.org/x/text v0.19.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY=
google.golang.org/genproto/googleapis/rpc v0.0.0-20240814211410-ddb44dafa142 h1:e7S5W7MGGLaSu8j3YjdezkZ+m1/Nm0uRVRMEMGk26Xs=
google.golang.org/genproto/googleapis/rpc v0.0.0-20240814211410-ddb44dafa142/go.mod h1:UqMtugtsSgubUsoxbuAoiCXvqvErP7Gf0so0mK9tHxU=
google.golang.org/grpc v1.67.1 h1:zWnc1Vrcno+lHZCOofnIMvycFcc0QRGIzm9dhnDX68E=
google.golang.org/grpc v1.67.1/go.mod h1:1gLDyUQU7CTLJI90u3nXZ9ekeghjeM7pTDZlqFNg2AA=
google.golang.org/protobuf v1.35.1 h1:m3LfL6/Ca+fqnjnlqQXNpFPABW1UD7mjh8KO2mKFytA=
google.golang.org/protobuf v1.35.1/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

0 comments on commit 45a2aac

Please sign in to comment.