/* Copyright 2021 IBM All Rights Reserved. SPDX-License-Identifier: Apache-2.0 */ package main import ( "bytes" "context" "crypto/x509" "encoding/json" "errors" "fmt" "io/ioutil" "log" "path" "time" "github.com/hyperledger/fabric-gateway/pkg/client" "github.com/hyperledger/fabric-gateway/pkg/identity" "github.com/hyperledger/fabric-protos-go-apiv2/gateway" "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/cert.pem" keyPath = cryptoPath + "/users/User1@org1.example.com/msp/keystore/" tlsCertPath = cryptoPath + "/peers/peer0.org1.example.com/tls/ca.crt" peerEndpoint = "localhost:7051" gatewayPeer = "peer0.org1.example.com" channelName = "mychannel" chaincodeName = "basic" ) var now = time.Now() var assetId = fmt.Sprintf("asset%d", now.Unix()*1e3+int64(now.Nanosecond())/1e6) func main() { log.Println("============ application-golang starts ============") // 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 gateway, err := client.Connect( id, client.WithSign(sign), 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 gateway.Close() network := gateway.GetNetwork(channelName) contract := network.GetContract(chaincodeName) fmt.Println("initLedger:") initLedger(contract) fmt.Println("getAllAssets:") getAllAssets(contract) fmt.Println("createAsset:") createAsset(contract) fmt.Println("readAssetByID:") readAssetByID(contract) fmt.Println("transferAssetAsync:") transferAssetAsync(contract) fmt.Println("exampleErrorHandling:") exampleErrorHandling(contract) log.Println("============ application-golang ends ============") } // newGrpcConnection creates a gRPC connection to the Gateway server. func newGrpcConnection() *grpc.ClientConn { certificate, err := loadCertificate(tlsCertPath) if err != nil { panic(err) } certPool := x509.NewCertPool() certPool.AddCert(certificate) transportCredentials := credentials.NewClientTLSFromCert(certPool, gatewayPeer) connection, err := grpc.Dial(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 { certificate, err := loadCertificate(certPath) if err != nil { panic(err) } id, err := identity.NewX509Identity(mspID, certificate) if err != nil { panic(err) } return id } func loadCertificate(filename string) (*x509.Certificate, error) { certificatePEM, err := ioutil.ReadFile(filename) if err != nil { return nil, fmt.Errorf("failed to read certificate file: %w", err) } return identity.CertificateFromPEM(certificatePEM) } // newSign creates a function that generates a digital signature from a message digest using a private key. func newSign() identity.Sign { files, err := ioutil.ReadDir(keyPath) if err != nil { panic(fmt.Errorf("failed to read private key directory: %w", err)) } privateKeyPEM, err := ioutil.ReadFile(path.Join(keyPath, files[0].Name())) 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 } // 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("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("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("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("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("Async Submit Transaction: TransferAsset, updates existing asset owner'\n") 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("Successfully submitted transaction to transfer ownership from %s to Mark. \n", string(submitResult)) fmt.Println("Waiting for transaction commit.") if status, err := commit.Status(); err != nil { panic(fmt.Errorf("failed to get commit status: %w", err)) } else if !status.Successful { panic(fmt.Errorf("transaction %s failed to commit with status: %d", status.TransactionID, int32(status.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("Submit Transaction: UpdateAsset asset70, asset70 does not exist and should return an error") _, err := contract.SubmitTransaction("UpdateAsset") if err != nil { switch err := err.(type) { case *client.EndorseError: fmt.Printf("Endorse error with gRPC status %v: %s\n", status.Code(err), err) case *client.SubmitError: fmt.Printf("Submit error with gRPC status %v: %s\n", status.Code(err), err) case *client.CommitStatusError: if errors.Is(err, context.DeadlineExceeded) { fmt.Printf("Timeout waiting for transaction %s commit status: %s", err.TransactionID, err) } else { fmt.Printf("Error obtaining commit status with gRPC status %v: %s\n", status.Code(err), err) } case *client.CommitError: fmt.Printf("Transaction %s failed to commit with status %d: %s\n", err.TransactionID, int32(err.Code), err) } // Any error that originates from a peer or orderer node external to the gateway will have its details // embedded within the gRPC status error. The following code shows how to extract that. statusErr := status.Convert(err) for _, detail := range statusErr.Details() { switch detail := detail.(type) { case *gateway.ErrorDetail: fmt.Printf("Error from endpoint: %s, mspId: %s, message: %s\n", detail.Address, detail.MspId, detail.Message) } } } } // 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() }