Skip to content

Commit

Permalink
FABG-936 Support minimal ccp for fabric samples (hyperledger#74)
Browse files Browse the repository at this point in the history
The Fabcar sample in fabric-samples contains a minimal CCP definition with just a single gateway peer entry and no channel or orderer information.  It is also written to be run on a single dev system whereby hostnames need to be translated to localhost.  This commit adds support to the gateway package for:
- automatic creation of channel definition on invocation of `getNetwork(channelName)`
- support for translation of discovered peers/orderers to localhost via environment variable (DISCOVERY_AS_LOCALHOST)
Both of these align the behaviour with the Node and Java SDKs and are required to implement the Fabcar sample.

Signed-off-by: andrew-coleman <[email protected]>
  • Loading branch information
andrew-coleman authored May 26, 2020
1 parent 5fe41b9 commit 219a09a
Show file tree
Hide file tree
Showing 4 changed files with 303 additions and 123 deletions.
249 changes: 190 additions & 59 deletions pkg/gateway/gateway.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,28 +7,36 @@ SPDX-License-Identifier: Apache-2.0
package gateway

import (
"fmt"
"os"
"strings"
"time"

"github.com/hyperledger/fabric-sdk-go/pkg/client/msp"
fabricCaUtil "github.com/hyperledger/fabric-sdk-go/internal/github.com/hyperledger/fabric-ca/util"
"github.com/hyperledger/fabric-sdk-go/pkg/common/providers/context"
"github.com/hyperledger/fabric-sdk-go/pkg/common/providers/core"
"github.com/hyperledger/fabric-sdk-go/pkg/common/providers/fab"
mspProvider "github.com/hyperledger/fabric-sdk-go/pkg/common/providers/msp"
"github.com/hyperledger/fabric-sdk-go/pkg/core/cryptosuite"
"github.com/hyperledger/fabric-sdk-go/pkg/fabsdk"
"github.com/hyperledger/fabric-sdk-go/pkg/fabsdk/api"
"github.com/pkg/errors"
)

const (
defaultTimeout = 5 * time.Minute
defaultDiscovery = true
defaultTimeout = 5 * time.Minute
defaultDiscovery = true
localhostEnvVarName = "DISCOVERY_AS_LOCALHOST"
)

// Gateway is the entry point to a Fabric network
type Gateway struct {
sdk *fabsdk.FabricSDK
options *gatewayOptions
cfg core.ConfigBackend
org string
sdk *fabsdk.FabricSDK
options *gatewayOptions
cfg core.ConfigBackend
org string
mspid string
peers []fab.PeerConfig
mspfactory api.MSPProviderFactory
}

type gatewayOptions struct {
Expand Down Expand Up @@ -58,14 +66,14 @@ func Connect(config ConfigOption, identity IdentityOption, options ...Option) (*
},
}

err := config(g)
err := identity(g)
if err != nil {
return nil, errors.Wrap(err, "Failed to apply config option")
return nil, errors.Wrap(err, "Failed to apply identity option")
}

err = identity(g)
err = config(g)
if err != nil {
return nil, errors.Wrap(err, "Failed to apply identity option")
return nil, errors.Wrap(err, "Failed to apply config option")
}

for _, option := range options {
Expand All @@ -81,14 +89,7 @@ func Connect(config ConfigOption, identity IdentityOption, options ...Option) (*
// WithConfig configures the gateway from a network config, such as a ccp file.
func WithConfig(config core.ConfigProvider) ConfigOption {
return func(gw *Gateway) error {
var err error
sdk, err := fabsdk.New(config)

if err != nil {
return err
}

gw.sdk = sdk
config = createGatewayConfigProvider(config, gw.getOrg)

configBackend, err := config()
if err != nil {
Expand All @@ -98,15 +99,38 @@ func WithConfig(config core.ConfigProvider) ConfigOption {
return errors.New("invalid config file")
}

cfg := configBackend[0]
gw.cfg = cfg
gw.cfg = configBackend[0]

value, ok := cfg.Lookup("client.organization")
value, ok := gw.cfg.Lookup("client.organization")
if !ok {
return errors.New("No client organization defined in the config")
}
gw.org = value.(string)

value, ok = gw.cfg.Lookup("organizations." + gw.org + ".mspid")
if !ok {
return errors.New("No client organization defined in the config")
}
gw.mspid = value.(string)

opts := []fabsdk.Option{}
if gw.mspfactory != nil {
opts = append(opts, fabsdk.WithMSPPkg(gw.mspfactory))
}

sdk, err := fabsdk.New(config, opts...)

if err != nil {
return err
}

gw.sdk = sdk

// find the 'gateway' peers
ctx := sdk.Context()
client, _ := ctx()
gw.peers, _ = client.EndpointConfig().PeersConfig(gw.org)

return nil
}
}
Expand Down Expand Up @@ -137,26 +161,22 @@ func WithSDK(sdk *fabsdk.FabricSDK) ConfigOption {
// All operations under this gateway connection will be performed using this identity.
func WithIdentity(wallet wallet, label string) IdentityOption {
return func(gw *Gateway) error {
mspClient, err := msp.New(gw.getSDK().Context(), msp.WithOrg(gw.getOrg()))
if err != nil {
return err
}

creds, err := wallet.Get(label)
if err != nil {
return err
}

var identity mspProvider.SigningIdentity
switch v := creds.(type) {
case *X509Identity:
identity, err = mspClient.CreateSigningIdentity(mspProvider.WithCert([]byte(v.Certificate())), mspProvider.WithPrivateKey([]byte(v.Key())))
if err != nil {
return err
}
privateKey, _ := fabricCaUtil.ImportBCCSPKeyFromPEMBytes([]byte(creds.(*X509Identity).Key()), cryptosuite.GetDefault(), true)
wid := &walletIdentity{
id: label,
mspID: creds.mspID(),
enrollmentCertificate: []byte(creds.(*X509Identity).Certificate()),
privateKey: privateKey,
}

gw.options.Identity = identity
gw.options.Identity = wid
gw.mspfactory = &walletmsp{}

return nil
}
}
Expand Down Expand Up @@ -189,29 +209,6 @@ func WithTimeout(timeout time.Duration) Option {
}
}

func (gw *Gateway) getSDK() *fabsdk.FabricSDK {
return gw.sdk
}

func (gw *Gateway) getOrg() string {
return gw.org
}

func (gw *Gateway) getPeersForOrg(org string) ([]string, error) {
value, ok := gw.cfg.Lookup("organizations." + org + ".peers")
if !ok {
return nil, errors.New("No client organization defined in the config")
}

val := value.([]interface{})
s := make([]string, len(val))
for i, v := range val {
s[i] = fmt.Sprint(v)
}

return s, nil
}

// GetNetwork returns an object representing a network channel.
func (gw *Gateway) GetNetwork(name string) (*Network, error) {
var channelProvider context.ChannelProvider
Expand All @@ -228,3 +225,137 @@ func (gw *Gateway) GetNetwork(name string) (*Network, error) {
func (gw *Gateway) Close() {
// future use
}

func (gw *Gateway) getOrg() string {
return gw.org
}

func createGatewayConfigProvider(config core.ConfigProvider, org func() string) func() ([]core.ConfigBackend, error) {
return func() ([]core.ConfigBackend, error) {
configBackend, err := config()
if err != nil {
return nil, err
}
if len(configBackend) != 1 {
return nil, errors.New("invalid config file")
}

cfg := configBackend[0]

lhConfig := make([]core.ConfigBackend, 0)
lhConfig = append(lhConfig, createGatewayConfig(cfg, org()))

return lhConfig, nil
}
}

func createGatewayConfig(backend core.ConfigBackend, org string) *gatewayConfig {
var matchers map[string][]map[string]string
if strings.ToUpper(os.Getenv(localhostEnvVarName)) == "TRUE" {
matchers = createLocalhostMappings()
}

var channelConfig map[string]map[string]map[string]map[string]bool
_, exists := backend.Lookup("channels")
if !exists {
channelConfig = createDefaultChannelConfig(backend, org)
}

return &gatewayConfig{
backend: backend,
matchers: matchers,
channelDef: channelConfig,
}
}

/* dynamically add the following to CCP:
entityMatchers:
peer:
- pattern: ([^:]+):(\\d+)
urlSubstitutionExp: localhost:${2}
sslTargetOverrideUrlSubstitutionExp: ${1}
mappedHost: ${1}
peer:
- pattern: ([^:]+):(\\d+)
urlSubstitutionExp: localhost:${2}
sslTargetOverrideUrlSubstitutionExp: localhost
mappedHost: ${1}
*/
func createLocalhostMappings() map[string][]map[string]string {
matchers := make(map[string][]map[string]string)
peerMappings := make([]map[string]string, 0)
ordererMappings := make([]map[string]string, 0)
mappedHost := "${1}"

peerMapping := make(map[string]string)
peerMapping["pattern"] = "([^:]+):(\\d+)"
peerMapping["urlSubstitutionExp"] = "localhost:${2}"
peerMapping["sslTargetOverrideUrlSubstitutionExp"] = mappedHost
peerMapping["mappedHost"] = mappedHost
peerMappings = append(peerMappings, peerMapping)

matchers["peer"] = peerMappings

ordererMapping := make(map[string]string)
ordererMapping["pattern"] = "([^:]+):(\\d+)"
ordererMapping["urlSubstitutionExp"] = "localhost:${2}"
ordererMapping["sslTargetOverrideUrlSubstitutionExp"] = "localhost"
ordererMapping["mappedHost"] = mappedHost
ordererMappings = append(ordererMappings, ordererMapping)

matchers["orderer"] = ordererMappings
return matchers
}

/* dynamically add the following to CCP:
channels:
_default:
peers:
<gateway_peer_name>:
endorsingPeer: true
chaincodeQuery: true
ledgerQuery: true
eventSource: true
*/
func createDefaultChannelConfig(backend core.ConfigBackend, org string) map[string]map[string]map[string]map[string]bool {
channels := make(map[string]map[string]map[string]map[string]bool)
_default := make(map[string]map[string]map[string]bool)
gateways := make(map[string]map[string]bool)
roles := make(map[string]bool)
roles["endorsingPeer"] = true
roles["chaincodeQuery"] = true
roles["ledgerQuery"] = true
roles["eventSource"] = true

value, ok := backend.Lookup("organizations." + org + ".peers")
if !ok {
return nil
}
arr := value.([]interface{})
for _, gatewayPeer := range arr {
gateways[gatewayPeer.(string)] = roles
}

_default["peers"] = gateways
channels["_default"] = _default
return channels
}

type gatewayConfig struct {
backend core.ConfigBackend
matchers map[string][]map[string]string
channelDef map[string]map[string]map[string]map[string]bool
}

func (gc *gatewayConfig) Lookup(key string) (interface{}, bool) {
if key == "entityMatchers" && gc.matchers != nil {
return gc.matchers, true
}
conf, exists := gc.backend.Lookup(key)
if key == "channels" && gc.channelDef != nil {
return gc.channelDef, true
}
return conf, exists
}
Loading

0 comments on commit 219a09a

Please sign in to comment.