fabric-samples/asset-transfer-events/application-gateway-go/app.go
Mark S. Lewis 2581f10984
Asset transfer events Go sample for Fabric Gateway (#597)
Signed-off-by: Mark S. Lewis <mark_lewis@uk.ibm.com>
2022-01-26 16:52:53 -05:00

245 lines
6.7 KiB
Go
Executable file

/*
Copyright 2022 IBM All Rights Reserved.
SPDX-License-Identifier: Apache-2.0
*/
package main
import (
"bytes"
"context"
"crypto/x509"
"encoding/json"
"fmt"
"io/ioutil"
"path"
"time"
"github.com/hyperledger/fabric-gateway/pkg/client"
"github.com/hyperledger/fabric-gateway/pkg/identity"
"google.golang.org/grpc"
"google.golang.org/grpc/credentials"
)
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 = "events"
)
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
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)
// Context used for event listening
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
// Listen for events emitted by subsequent transactions
startChaincodeEventListening(ctx, network)
firstBlockNumber := createAsset(contract)
updateAsset(contract)
transferAsset(contract)
deleteAsset(contract)
// Replay events from the block containing the first transaction
replayChaincodeEvents(ctx, network, firstBlockNumber)
}
// 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
}
func startChaincodeEventListening(ctx context.Context, network *client.Network) {
fmt.Printf("\n*** Start chaincode event listening\n")
events, err := network.ChaincodeEvents(ctx, chaincodeName)
if err != nil {
panic(fmt.Errorf("failed to start chaincode event listening: %w", err))
}
go func() {
for event := range events {
asset := formatJSON(event.Payload)
fmt.Printf("\n<-- Chaincode event received: %s - %s\n", event.EventName, asset)
}
}()
}
func formatJSON(data []byte) string {
var result bytes.Buffer
if err := json.Indent(&result, data, "", " "); err != nil {
panic(fmt.Errorf("failed to parse JSON: %w", err))
}
return result.String()
}
func createAsset(contract *client.Contract) uint64 {
fmt.Printf("\n--> Submit transaction: CreateAsset, %s owned by Sam with appraised value 100\n", assetID)
_, commit, err := contract.SubmitAsync("CreateAsset", client.WithArguments(assetID, "blue", "10", "Sam", "100"))
if err != nil {
panic(fmt.Errorf("failed to submit transaction: %w", err))
}
status, err := commit.Status()
if err != nil {
panic(fmt.Errorf("failed to get transaction commit status: %w", err))
}
if !status.Successful {
panic(fmt.Errorf("failed to commit transaction with status code %v", status.Code))
}
fmt.Printf("\n*** CreateAsset committed successfully\n")
return status.BlockNumber
}
func updateAsset(contract *client.Contract) {
fmt.Printf("\n--> Submit transaction: UpdateAsset, %s update appraised value to 200\n", assetID)
_, err := contract.SubmitTransaction("UpdateAsset", assetID, "blue", "10", "Sam", "200")
if err != nil {
panic(fmt.Errorf("failed to submit transaction: %w", err))
}
fmt.Printf("\n*** UpdateAsset committed successfully\n")
}
func transferAsset(contract *client.Contract) {
fmt.Printf("\n--> Submit transaction: TransferAsset, %s to Mary\n", assetID)
_, err := contract.SubmitTransaction("TransferAsset", assetID, "Mary")
if err != nil {
panic(fmt.Errorf("failed to submit transaction: %w", err))
}
fmt.Printf("\n*** TransferAsset committed successfully\n")
}
func deleteAsset(contract *client.Contract) {
fmt.Printf("\n--> Submit transaction: DeleteAsset, %s\n", assetID)
_, err := contract.SubmitTransaction("DeleteAsset", assetID)
if err != nil {
panic(fmt.Errorf("failed to submit transaction: %w", err))
}
fmt.Printf("\n*** DeleteAsset committed successfully\n")
}
func replayChaincodeEvents(ctx context.Context, network *client.Network, startBlock uint64) {
fmt.Printf("\n*** Start chaincode event replay\n")
events, err := network.ChaincodeEvents(ctx, chaincodeName, client.WithStartBlock(startBlock))
if err != nil {
panic(fmt.Errorf("failed to start chaincode event listening: %w", err))
}
for event := range events {
asset := formatJSON(event.Payload)
fmt.Printf("\n<-- Chaincode event replayed: %s - %s\n", event.EventName, asset)
if event.EventName == "DeleteAsset" {
break
}
}
}