fabric-samples/asset-transfer-basic/application-gateway-go/assetTransfer.go
Mark S. Lewis bdaeac199d
Update Go error handling for v1.10 client API (#1362)
The Fabric Gateway client API v1.10.0 provides simplified access to Go
error details. This change updates sample client application to use this
latest version of the client API, and updates Go client applications to
reflect the error handling changes.

Signed-off-by: Mark S. Lewis <Mark.S.Lewis@outlook.com>
2025-11-10 09:53:58 +09:00

275 lines
8.6 KiB
Go
Executable file

/*
Copyright 2021 IBM All Rights Reserved.
SPDX-License-Identifier: Apache-2.0
*/
package main
import (
"bytes"
"context"
"crypto/x509"
"encoding/json"
"errors"
"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"
"google.golang.org/grpc/status"
)
const (
mspID = "Org1MSP"
cryptoPath = "../../test-network/organizations/peerOrganizations/org1.example.com"
certPath = cryptoPath + "/users/User1@org1.example.com/msp/signcerts"
keyPath = cryptoPath + "/users/User1@org1.example.com/msp/keystore"
tlsCertPath = cryptoPath + "/peers/peer0.org1.example.com/tls/ca.crt"
peerEndpoint = "dns:///localhost:7051"
gatewayPeer = "peer0.org1.example.com"
)
var now = time.Now()
var assetId = fmt.Sprintf("asset%d", now.Unix()*1e3+int64(now.Nanosecond())/1e6)
func main() {
// The gRPC client connection should be shared by all Gateway connections to this endpoint
clientConnection := newGrpcConnection()
defer clientConnection.Close()
id := newIdentity()
sign := newSign()
// Create a Gateway connection for a specific client identity
gw, err := client.Connect(
id,
client.WithSign(sign),
client.WithHash(hash.SHA256),
client.WithClientConnection(clientConnection),
// Default timeouts for different gRPC calls
client.WithEvaluateTimeout(5*time.Second),
client.WithEndorseTimeout(15*time.Second),
client.WithSubmitTimeout(5*time.Second),
client.WithCommitStatusTimeout(1*time.Minute),
)
if err != nil {
panic(err)
}
defer gw.Close()
// Override default values for chaincode and channel name as they may differ in testing contexts.
chaincodeName := "basic"
if ccname := os.Getenv("CHAINCODE_NAME"); ccname != "" {
chaincodeName = ccname
}
channelName := "mychannel"
if cname := os.Getenv("CHANNEL_NAME"); cname != "" {
channelName = cname
}
network := gw.GetNetwork(channelName)
contract := network.GetContract(chaincodeName)
initLedger(contract)
getAllAssets(contract)
createAsset(contract)
readAssetByID(contract)
transferAssetAsync(contract)
exampleErrorHandling(contract)
}
// newGrpcConnection creates a gRPC connection to the Gateway server.
func newGrpcConnection() *grpc.ClientConn {
certificatePEM, err := os.ReadFile(tlsCertPath)
if err != nil {
panic(fmt.Errorf("failed to read TLS certifcate file: %w", err))
}
certificate, err := identity.CertificateFromPEM(certificatePEM)
if err != nil {
panic(err)
}
certPool := x509.NewCertPool()
certPool.AddCert(certificate)
transportCredentials := credentials.NewClientTLSFromCert(certPool, gatewayPeer)
connection, err := grpc.NewClient(peerEndpoint, grpc.WithTransportCredentials(transportCredentials))
if err != nil {
panic(fmt.Errorf("failed to create gRPC connection: %w", err))
}
return connection
}
// newIdentity creates a client identity for this Gateway connection using an X.509 certificate.
func newIdentity() *identity.X509Identity {
certificatePEM, err := readFirstFile(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
}
// newSign creates a function that generates a digital signature from a message digest using a private key.
func newSign() identity.Sign {
privateKeyPEM, err := readFirstFile(keyPath)
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]))
}
// This type of transaction would typically only be run once by an application the first time it was started after its
// initial deployment. A new version of the chaincode deployed later would likely not need to run an "init" function.
func initLedger(contract *client.Contract) {
fmt.Printf("\n--> Submit Transaction: InitLedger, function creates the initial set of assets on the ledger \n")
_, err := contract.SubmitTransaction("InitLedger")
if err != nil {
panic(fmt.Errorf("failed to submit transaction: %w", err))
}
fmt.Printf("*** Transaction committed successfully\n")
}
// Evaluate a transaction to query ledger state.
func getAllAssets(contract *client.Contract) {
fmt.Println("\n--> Evaluate Transaction: GetAllAssets, function returns all the current assets on the ledger")
evaluateResult, err := contract.EvaluateTransaction("GetAllAssets")
if err != nil {
panic(fmt.Errorf("failed to evaluate transaction: %w", err))
}
result := formatJSON(evaluateResult)
fmt.Printf("*** Result:%s\n", result)
}
// Submit a transaction synchronously, blocking until it has been committed to the ledger.
func createAsset(contract *client.Contract) {
fmt.Printf("\n--> Submit Transaction: CreateAsset, creates new asset with ID, Color, Size, Owner and AppraisedValue arguments \n")
_, err := contract.SubmitTransaction("CreateAsset", assetId, "yellow", "5", "Tom", "1300")
if err != nil {
panic(fmt.Errorf("failed to submit transaction: %w", err))
}
fmt.Printf("*** Transaction committed successfully\n")
}
// Evaluate a transaction by assetID to query ledger state.
func readAssetByID(contract *client.Contract) {
fmt.Printf("\n--> Evaluate Transaction: ReadAsset, function returns asset attributes\n")
evaluateResult, err := contract.EvaluateTransaction("ReadAsset", assetId)
if err != nil {
panic(fmt.Errorf("failed to evaluate transaction: %w", err))
}
result := formatJSON(evaluateResult)
fmt.Printf("*** Result:%s\n", result)
}
// Submit transaction asynchronously, blocking until the transaction has been sent to the orderer, and allowing
// this thread to process the chaincode response (e.g. update a UI) without waiting for the commit notification
func transferAssetAsync(contract *client.Contract) {
fmt.Printf("\n--> Async Submit Transaction: TransferAsset, updates existing asset owner")
submitResult, commit, err := contract.SubmitAsync("TransferAsset", client.WithArguments(assetId, "Mark"))
if err != nil {
panic(fmt.Errorf("failed to submit transaction asynchronously: %w", err))
}
fmt.Printf("\n*** Successfully submitted transaction to transfer ownership from %s to Mark. \n", string(submitResult))
fmt.Println("*** Waiting for transaction commit.")
if commitStatus, err := commit.Status(); err != nil {
panic(fmt.Errorf("failed to get commit status: %w", err))
} else if !commitStatus.Successful {
panic(fmt.Errorf("transaction %s failed to commit with status: %d", commitStatus.TransactionID, int32(commitStatus.Code)))
}
fmt.Printf("*** Transaction committed successfully\n")
}
// Submit transaction, passing in the wrong number of arguments ,expected to throw an error containing details of any error responses from the smart contract.
func exampleErrorHandling(contract *client.Contract) {
fmt.Println("\n--> Submit Transaction: UpdateAsset asset70, asset70 does not exist and should return an error")
_, err := contract.SubmitTransaction("UpdateAsset", "asset70", "blue", "5", "Tomoko", "300")
if err == nil {
panic("******** FAILED to return an error")
}
fmt.Println("*** Successfully caught the error:")
var commitStatusErr *client.CommitStatusError
var transactionErr *client.TransactionError
if errors.As(err, &commitStatusErr) {
if errors.Is(err, context.DeadlineExceeded) {
fmt.Printf("Timeout waiting for transaction %s commit status: %s\n", commitStatusErr.TransactionID, commitStatusErr)
} else {
fmt.Printf("Error obtaining commit status for transaction %s with gRPC status %v: %s\n", commitStatusErr.TransactionID, status.Code(commitStatusErr), commitStatusErr)
}
} else if errors.As(err, &transactionErr) {
// The error could be an EndorseError, SubmitError or CommitError.
fmt.Println(err)
fmt.Printf("TransactionID: %s\n", transactionErr.TransactionID)
} else {
panic(fmt.Errorf("unexpected error type %T: %w", err, err))
}
}
// Format JSON data
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()
}