refactor the commands

Signed-off-by: nXtCyberNet <rohantech2005@gmail.com>
This commit is contained in:
nXtCyberNet 2026-06-10 21:22:59 +05:30
parent d42cc27a98
commit cf7aa047c4
17 changed files with 447 additions and 325 deletions

View file

@ -31,6 +31,19 @@ jobs:
- run: just test-appdev
working-directory: full-stack-asset-transfer-guide
appdev-go:
runs-on: ${{ github.repository == 'hyperledger/fabric-samples' && 'fabric-ubuntu-22.04' || 'ubuntu-22.04' }}
steps:
- uses: actions/checkout@v6
- name: Set up Go
uses: actions/setup-go@v6
with:
go-version-file: full-stack-asset-transfer-guide/applications/trader-go/go.mod
- name: Set up Full Stack Runtime
uses: ./.github/actions/fsat-setup
- run: just test-appdev-go
working-directory: full-stack-asset-transfer-guide
chaincode:
runs-on: ${{ github.repository == 'hyperledger/fabric-samples' && 'fabric-ubuntu-22.04' || 'ubuntu-22.04' }}
steps:

View file

@ -1,10 +1,10 @@
package main
package commands
import "github.com/hyperledger/fabric-gateway/pkg/client"
type Command func(gw *client.Gateway, args []string) error
var commands = map[string]Command{
var Commands = map[string]Command{
"create": cmdCreate,
"delete": cmdDelete,
"getAllAssets": cmdGetAllAssets,

View file

@ -0,0 +1,17 @@
package commands
import "os"
func channelName() string {
if v := os.Getenv("CHANNEL_NAME"); v != "" {
return v
}
return "mychannel"
}
func chaincodeName() string {
if v := os.Getenv("CHAINCODE_NAME"); v != "" {
return v
}
return "asset-transfer"
}

View file

@ -0,0 +1,27 @@
package commands
import (
"fmt"
"github.com/hyperledger/fabric-gateway/pkg/client"
)
// cmdCreate creates a new asset on the ledger.
// Arguments: <assetId> <ownerName> <color>
func cmdCreate(gw *client.Gateway, args []string) error {
if len(args) < 3 {
return fmt.Errorf("arguments: <assetId> <ownerName> <color>")
}
network := gw.GetNetwork(channelName())
contract := network.GetContract(chaincodeName())
smartContract := NewAssetTransfer(contract)
return smartContract.CreateAsset(Asset{
ID: args[0],
Owner: args[1],
Color: args[2],
Size: 1,
AppraisedValue: 1,
})
}

View file

@ -0,0 +1,21 @@
package commands
import (
"fmt"
"github.com/hyperledger/fabric-gateway/pkg/client"
)
// cmdDelete deletes an asset from the ledger.
// Arguments: <assetId>
func cmdDelete(gw *client.Gateway, args []string) error {
if len(args) < 1 {
return fmt.Errorf("arguments: <assetId>")
}
network := gw.GetNetwork(channelName())
contract := network.GetContract(chaincodeName())
smartContract := NewAssetTransfer(contract)
return smartContract.DeleteAsset(args[0])
}

View file

@ -1,10 +1,4 @@
/*
* Copyright IBM Corp. All Rights Reserved.
*
* SPDX-License-Identifier: Apache-2.0
*/
package main
package commands
// ExpectedError represents a known, expected application error that should be
// displayed normally rather than treated as an unexpected failure.

View file

@ -0,0 +1,31 @@
package commands
import (
"encoding/json"
"fmt"
"strings"
"github.com/hyperledger/fabric-gateway/pkg/client"
)
// cmdGetAllAssets queries and prints all assets currently on the ledger.
func cmdGetAllAssets(gw *client.Gateway, _ []string) error {
network := gw.GetNetwork(channelName())
contract := network.GetContract(chaincodeName())
smartContract := NewAssetTransfer(contract)
assets, err := smartContract.GetAllAssets()
if err != nil {
return err
}
data, err := json.MarshalIndent(assets, "", " ")
if err != nil {
return fmt.Errorf("failed to marshal assets: %w", err)
}
for _, line := range strings.Split(string(data), "\n") {
fmt.Println(line)
}
return nil
}

View file

@ -0,0 +1,81 @@
package commands
import (
"context"
"fmt"
"os"
"strconv"
"github.com/hyperledger/fabric-gateway/pkg/client"
)
const startBlock = uint64(0)
func cmdListen(gw *client.Gateway, _ []string) error {
network := gw.GetNetwork(channelName())
checkpointFile := os.Getenv("CHECKPOINT_FILE")
if checkpointFile == "" {
checkpointFile = "checkpoint.json"
}
simulatedFailureCount, err := getSimulatedFailureCount()
if err != nil {
return err
}
checkpointer, err := client.NewFileCheckpointer(checkpointFile)
if err != nil {
return fmt.Errorf("failed to create checkpointer: %w", err)
}
defer checkpointer.Close()
displayBlock := checkpointer.BlockNumber()
if displayBlock == 0 {
displayBlock = startBlock
}
fmt.Println("Starting event listening from block", displayBlock)
fmt.Println("Last processed transaction ID within block:", checkpointer.TransactionID())
if simulatedFailureCount > 0 {
fmt.Println("Simulating a write failure every", simulatedFailureCount, "transactions")
}
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
events, err := network.ChaincodeEvents(ctx, chaincodeName(),
client.WithStartBlock(startBlock),
client.WithCheckpoint(checkpointer),
)
if err != nil {
return fmt.Errorf("failed to start chaincode event subscription: %w", err)
}
eventCount := 0
for event := range events {
if simulatedFailureCount > 0 {
eventCount++
if eventCount >= simulatedFailureCount {
eventCount = 0
return &ExpectedError{Message: "Simulated write failure"}
}
}
fmt.Printf("Chaincode event: BlockNumber=%d TxID=%s Name=%s Payload=%s\n",
event.BlockNumber, event.TransactionID, event.EventName, string(event.Payload))
}
return nil
}
func getSimulatedFailureCount() (int, error) {
value := os.Getenv("SIMULATED_FAILURE_COUNT")
if value == "" {
return 0, nil
}
count, err := strconv.Atoi(value)
if err != nil || count < 0 {
return 0, fmt.Errorf("invalid SIMULATED_FAILURE_COUNT value: %s", value)
}
return count, nil
}

View file

@ -0,0 +1,33 @@
package commands
import (
"encoding/json"
"fmt"
"github.com/hyperledger/fabric-gateway/pkg/client"
)
// cmdRead reads and prints a single asset from the ledger.
// Arguments: <assetId>
func cmdRead(gw *client.Gateway, args []string) error {
if len(args) < 1 {
return fmt.Errorf("arguments: <assetId>")
}
network := gw.GetNetwork(channelName())
contract := network.GetContract(chaincodeName())
smartContract := NewAssetTransfer(contract)
asset, err := smartContract.ReadAsset(args[0])
if err != nil {
return err
}
data, err := json.MarshalIndent(asset, "", " ")
if err != nil {
return fmt.Errorf("failed to marshal asset: %w", err)
}
fmt.Println(string(data))
return nil
}

View file

@ -0,0 +1,126 @@
package commands
import (
"crypto/rand"
"encoding/hex"
"fmt"
"math/big"
"strings"
"sync"
"github.com/hyperledger/fabric-gateway/pkg/client"
)
var (
colors = []string{"red", "green", "blue"}
maxInitialSize = 10
maxInitialVal = 1000
)
// cmdTransact runs a batch of concurrent create/update/delete transactions to demonstrate
func cmdTransact(gw *client.Gateway, _ []string) error {
network := gw.GetNetwork(channelName())
contract := network.GetContract(chaincodeName())
smartContract := NewAssetTransfer(contract)
app := &transactApp{smartContract: smartContract, batchSize: 6}
return app.run()
}
type transactApp struct {
smartContract *AssetTransfer
batchSize int
}
func (a *transactApp) run() error {
var wg sync.WaitGroup
errCh := make(chan error, a.batchSize)
for i := 0; i < a.batchSize; i++ {
wg.Add(1)
go func() {
defer wg.Done()
if err := a.transact(); err != nil {
errCh <- err
}
}()
}
wg.Wait()
close(errCh)
var failures []string
for err := range errCh {
failures = append(failures, err.Error())
}
if len(failures) > 0 {
return fmt.Errorf("%d failures:\n- %s", len(failures), strings.Join(failures, "\n- "))
}
return nil
}
func (a *transactApp) transact() error {
asset := a.newAsset()
if err := a.smartContract.CreateAsset(asset); err != nil {
return err
}
fmt.Printf("Created asset %s\n", asset.ID)
if randomInt(2) == 0 {
oldColor := asset.Color
asset.Color = differentElement(colors, oldColor)
if err := a.smartContract.UpdateAsset(asset); err != nil {
return err
}
fmt.Printf("Updated color of asset %s from %s to %s\n", asset.ID, oldColor, asset.Color)
}
if randomInt(4) == 0 {
if err := a.smartContract.DeleteAsset(asset.ID); err != nil {
return err
}
fmt.Printf("Deleted asset %s\n", asset.ID)
}
return nil
}
func (a *transactApp) newAsset() Asset {
return Asset{
ID: randomHexString(8),
Color: randomElement(colors),
Size: randomInt(maxInitialSize) + 1,
AppraisedValue: float64(randomInt(maxInitialVal) + 1),
}
}
func randomHexString(length int) string {
b := make([]byte, (length+1)/2)
if _, err := rand.Read(b); err != nil {
panic(fmt.Sprintf("failed to generate random bytes: %v", err))
}
return hex.EncodeToString(b)[:length]
}
func randomInt(max int) int {
n, err := rand.Int(rand.Reader, big.NewInt(int64(max)))
if err != nil {
panic(fmt.Sprintf("failed to generate random int: %v", err))
}
return int(n.Int64())
}
func randomElement(values []string) string {
return values[randomInt(len(values))]
}
func differentElement(values []string, currentValue string) string {
var candidates []string
for _, v := range values {
if v != currentValue {
candidates = append(candidates, v)
}
}
return randomElement(candidates)
}

View file

@ -0,0 +1,21 @@
package commands
import (
"fmt"
"github.com/hyperledger/fabric-gateway/pkg/client"
)
// cmdTransfer transfers ownership of an asset to a new owner in a different organisation.
// Arguments: <assetId> <ownerName> <ownerMspId>
func cmdTransfer(gw *client.Gateway, args []string) error {
if len(args) < 3 {
return fmt.Errorf("arguments: <assetId> <ownerName> <ownerMspId>")
}
network := gw.GetNetwork(channelName())
contract := network.GetContract(chaincodeName())
smartContract := NewAssetTransfer(contract)
return smartContract.TransferAsset(args[0], args[1], args[2])
}

View file

@ -1,296 +0,0 @@
package main
import (
"context"
"crypto/rand"
"encoding/hex"
"encoding/json"
"fmt"
"math/big"
"os"
"strconv"
"strings"
"sync"
"github.com/hyperledger/fabric-gateway/pkg/client"
)
// cmdCreate creates a new asset on the ledger.
// Arguments: <assetId> <ownerName> <color>
func cmdCreate(gw *client.Gateway, args []string) error {
if len(args) < 3 {
return fmt.Errorf("arguments: <assetId> <ownerName> <color>")
}
network := gw.GetNetwork(channelName())
contract := network.GetContract(chaincodeName())
smartContract := NewAssetTransfer(contract)
return smartContract.CreateAsset(Asset{
ID: args[0],
Owner: args[1],
Color: args[2],
Size: 1,
AppraisedValue: 1,
})
}
// cmdDelete deletes an asset from the ledger.
// Arguments: <assetId>
func cmdDelete(gw *client.Gateway, args []string) error {
if len(args) < 1 {
return fmt.Errorf("arguments: <assetId>")
}
network := gw.GetNetwork(channelName())
contract := network.GetContract(chaincodeName())
smartContract := NewAssetTransfer(contract)
return smartContract.DeleteAsset(args[0])
}
// cmdGetAllAssets queries and prints all assets currently on the ledger.
func cmdGetAllAssets(gw *client.Gateway, _ []string) error {
network := gw.GetNetwork(channelName())
contract := network.GetContract(chaincodeName())
smartContract := NewAssetTransfer(contract)
assets, err := smartContract.GetAllAssets()
if err != nil {
return err
}
data, err := json.MarshalIndent(assets, "", " ")
if err != nil {
return fmt.Errorf("failed to marshal assets: %w", err)
}
for _, line := range strings.Split(string(data), "\n") {
fmt.Println(line)
}
return nil
}
const startBlock = uint64(0)
func cmdListen(gw *client.Gateway, _ []string) error {
network := gw.GetNetwork(channelName())
checkpointFile := os.Getenv("CHECKPOINT_FILE")
if checkpointFile == "" {
checkpointFile = "checkpoint.json"
}
simulatedFailureCount, err := getSimulatedFailureCount()
if err != nil {
return err
}
checkpointer, err := client.NewFileCheckpointer(checkpointFile)
if err != nil {
return fmt.Errorf("failed to create checkpointer: %w", err)
}
defer checkpointer.Close()
displayBlock := checkpointer.BlockNumber()
if displayBlock == 0 {
displayBlock = startBlock
}
fmt.Println("Starting event listening from block", displayBlock)
fmt.Println("Last processed transaction ID within block:", checkpointer.TransactionID())
if simulatedFailureCount > 0 {
fmt.Println("Simulating a write failure every", simulatedFailureCount, "transactions")
}
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
events, err := network.ChaincodeEvents(ctx, chaincodeName(),
client.WithStartBlock(startBlock),
client.WithCheckpoint(checkpointer),
)
if err != nil {
return fmt.Errorf("failed to start chaincode event subscription: %w", err)
}
eventCount := 0
for event := range events {
if simulatedFailureCount > 0 {
eventCount++
if eventCount >= simulatedFailureCount {
eventCount = 0
return &ExpectedError{Message: "Simulated write failure"}
}
}
fmt.Printf("Chaincode event: BlockNumber=%d TxID=%s Name=%s Payload=%s\n",
event.BlockNumber, event.TransactionID, event.EventName, string(event.Payload))
}
return nil
}
func getSimulatedFailureCount() (int, error) {
value := os.Getenv("SIMULATED_FAILURE_COUNT")
if value == "" {
return 0, nil
}
count, err := strconv.Atoi(value)
if err != nil || count < 0 {
return 0, fmt.Errorf("invalid SIMULATED_FAILURE_COUNT value: %s", value)
}
return count, nil
}
// cmdRead reads and prints a single asset from the ledger.
// Arguments: <assetId>
func cmdRead(gw *client.Gateway, args []string) error {
if len(args) < 1 {
return fmt.Errorf("arguments: <assetId>")
}
network := gw.GetNetwork(channelName())
contract := network.GetContract(chaincodeName())
smartContract := NewAssetTransfer(contract)
asset, err := smartContract.ReadAsset(args[0])
if err != nil {
return err
}
data, err := json.MarshalIndent(asset, "", " ")
if err != nil {
return fmt.Errorf("failed to marshal asset: %w", err)
}
fmt.Println(string(data))
return nil
}
var (
colors = []string{"red", "green", "blue"}
maxInitialSize = 10
maxInitialVal = 1000
)
// cmdTransact runs a batch of concurrent create/update/delete transactions to demonstrate
func cmdTransact(gw *client.Gateway, _ []string) error {
network := gw.GetNetwork(channelName())
contract := network.GetContract(chaincodeName())
smartContract := NewAssetTransfer(contract)
app := &transactApp{smartContract: smartContract, batchSize: 6}
return app.run()
}
type transactApp struct {
smartContract *AssetTransfer
batchSize int
}
func (a *transactApp) run() error {
var wg sync.WaitGroup
errCh := make(chan error, a.batchSize)
for i := 0; i < a.batchSize; i++ {
wg.Add(1)
go func() {
defer wg.Done()
if err := a.transact(); err != nil {
errCh <- err
}
}()
}
wg.Wait()
close(errCh)
var failures []string
for err := range errCh {
failures = append(failures, err.Error())
}
if len(failures) > 0 {
return fmt.Errorf("%d failures:\n- %s", len(failures), strings.Join(failures, "\n- "))
}
return nil
}
func (a *transactApp) transact() error {
asset := a.newAsset()
if err := a.smartContract.CreateAsset(asset); err != nil {
return err
}
fmt.Printf("Created asset %s\n", asset.ID)
if randomInt(2) == 0 {
oldColor := asset.Color
asset.Color = differentElement(colors, oldColor)
if err := a.smartContract.UpdateAsset(asset); err != nil {
return err
}
fmt.Printf("Updated color of asset %s from %s to %s\n", asset.ID, oldColor, asset.Color)
}
if randomInt(4) == 0 {
if err := a.smartContract.DeleteAsset(asset.ID); err != nil {
return err
}
fmt.Printf("Deleted asset %s\n", asset.ID)
}
return nil
}
func (a *transactApp) newAsset() Asset {
return Asset{
ID: randomHexString(8),
Color: randomElement(colors),
Size: randomInt(maxInitialSize) + 1,
AppraisedValue: float64(randomInt(maxInitialVal) + 1),
}
}
func randomHexString(length int) string {
b := make([]byte, (length+1)/2)
if _, err := rand.Read(b); err != nil {
panic(fmt.Sprintf("failed to generate random bytes: %v", err))
}
return hex.EncodeToString(b)[:length]
}
func randomInt(max int) int {
n, err := rand.Int(rand.Reader, big.NewInt(int64(max)))
if err != nil {
panic(fmt.Sprintf("failed to generate random int: %v", err))
}
return int(n.Int64())
}
func randomElement(values []string) string {
return values[randomInt(len(values))]
}
func differentElement(values []string, currentValue string) string {
var candidates []string
for _, v := range values {
if v != currentValue {
candidates = append(candidates, v)
}
}
return randomElement(candidates)
}
// cmdTransfer transfers ownership of an asset to a new owner in a different organisation.
// Arguments: <assetId> <ownerName> <ownerMspId>
func cmdTransfer(gw *client.Gateway, args []string) error {
if len(args) < 3 {
return fmt.Errorf("arguments: <assetId> <ownerName> <ownerMspId>")
}
network := gw.GetNetwork(channelName())
contract := network.GetContract(chaincodeName())
smartContract := NewAssetTransfer(contract)
return smartContract.TransferAsset(args[0], args[1], args[2])
}

View file

@ -25,20 +25,6 @@ func tlsCertPath() string {
return os.Getenv("TLS_CERT")
}
func channelName() string {
if v := os.Getenv("CHANNEL_NAME"); v != "" {
return v
}
return "mychannel"
}
func chaincodeName() string {
if v := os.Getenv("CHAINCODE_NAME"); v != "" {
return v
}
return "asset-transfer"
}
func hostAlias() string {
return os.Getenv("HOST_ALIAS")
}

View file

@ -5,11 +5,13 @@ import (
"fmt"
"os"
"sort"
"trader-go/commands"
)
func main() {
if err := run(); err != nil {
var expectedErr *ExpectedError
var expectedErr *commands.ExpectedError
if errors.As(err, &expectedErr) {
fmt.Println(err)
} else {
@ -29,7 +31,7 @@ func run() error {
commandName := args[0]
commandArgs := args[1:]
command, ok := commands[commandName]
command, ok := commands.Commands[commandName]
if !ok {
printUsage()
return fmt.Errorf("unknown command: %s", commandName)
@ -51,8 +53,8 @@ func run() error {
}
func printUsage() {
names := make([]string, 0, len(commands))
for name := range commands {
names := make([]string, 0, len(commands.Commands))
for name := range commands.Commands {
names = append(names, name)
}
sort.Strings(names)

View file

@ -101,7 +101,7 @@ operator-crds: check-kube
###############################################################################
# Run e2e tests of all scenarios
test: test-chaincode test-appdev test-cloud # test-ansible
test: test-chaincode test-appdev test-appdev-go test-cloud # test-ansible
# Run an e2e test of the SmartContractDev scenario
test-chaincode:
@ -111,6 +111,10 @@ test-chaincode:
test-appdev:
tests/10-appdev-e2e.sh
# Run an e2e test of the ApplicationDev Go scenario
test-appdev-go:
tests/10-appdev-e2e-go.sh
# Run an e2e test of the CloudNative scenario
test-cloud:
tests/20-cloud-e2e.sh

View file

@ -0,0 +1,62 @@
#!/usr/bin/env bash
#
# Copyright contributors to the Hyperledgendary Full Stack Asset Transfer project
#
# SPDX-License-Identifier: Apache-2.0
#
set -v -eou pipefail
# All tests run in the workshop root folder
cd "$(dirname "$0")"/..
export WORKSHOP_PATH="${PWD}"
export PATH="${WORKSHOP_PATH}/bin:${PATH}"
export FABRIC_CFG_PATH="${WORKSHOP_PATH}/config"
"${WORKSHOP_PATH}/check.sh"
CHAINCODE_PID=
function exitHook() {
# shut down the chaincode container/process
[ -n "${CHAINCODE_PID}" ] && kill "${CHAINCODE_PID}"
# Shut down microfab
docker kill microfab &> /dev/null
# Delete the network configuration and crypto material
rm -rf "${WORKSHOP_PATH}"/_cfg
}
trap exitHook SIGINT SIGTERM EXIT
just microfab
source "${WORKSHOP_PATH}/_cfg/uf/org1admin.env"
just debugcc
source "${WORKSHOP_PATH}/_cfg/uf/org1admin.env"
cd "${WORKSHOP_PATH}/contracts/asset-transfer-typescript"
npm install
npm run build
node_modules/.bin/fabric-chaincode-node server --chaincode-address="${CHAINCODE_SERVER_ADDRESS}" --chaincode-id="${CHAINCODE_ID}" &
CHAINCODE_PID=$!
sleep 5
cd "${WORKSHOP_PATH}/applications/trader-go"
export ENDPOINT=org1peer-api.127-0-0-1.nip.io:8080
export MSP_ID=org1MSP
export CERTIFICATE=../../_cfg/uf/_msp/org1/org1admin/msp/signcerts/cert.pem
export PRIVATE_KEY=../../_cfg/uf/_msp/org1/org1admin/msp/keystore/cert_sk
go build -o trader
./trader getAllAssets
./trader transact
./trader getAllAssets
./trader create banana bananaman yellow
./trader read banana
./trader delete banana
SIMULATED_FAILURE_COUNT=2 ./trader listen