Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add off-chain-data go client application #1269

Open
wants to merge 16 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
13 changes: 12 additions & 1 deletion ci/scripts/run-test-network-off-chain.sh
Original file line number Diff line number Diff line change
Expand Up @@ -41,11 +41,22 @@ print "Initializing Typescript off-chain data application"
pushd ../off_chain_data/application-typescript
rm -f checkpoint.json store.log
npm install
print "Running the output app"
print "Running the Typescript app"
SIMULATED_FAILURE_COUNT=1 npm start getAllAssets transact getAllAssets listen
SIMULATED_FAILURE_COUNT=1 npm start listen
popd

# Run off-chain data Go application
export CHAINCODE_NAME=go_off_chain_data
deployChaincode
print "Initializing Go off-chain data application"
pushd ../off_chain_data/application-go
rm -f checkpoint.json store.log
print "Running the Go app"
SIMULATED_FAILURE_COUNT=1 go run . getAllAssets transact getAllAssets listen
SIMULATED_FAILURE_COUNT=1 go run . listen
popd

# Run off-chain data Java application
#createNetwork
export CHAINCODE_NAME=off_chain_data
Expand Down
18 changes: 16 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 @@ -65,6 +65,10 @@ The Fabric test network is used to deploy and run this sample. Follow these step
npm install
npm start transact listen

# To run the Go sample application
cd application-go
go run . transact listen

# To run the Java sample application
cd application-java
./gradlew run --quiet --args='transact listen'
Expand All @@ -79,6 +83,10 @@ The Fabric test network is used to deploy and run this sample. Follow these step
cd application-typescript
npm --silent start getAllAssets

# To run the Go sample application
cd application-go
go run . getAllAssets

# To run the Java sample application
cd application-java
./gradlew run --quiet --args=getAllAssets
Expand All @@ -93,6 +101,12 @@ The Fabric test network is used to deploy and run this sample. Follow these step
SIMULATED_FAILURE_COUNT=5 npm start listen
npm start listen

# To run the Go sample application
cd application-go
go run . transact
SIMULATED_FAILURE_COUNT=5 go run . listen
go run . listen

# To run the Java sample application
cd application-java
./gradlew run --quiet --args=transact
Expand All @@ -112,4 +126,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){
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The clientConnection parameter name can be omitted here. Your choice which you find clearer though.

Suggested change
var allCommands = map[string]func(clientConnection *grpc.ClientConn){
var allCommands = map[string]func(*grpc.ClientConn){

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Omitted.

"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.Println("command:", name)
}

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

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

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

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

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

package main

import (
"crypto/x509"
"fmt"
"offChainData/utils"
"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 = utils.EnvOrDefault("CHANNEL_NAME", "mychannel")
chaincodeName = utils.EnvOrDefault("CHAINCODE_NAME", "basic")
mspID = utils.EnvOrDefault("MSP_ID", "Org1MSP")

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

// Path to user private key directory.
keyDirectoryPath = utils.EnvOrDefault("KEY_DIRECTORY_PATH", cryptoPath+"/users/[email protected]/msp/keystore")

// Path to user certificate.
certPath = utils.EnvOrDefault("CERT_PATH", cryptoPath+"/users/[email protected]/msp/signcerts/cert.pem")

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

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

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

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]))
}
71 changes: 71 additions & 0 deletions off_chain_data/application-go/contract/contract.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
/*
* Copyright 2024 IBM All Rights Reserved.
*
* SPDX-License-Identifier: Apache-2.0
*/
package contract

import (
"fmt"
"strconv"

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

type AssetTransferBasic struct {
contract *client.Contract
}

func NewAssetTransferBasic(contract *client.Contract) *AssetTransferBasic {
return &AssetTransferBasic{contract}
}

func (atb *AssetTransferBasic) CreateAsset(anAsset Asset) error {
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 {
return fmt.Errorf("in CreateAsset: %w", err)
}
return nil
}

func (atb *AssetTransferBasic) TransferAsset(id, newOwner string) (string, error) {
result, err := atb.contract.Submit(
"TransferAsset",
client.WithArguments(
id,
newOwner,
),
)
if err != nil {
return "", fmt.Errorf("in TransferAsset: %w", err)
}

return string(result), nil
}

func (atb *AssetTransferBasic) DeleteAsset(id string) error {
if _, err := atb.contract.Submit(
"DeleteAsset",
client.WithArguments(
id,
),
); err != nil {
return fmt.Errorf("in DeleteAsset: %w", err)
}
return nil
}

func (atb *AssetTransferBasic) GetAllAssets() ([]byte, error) {
result, err := atb.contract.Evaluate("GetAllAssets")
if err != nil {
return []byte{}, fmt.Errorf("in GetAllAssets: %w", err)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

See https://go.dev/wiki/CodeReviewComments#declaring-empty-slices

Suggested change
return []byte{}, fmt.Errorf("in GetAllAssets: %w", err)
return nil, fmt.Errorf("in GetAllAssets: %w", err)

}
return result, nil
}
Loading
Loading