mirror of
https://github.com/hyperledger/fabric-samples.git
synced 2026-06-17 15:35:09 +00:00
245 lines
6.7 KiB
Go
Executable file
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
|
|
}
|
|
}
|
|
}
|