Skip to content

Commit

Permalink
Update to use fabric-gateway
Browse files Browse the repository at this point in the history
Signed-off-by: James Taylor <jamest@uk.ibm.com>
  • Loading branch information
jt-nti committed Nov 10, 2023
1 parent 16c4aaf commit 7242f45
Show file tree
Hide file tree
Showing 5 changed files with 189 additions and 287 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/go.yml
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ jobs:
- name: Set up Go
uses: actions/setup-go@v2
with:
go-version: 1.17
go-version: 1.20

- name: Build
run: go build -v ./...
Expand Down
22 changes: 7 additions & 15 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,34 +19,26 @@ go install ./...
## Usage

```
Usage: ccmetadata --cert=<path> --key=<path> --mspid=<name> --connection=<path> --channel=<name> [--aslocalhost] [--verbose] <chaincode>
Usage: ccmetadata --gateway=<address> --cert=<path> --key=<path> --mspid=<name> --channel=<name> [--tlscert=<path>] [--override=<hostname>] [--aslocalhost] [--verbose] <chaincode>
Get metadata for the specified chaincode name
-g, --gateway string
gateway peer address
-c, --cert string
certificate file
-k, --key string
private key file
-m, --mspid string
membership service provider name, e.g. Org1MSP
-p, --connection string
connection profile file
-n, --channel string
channel name, e.g. mychannel
-t, --tlscert string
tls certificate file
-o, --override string
server name override
-l, --aslocalhost
use discovery service as localhost
-v, --verbose
enable verbose logging
```

For example, in the _fabric-samples/test-network_ directory:

```
ccmetadata --cert=organizations/peerOrganizations/org1.example.com/users/User1@org1.example.com/msp/signcerts/User1@org1.example.com-cert.pem \
--key=organizations/peerOrganizations/org1.example.com/users/User1@org1.example.com/msp/keystore/priv_sk \
--mspid=Org1MSP \
--connection=organizations/peerOrganizations/org1.example.com/connection-org1.yaml \
--channel=mychannel \
--aslocalhost \
basic
```
180 changes: 133 additions & 47 deletions cmd/ccmetadata/main.go
Original file line number Diff line number Diff line change
@@ -1,70 +1,83 @@
package main

import (
"bytes"
"crypto/x509"
"encoding/json"
"flag"
"fmt"
"io/ioutil"
"log"
"os"
"path/filepath"
"time"

"github.com/hyperledger/fabric-sdk-go/pkg/common/logging"
"github.com/hyperledger/fabric-sdk-go/pkg/core/config"
"github.com/hyperledger/fabric-sdk-go/pkg/gateway"
"github.com/hyperledger/fabric-gateway/pkg/client"
"github.com/hyperledger/fabric-gateway/pkg/identity"
"google.golang.org/grpc"
"google.golang.org/grpc/credentials"
"google.golang.org/grpc/credentials/insecure"
)

func usage() {
fmt.Fprintf(os.Stderr, "Usage: ccmetadata --cert=<path> --key=<path> --mspid=<name> --connection=<path> --channel=<name> [--aslocalhost] [--verbose] <chaincode>\n\n")
fmt.Fprintf(os.Stderr, "Usage: ccmetadata --gateway=<address> --cert=<path> --key=<path> --mspid=<name> --channel=<name> [--tlscert=<path>] [--override=<hostname>] [--aslocalhost] [--verbose] <chaincode>\n\n")

fmt.Fprintf(os.Stderr, "Get metadata for the specified chaincode name\n\n")

fmt.Fprintf(os.Stderr, " -g, --gateway string\n gateway peer address\n")
fmt.Fprintf(os.Stderr, " -c, --cert string\n certificate file\n")
fmt.Fprintf(os.Stderr, " -k, --key string\n private key file\n")
fmt.Fprintf(os.Stderr, " -m, --mspid string\n membership service provider name, e.g. Org1MSP\n")
fmt.Fprintf(os.Stderr, " -p, --connection string\n connection profile file\n")
fmt.Fprintf(os.Stderr, " -n, --channel string\n channel name, e.g. mychannel\n")
fmt.Fprintf(os.Stderr, " -t, --tlscert string\n tls certificate file\n")
fmt.Fprintf(os.Stderr, " -o, --override string\n server name override\n")
fmt.Fprintf(os.Stderr, " -l, --aslocalhost\n use discovery service as localhost\n")
fmt.Fprintf(os.Stderr, " -v, --verbose\n enable verbose logging\n")
}

func main() {
var gatewayAddress string
var certPath string
var keyPath string
var mspid string
var ccpPath string
var channelName string
var tlsCertPath string
var serverNameOverride string
var aslocalhost bool
var verbose bool

flag.Usage = usage

flag.StringVar(&gatewayAddress, "gateway", "", "gateway peer address")
flag.StringVar(&gatewayAddress, "g", "", "gateway peer address")
flag.StringVar(&certPath, "cert", "", "certificate file")
flag.StringVar(&certPath, "c", "", "certificate file")
flag.StringVar(&keyPath, "key", "", "private key file")
flag.StringVar(&keyPath, "k", "", "private key file")
flag.StringVar(&mspid, "mspid", "", "membership service provider name, e.g. Org1MSP")
flag.StringVar(&mspid, "m", "", "membership service provider name, e.g. Org1MSP")
flag.StringVar(&ccpPath, "connection", "", "connection profile file")
flag.StringVar(&ccpPath, "p", "", "connection profile file")
flag.StringVar(&channelName, "channel", "", "channel name, e.g. mychannel")
flag.StringVar(&channelName, "n", "", "channel name, e.g. mychannel")
flag.StringVar(&tlsCertPath, "tlscert", "", "tls certificate file")
flag.StringVar(&tlsCertPath, "t", "", "tls certificate file")
flag.StringVar(&serverNameOverride, "override", "", "server name override")
flag.StringVar(&serverNameOverride, "o", "", "server name override")
flag.BoolVar(&aslocalhost, "aslocalhost", false, "use discovery service as localhost")
flag.BoolVar(&aslocalhost, "l", false, "use discovery service as localhost")
flag.BoolVar(&verbose, "verbose", false, "enable verbose logging")
flag.BoolVar(&verbose, "v", false, "enable verbose logging")

flag.Parse()

required := []string{"cert", "key", "mspid", "connection", "channel"}
provided := make(map[string]bool)
flag.Visit(func(f *flag.Flag) { provided[f.Name] = true })
for _, r := range required {
if !provided[r] {
fmt.Fprintf(os.Stderr, "flag required but not provided: -%s\n", r)
usage()
os.Exit(2)
}
}
// TODO this doesn't handle short options
// required := []string{"gateway", "cert", "key", "mspid", "channel"}
// provided := make(map[string]bool)
// flag.Visit(func(f *flag.Flag) { provided[f.Name] = true })
// for _, r := range required {
// if !provided[r] {
// fmt.Fprintf(os.Stderr, "flag required but not provided: -%s\n", r)
// usage()
// os.Exit(2)
// }
// }

if len(flag.Args()) == 0 {
fmt.Fprintln(os.Stderr, "argument required but not provided: chaincode")
Expand All @@ -73,69 +86,142 @@ func main() {
}
chaincodeName := flag.Arg(0)

// TODO validate that the following arguments have been provided...
// gatewayAddress
// certPath
// keyPath
// mspid
// channel

if verbose {
fmt.Printf("Gateway address: %s\n", gatewayAddress)
fmt.Printf("Certificate file: %s\n", certPath)
fmt.Printf("Private key file: %s\n", keyPath)
fmt.Printf("MSP ID: %s\n", mspid)
fmt.Printf("Channel name: %s\n", channelName)
fmt.Printf("TLS certificate file: %s\n", tlsCertPath)
fmt.Printf("Server name override: %s\n", serverNameOverride)
fmt.Printf("As localhost option: %t\n", aslocalhost)
fmt.Printf("Chaincode name: %s\n", chaincodeName)
} else {
logging.SetLevel("", logging.ERROR)
}

if aslocalhost {
err := os.Setenv("DISCOVERY_AS_LOCALHOST", "true")
if err != nil {
log.Fatalf("Failed to set DISCOVERY_AS_LOCALHOST environment variable: %v", err)
panic(fmt.Errorf("failed to set DISCOVERY_AS_LOCALHOST environment variable: %w", err))
}
}

wallet, err := createWallet(certPath, keyPath, mspid)
clientConnection := newGrpcConnection(gatewayAddress, tlsCertPath, serverNameOverride)
defer clientConnection.Close()

id := newIdentity(certPath, mspid)
sign := newSign(keyPath)

gw, 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 {
log.Fatalf("Failed to get credentials: %v", err)
panic(fmt.Errorf("failed to connect to Fabric Gateway: %w", err))
}
defer gw.Close()

connectionConfig := config.FromFile(filepath.Clean(ccpPath))
network := gw.GetNetwork(channelName)
contract := network.GetContract(chaincodeName)

gateway, err := gateway.Connect(
gateway.WithConfig(connectionConfig),
gateway.WithIdentity(wallet, "identity"),
)
evaluateResult, err := contract.EvaluateTransaction("org.hyperledger.fabric:GetMetadata")
if err != nil {
log.Fatalf("Failed to connect to gateway: %v", err)
panic(fmt.Errorf("failed to evaluate transaction: %w", err))
}
defer gateway.Close()
result := formatJSON(evaluateResult)
fmt.Print(result)
}

network, err := gateway.GetNetwork(channelName)
// newGrpcConnection creates a gRPC connection to the Gateway server.
func newGrpcConnection(gatewayAddress, tlsCertPath, serverNameOverride string) *grpc.ClientConn {
var connection *grpc.ClientConn
var err error

if len(tlsCertPath) == 0 {
connection, err = grpc.Dial(gatewayAddress, grpc.WithTransportCredentials(insecure.NewCredentials()))
if err != nil {
panic(fmt.Errorf("failed to create insecure gRPC connection: %w", err))
}
} else {
certificate := loadCertificate(tlsCertPath)

certPool := x509.NewCertPool()
certPool.AddCert(certificate)
transportCredentials := credentials.NewClientTLSFromCert(certPool, serverNameOverride)

connection, err = grpc.Dial(gatewayAddress, grpc.WithTransportCredentials(transportCredentials))
if err != nil {
panic(fmt.Errorf("failed to create secure gRPC connection: %w", err))
}
}

return connection
}

// newIdentity creates a client identity for this Gateway connection using an X.509 certificate.
func newIdentity(certPath, mspID string) *identity.X509Identity {
certificate := loadCertificate(certPath)

id, err := identity.NewX509Identity(mspID, certificate)
if err != nil {
log.Fatalf("Failed to get network: %v", err)
panic(fmt.Errorf("failed to create identity from certificate: %w", err))
}

contract := network.GetContract(chaincodeName)
return id
}

result, err := contract.EvaluateTransaction("org.hyperledger.fabric:GetMetadata")
// loadCertificate loads a certificate from a PEM file.
func loadCertificate(filename string) *x509.Certificate {
certificatePEM, err := os.ReadFile(filepath.Clean(filename))
if err != nil {
log.Fatalf("Failed to evaluate transaction: %v", err)
panic(fmt.Errorf("failed to read certificate file: %w", err))
}
fmt.Println(string(result))

certificate, err := identity.CertificateFromPEM(certificatePEM)
if err != nil {
panic(fmt.Errorf("failed to create certificate from file contents: %w", err))
}

return certificate
}

func createWallet(certPath, keyPath, mspid string) (*gateway.Wallet, error) {
wallet := gateway.NewInMemoryWallet()
// newSign creates a function that generates a digital signature from a message digest using a private key.
func newSign(keyPath string) identity.Sign {
privateKeyPEM, err := os.ReadFile(filepath.Clean(keyPath))
if err != nil {
panic(fmt.Errorf("failed to read private key file: %w", err))
}

cert, err := ioutil.ReadFile(filepath.Clean(certPath))
privateKey, err := identity.PrivateKeyFromPEM(privateKeyPEM)
if err != nil {
return wallet, err
panic(fmt.Errorf("failed to create private key from file contents: %w", err))
}

key, err := ioutil.ReadFile(filepath.Clean(keyPath))
sign, err := identity.NewPrivateKeySign(privateKey)
if err != nil {
return wallet, err
panic(fmt.Errorf("failed to create signing function from private key: %w", err))
}

identity := gateway.NewX509Identity(mspid, string(cert), string(key))
wallet.Put("identity", identity)
return sign
}

return wallet, nil
// 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()
}
19 changes: 17 additions & 2 deletions go.mod
Original file line number Diff line number Diff line change
@@ -1,5 +1,20 @@
module github.com/hyperledgendary/ccmetadata

go 1.14
go 1.20

require github.com/hyperledger/fabric-sdk-go v1.0.0
require (
github.com/hyperledger/fabric-gateway v1.4.0
google.golang.org/grpc v1.59.0
)

require (
github.com/golang/protobuf v1.5.3 // indirect
github.com/hyperledger/fabric-protos-go-apiv2 v0.2.1 // indirect
github.com/miekg/pkcs11 v1.1.1 // indirect
golang.org/x/crypto v0.14.0 // indirect
golang.org/x/net v0.17.0 // indirect
golang.org/x/sys v0.13.0 // indirect
golang.org/x/text v0.13.0 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20231016165738-49dd2c1f3d0b // indirect
google.golang.org/protobuf v1.31.0 // indirect
)
Loading

0 comments on commit 7242f45

Please sign in to comment.