1953 lines
62 KiB
Go
1953 lines
62 KiB
Go
/*
|
|
Copyright IBM Corp. All Rights Reserved.
|
|
|
|
SPDX-License-Identifier: Apache-2.0
|
|
*/
|
|
|
|
package nwo
|
|
|
|
import (
|
|
"bytes"
|
|
"context"
|
|
"fmt"
|
|
"io"
|
|
"io/ioutil"
|
|
"net"
|
|
"os"
|
|
"os/exec"
|
|
"path/filepath"
|
|
"runtime"
|
|
"sort"
|
|
"strings"
|
|
"sync"
|
|
"syscall"
|
|
"text/template"
|
|
"time"
|
|
|
|
docker "github.com/fsouza/go-dockerclient"
|
|
"github.com/golang/protobuf/proto"
|
|
"github.com/hyperledger/fabric-protos-go/common"
|
|
pb "github.com/hyperledger/fabric-protos-go/peer"
|
|
"github.com/hyperledger/fabric/integration/nwo/commands"
|
|
"github.com/hyperledger/fabric/integration/nwo/fabricconfig"
|
|
"github.com/hyperledger/fabric/integration/nwo/runner"
|
|
"github.com/hyperledger/fabric/protoutil"
|
|
ginkgo "github.com/onsi/ginkgo/v2"
|
|
. "github.com/onsi/gomega"
|
|
"github.com/onsi/gomega/gbytes"
|
|
"github.com/onsi/gomega/gexec"
|
|
"github.com/onsi/gomega/gstruct"
|
|
"github.com/onsi/gomega/types"
|
|
"github.com/tedsuo/ifrit"
|
|
ginkgomon "github.com/tedsuo/ifrit/ginkgomon_v2"
|
|
"github.com/tedsuo/ifrit/grouper"
|
|
"google.golang.org/grpc"
|
|
"google.golang.org/grpc/credentials"
|
|
"gopkg.in/yaml.v2"
|
|
)
|
|
|
|
// Blocks defines block cutting config.
|
|
type Blocks struct {
|
|
BatchTimeout int `yaml:"batch_timeout,omitempty"`
|
|
MaxMessageCount int `yaml:"max_message_count,omitempty"`
|
|
AbsoluteMaxBytes int `yaml:"absolute_max_bytes,omitempty"`
|
|
PreferredMaxBytes int `yaml:"preferred_max_bytes,omitempty"`
|
|
}
|
|
|
|
// Organization models information about an Organization. It includes
|
|
// the information needed to populate an MSP with cryptogen.
|
|
type Organization struct {
|
|
MSPID string `yaml:"msp_id,omitempty"`
|
|
MSPType string `yaml:"msp_type,omitempty"`
|
|
Name string `yaml:"name,omitempty"`
|
|
Domain string `yaml:"domain,omitempty"`
|
|
EnableNodeOUs bool `yaml:"enable_node_organizational_units"`
|
|
Users int `yaml:"users,omitempty"`
|
|
CA *CA `yaml:"ca,omitempty"`
|
|
}
|
|
|
|
type CA struct {
|
|
Hostname string `yaml:"hostname,omitempty"`
|
|
}
|
|
|
|
// Consensus indicates the orderer types.
|
|
type Consensus struct {
|
|
Type string `yaml:"type,omitempty"`
|
|
}
|
|
|
|
// Channel associates a channel name with a configtxgen profile name.
|
|
type Channel struct {
|
|
Name string `yaml:"name,omitempty"`
|
|
Profile string `yaml:"profile,omitempty"`
|
|
BaseProfile string `yaml:"baseprofile,omitempty"`
|
|
}
|
|
|
|
// Orderer defines an orderer instance and its owning organization.
|
|
type Orderer struct {
|
|
Name string `yaml:"name,omitempty"`
|
|
Organization string `yaml:"organization,omitempty"`
|
|
Id int `yaml:"id,omitempty"`
|
|
}
|
|
|
|
// ID provides a unique identifier for an orderer instance.
|
|
func (o Orderer) ID() string {
|
|
return fmt.Sprintf("%s.%s", o.Organization, o.Name)
|
|
}
|
|
|
|
// Peer defines a peer instance, it's owning organization, and the list of
|
|
// channels that the peer should be joined to.
|
|
type Peer struct {
|
|
Name string `yaml:"name,omitempty"`
|
|
DevMode bool `yaml:"devmode,omitempty"`
|
|
Organization string `yaml:"organization,omitempty"`
|
|
Channels []*PeerChannel `yaml:"channels,omitempty"`
|
|
}
|
|
|
|
// PeerChannel names of the channel a peer should be joined to and whether or
|
|
// not the peer should be an anchor for the channel.
|
|
type PeerChannel struct {
|
|
Name string `yaml:"name,omitempty"`
|
|
Anchor bool `yaml:"anchor"`
|
|
}
|
|
|
|
// ID provides a unique identifier for a peer instance.
|
|
func (p *Peer) ID() string {
|
|
return fmt.Sprintf("%s.%s", p.Organization, p.Name)
|
|
}
|
|
|
|
// Anchor returns true if this peer is an anchor for any channel it has joined.
|
|
func (p *Peer) Anchor() bool {
|
|
for _, c := range p.Channels {
|
|
if c.Anchor {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
// A profile encapsulates basic information for a configtxgen profile.
|
|
type Profile struct {
|
|
Name string `yaml:"name,omitempty"`
|
|
Orderers []string `yaml:"orderers,omitempty"`
|
|
Consortium string `yaml:"consortium,omitempty"`
|
|
Organizations []string `yaml:"organizations,omitempty"`
|
|
AppCapabilities []string `yaml:"app_capabilities,omitempty"`
|
|
ChannelCapabilities []string `yaml:"channel_capabilities,omitempty"`
|
|
Blocks *Blocks `yaml:"blocks,omitempty"`
|
|
}
|
|
|
|
// Network holds information about a fabric network.
|
|
type Network struct {
|
|
RootDir string
|
|
StartPort uint16
|
|
Components *Components
|
|
DockerClient *docker.Client
|
|
ExternalBuilders []fabricconfig.ExternalBuilder
|
|
NetworkID string
|
|
EventuallyTimeout time.Duration
|
|
SessionCreateInterval time.Duration
|
|
MetricsProvider string
|
|
StatsdEndpoint string
|
|
ClientAuthRequired bool
|
|
TLSEnabled bool
|
|
GatewayEnabled bool
|
|
|
|
PortsByOrdererID map[string]Ports
|
|
PortsByPeerID map[string]Ports
|
|
Organizations []*Organization
|
|
Channels []*Channel
|
|
Consensus *Consensus
|
|
Orderers []*Orderer
|
|
Peers []*Peer
|
|
Profiles []*Profile
|
|
Templates *Templates
|
|
|
|
mutex sync.Locker
|
|
colorIndex uint
|
|
lastExecuted map[string]time.Time
|
|
}
|
|
|
|
// New creates a Network from a simple configuration. All generated or managed
|
|
// artifacts for the network will be located under rootDir. Ports will be
|
|
// allocated sequentially from the specified startPort.
|
|
func New(c *Config, rootDir string, dockerClient *docker.Client, startPort int, components *Components) *Network {
|
|
network := &Network{
|
|
StartPort: uint16(startPort),
|
|
RootDir: rootDir,
|
|
Components: components,
|
|
DockerClient: dockerClient,
|
|
|
|
NetworkID: runner.UniqueName(),
|
|
EventuallyTimeout: time.Minute,
|
|
MetricsProvider: "prometheus",
|
|
PortsByOrdererID: map[string]Ports{},
|
|
PortsByPeerID: map[string]Ports{},
|
|
|
|
Organizations: c.Organizations,
|
|
Consensus: c.Consensus,
|
|
Orderers: c.Orderers,
|
|
Peers: c.Peers,
|
|
Channels: c.Channels,
|
|
Profiles: c.Profiles,
|
|
Templates: c.Templates,
|
|
TLSEnabled: true, // Set TLS enabled as true for default
|
|
GatewayEnabled: true, // Set Gateway enabled as true for default
|
|
|
|
mutex: &sync.Mutex{},
|
|
lastExecuted: make(map[string]time.Time),
|
|
}
|
|
|
|
// add the ccaas builder as well; that is built into the release directory
|
|
// so work that out based on current runtime.
|
|
// make integration-preqreqs have been updated to ensure this is built
|
|
cwd, err := os.Getwd()
|
|
Expect(err).NotTo(HaveOccurred())
|
|
network.ExternalBuilders = []fabricconfig.ExternalBuilder{{
|
|
Path: filepath.Join(cwd, "..", "externalbuilders", "binary"),
|
|
Name: "binary",
|
|
PropagateEnvironment: []string{"GOPROXY"},
|
|
}, {
|
|
Path: filepath.Join(cwd, "..", "..", "release", fmt.Sprintf("%s-%s", runtime.GOOS, runtime.GOARCH), "builders", "ccaas"),
|
|
Name: "ccaas",
|
|
PropagateEnvironment: []string{"CHAINCODE_AS_A_SERVICE_BUILDER_CONFIG"},
|
|
}}
|
|
|
|
if network.Templates == nil {
|
|
network.Templates = &Templates{}
|
|
}
|
|
if network.SessionCreateInterval == 0 {
|
|
network.SessionCreateInterval = time.Second
|
|
}
|
|
|
|
for _, o := range c.Orderers {
|
|
ports := Ports{}
|
|
for _, portName := range OrdererPortNames() {
|
|
ports[portName] = network.ReservePort()
|
|
}
|
|
network.PortsByOrdererID[o.ID()] = ports
|
|
}
|
|
|
|
for _, p := range c.Peers {
|
|
ports := Ports{}
|
|
for _, portName := range PeerPortNames() {
|
|
ports[portName] = network.ReservePort()
|
|
}
|
|
network.PortsByPeerID[p.ID()] = ports
|
|
}
|
|
|
|
if dockerClient != nil {
|
|
assertImagesExist(dockerClient, RequiredImages...)
|
|
}
|
|
|
|
return network
|
|
}
|
|
|
|
func assertImagesExist(dockerClient *docker.Client, images ...string) {
|
|
for _, imageName := range images {
|
|
images, err := dockerClient.ListImages(docker.ListImagesOptions{
|
|
Filters: map[string][]string{"reference": {imageName}},
|
|
})
|
|
Expect(err).NotTo(HaveOccurred())
|
|
|
|
if len(images) != 1 {
|
|
ginkgo.Fail(fmt.Sprintf("missing required image: %s", imageName), 1)
|
|
}
|
|
}
|
|
}
|
|
|
|
// AddOrg adds an organization to a network.
|
|
func (n *Network) AddOrg(o *Organization, peers ...*Peer) {
|
|
for _, p := range peers {
|
|
ports := Ports{}
|
|
for _, portName := range PeerPortNames() {
|
|
ports[portName] = n.ReservePort()
|
|
}
|
|
n.PortsByPeerID[p.ID()] = ports
|
|
n.Peers = append(n.Peers, p)
|
|
}
|
|
|
|
n.Organizations = append(n.Organizations, o)
|
|
}
|
|
|
|
// ConfigTxPath returns the path to the generated configtxgen configuration
|
|
// file.
|
|
func (n *Network) ConfigTxConfigPath() string {
|
|
return filepath.Join(n.RootDir, "configtx.yaml")
|
|
}
|
|
|
|
// CryptoPath returns the path to the directory where cryptogen will place its
|
|
// generated artifacts.
|
|
func (n *Network) CryptoPath() string {
|
|
return filepath.Join(n.RootDir, "crypto")
|
|
}
|
|
|
|
// CryptoConfigPath returns the path to the generated cryptogen configuration
|
|
// file.
|
|
func (n *Network) CryptoConfigPath() string {
|
|
return filepath.Join(n.RootDir, "crypto-config.yaml")
|
|
}
|
|
|
|
// OutputBlockPath returns the path to the genesis block for the named system
|
|
// channel.
|
|
func (n *Network) OutputBlockPath(channelName string) string {
|
|
return filepath.Join(n.RootDir, fmt.Sprintf("%s_block.pb", channelName))
|
|
}
|
|
|
|
// CreateChannelTxPath returns the path to the create channel transaction for
|
|
// the named channel.
|
|
func (n *Network) CreateChannelTxPath(channelName string) string {
|
|
return filepath.Join(n.RootDir, fmt.Sprintf("%s_tx.pb", channelName))
|
|
}
|
|
|
|
// OrdererDir returns the path to the configuration directory for the specified
|
|
// Orderer.
|
|
func (n *Network) OrdererDir(o *Orderer) string {
|
|
return filepath.Join(n.RootDir, "orderers", o.ID())
|
|
}
|
|
|
|
// OrdererConfigPath returns the path to the orderer configuration document for
|
|
// the specified Orderer.
|
|
func (n *Network) OrdererConfigPath(o *Orderer) string {
|
|
return filepath.Join(n.OrdererDir(o), "orderer.yaml")
|
|
}
|
|
|
|
// ReadOrdererConfig unmarshals an orderer's orderer.yaml and returns an
|
|
// object approximating its contents.
|
|
func (n *Network) ReadOrdererConfig(o *Orderer) *fabricconfig.Orderer {
|
|
var orderer fabricconfig.Orderer
|
|
ordererBytes, err := ioutil.ReadFile(n.OrdererConfigPath(o))
|
|
Expect(err).NotTo(HaveOccurred())
|
|
|
|
err = yaml.Unmarshal(ordererBytes, &orderer)
|
|
Expect(err).NotTo(HaveOccurred())
|
|
|
|
return &orderer
|
|
}
|
|
|
|
// WriteOrdererConfig serializes the provided configuration as the specified
|
|
// orderer's orderer.yaml document.
|
|
func (n *Network) WriteOrdererConfig(o *Orderer, config *fabricconfig.Orderer) {
|
|
ordererBytes, err := yaml.Marshal(config)
|
|
Expect(err).NotTo(HaveOccurred())
|
|
|
|
err = ioutil.WriteFile(n.OrdererConfigPath(o), ordererBytes, 0o644)
|
|
Expect(err).NotTo(HaveOccurred())
|
|
|
|
pw := gexec.NewPrefixedWriter(fmt.Sprintf("[updated-%s#orderer.yaml] ", o.ID()), ginkgo.GinkgoWriter)
|
|
_, err = pw.Write(ordererBytes)
|
|
Expect(err).NotTo(HaveOccurred())
|
|
}
|
|
|
|
// ReadConfigTxConfig unmarshals the configtx.yaml and returns an
|
|
// object approximating its contents.
|
|
func (n *Network) ReadConfigTxConfig() *fabricconfig.ConfigTx {
|
|
var configtx fabricconfig.ConfigTx
|
|
configtxBytes, err := ioutil.ReadFile(n.ConfigTxConfigPath())
|
|
Expect(err).NotTo(HaveOccurred())
|
|
|
|
err = yaml.Unmarshal(configtxBytes, &configtx)
|
|
Expect(err).NotTo(HaveOccurred())
|
|
|
|
return &configtx
|
|
}
|
|
|
|
// WriteConfigTxConfig serializes the provided configuration to configtx.yaml.
|
|
func (n *Network) WriteConfigTxConfig(config *fabricconfig.ConfigTx) {
|
|
configtxBytes, err := yaml.Marshal(config)
|
|
Expect(err).NotTo(HaveOccurred())
|
|
|
|
err = ioutil.WriteFile(n.ConfigTxConfigPath(), configtxBytes, 0o644)
|
|
Expect(err).NotTo(HaveOccurred())
|
|
}
|
|
|
|
// PeerDir returns the path to the configuration directory for the specified
|
|
// Peer.
|
|
func (n *Network) PeerDir(p *Peer) string {
|
|
return filepath.Join(n.RootDir, "peers", p.ID())
|
|
}
|
|
|
|
// PeerConfigPath returns the path to the peer configuration document for the
|
|
// specified peer.
|
|
func (n *Network) PeerConfigPath(p *Peer) string {
|
|
return filepath.Join(n.PeerDir(p), "core.yaml")
|
|
}
|
|
|
|
// PeerLedgerDir returns the ledger root directory for the specified peer.
|
|
func (n *Network) PeerLedgerDir(p *Peer) string {
|
|
return filepath.Join(n.PeerDir(p), "filesystem/ledgersData")
|
|
}
|
|
|
|
// ReadPeerConfig unmarshals a peer's core.yaml and returns an object
|
|
// approximating its contents.
|
|
func (n *Network) ReadPeerConfig(p *Peer) *fabricconfig.Core {
|
|
var core fabricconfig.Core
|
|
coreBytes, err := ioutil.ReadFile(n.PeerConfigPath(p))
|
|
Expect(err).NotTo(HaveOccurred())
|
|
|
|
err = yaml.Unmarshal(coreBytes, &core)
|
|
Expect(err).NotTo(HaveOccurred())
|
|
|
|
return &core
|
|
}
|
|
|
|
// WritePeerConfig serializes the provided configuration as the specified
|
|
// peer's core.yaml document.
|
|
func (n *Network) WritePeerConfig(p *Peer, config *fabricconfig.Core) {
|
|
coreBytes, err := yaml.Marshal(config)
|
|
Expect(err).NotTo(HaveOccurred())
|
|
|
|
err = ioutil.WriteFile(n.PeerConfigPath(p), coreBytes, 0o644)
|
|
Expect(err).NotTo(HaveOccurred())
|
|
|
|
pw := gexec.NewPrefixedWriter(fmt.Sprintf("[updated-%s#core.yaml] ", p.ID()), ginkgo.GinkgoWriter)
|
|
_, err = pw.Write(coreBytes)
|
|
Expect(err).NotTo(HaveOccurred())
|
|
}
|
|
|
|
// peerUserCryptoDir returns the path to the directory containing the
|
|
// certificates and keys for the specified user of the peer.
|
|
func (n *Network) peerUserCryptoDir(p *Peer, user, cryptoMaterialType string) string {
|
|
org := n.Organization(p.Organization)
|
|
Expect(org).NotTo(BeNil())
|
|
|
|
return n.userCryptoDir(org, "peerOrganizations", user, cryptoMaterialType)
|
|
}
|
|
|
|
// ordererUserCryptoDir returns the path to the directory containing the
|
|
// certificates and keys for the specified user of the orderer.
|
|
func (n *Network) ordererUserCryptoDir(o *Orderer, user, cryptoMaterialType string) string {
|
|
org := n.Organization(o.Organization)
|
|
Expect(org).NotTo(BeNil())
|
|
|
|
return n.userCryptoDir(org, "ordererOrganizations", user, cryptoMaterialType)
|
|
}
|
|
|
|
// userCryptoDir returns the path to the folder with crypto materials for either peers or orderer organizations
|
|
// specific user
|
|
func (n *Network) userCryptoDir(org *Organization, nodeOrganizationType, user, cryptoMaterialType string) string {
|
|
return filepath.Join(
|
|
n.RootDir,
|
|
"crypto",
|
|
nodeOrganizationType,
|
|
org.Domain,
|
|
"users",
|
|
fmt.Sprintf("%s@%s", user, org.Domain),
|
|
cryptoMaterialType,
|
|
)
|
|
}
|
|
|
|
// PeerOrgCADir returns the path to the folder containing the CA certificate(s) and/or
|
|
// keys for the specified peer organization.
|
|
func (n *Network) PeerOrgCADir(o *Organization) string {
|
|
return filepath.Join(
|
|
n.CryptoPath(),
|
|
"peerOrganizations",
|
|
o.Domain,
|
|
"ca",
|
|
)
|
|
}
|
|
|
|
// OrdererOrgCADir returns the path to the folder containing the CA certificate(s) and/or
|
|
// keys for the specified orderer organization.
|
|
func (n *Network) OrdererOrgCADir(o *Organization) string {
|
|
return filepath.Join(
|
|
n.CryptoPath(),
|
|
"ordererOrganizations",
|
|
o.Domain,
|
|
"ca",
|
|
)
|
|
}
|
|
|
|
// PeerUserMSPDir returns the path to the MSP directory containing the
|
|
// certificates and keys for the specified user of the peer.
|
|
func (n *Network) PeerUserMSPDir(p *Peer, user string) string {
|
|
return n.peerUserCryptoDir(p, user, "msp")
|
|
}
|
|
|
|
// IdemixUserMSPDir returns the path to the MSP directory containing the
|
|
// idemix-related crypto material for the specified user of the organization.
|
|
func (n *Network) IdemixUserMSPDir(o *Organization, user string) string {
|
|
return n.userCryptoDir(o, "peerOrganizations", user, "")
|
|
}
|
|
|
|
// OrdererUserMSPDir returns the path to the MSP directory containing the
|
|
// certificates and keys for the specified user of the peer.
|
|
func (n *Network) OrdererUserMSPDir(o *Orderer, user string) string {
|
|
return n.ordererUserCryptoDir(o, user, "msp")
|
|
}
|
|
|
|
// PeerUserTLSDir returns the path to the TLS directory containing the
|
|
// certificates and keys for the specified user of the peer.
|
|
func (n *Network) PeerUserTLSDir(p *Peer, user string) string {
|
|
return n.peerUserCryptoDir(p, user, "tls")
|
|
}
|
|
|
|
// PeerUserCert returns the path to the certificate for the specified user in
|
|
// the peer organization.
|
|
func (n *Network) PeerUserCert(p *Peer, user string) string {
|
|
org := n.Organization(p.Organization)
|
|
Expect(org).NotTo(BeNil())
|
|
|
|
return filepath.Join(
|
|
n.PeerUserMSPDir(p, user),
|
|
"signcerts",
|
|
fmt.Sprintf("%s@%s-cert.pem", user, org.Domain),
|
|
)
|
|
}
|
|
|
|
// PeerCACert returns the path to the CA certificate for the peer
|
|
// organization.
|
|
func (n *Network) PeerCACert(p *Peer) string {
|
|
org := n.Organization(p.Organization)
|
|
Expect(org).NotTo(BeNil())
|
|
|
|
return filepath.Join(
|
|
n.PeerOrgMSPDir(org),
|
|
"cacerts",
|
|
fmt.Sprintf("ca.%s-cert.pem", org.Domain),
|
|
)
|
|
}
|
|
|
|
// OrdererUserCert returns the path to the certificate for the specified user in
|
|
// the orderer organization.
|
|
func (n *Network) OrdererUserCert(o *Orderer, user string) string {
|
|
org := n.Organization(o.Organization)
|
|
Expect(org).NotTo(BeNil())
|
|
|
|
return filepath.Join(
|
|
n.OrdererUserMSPDir(o, user),
|
|
"signcerts",
|
|
fmt.Sprintf("%s@%s-cert.pem", user, org.Domain),
|
|
)
|
|
}
|
|
|
|
// OrdererCACert returns the path to the CA certificate for the orderer
|
|
// organization.
|
|
func (n *Network) OrdererCACert(o *Orderer) string {
|
|
org := n.Organization(o.Organization)
|
|
Expect(org).NotTo(BeNil())
|
|
|
|
return filepath.Join(
|
|
n.OrdererOrgMSPDir(org),
|
|
"cacerts",
|
|
fmt.Sprintf("ca.%s-cert.pem", org.Domain),
|
|
)
|
|
}
|
|
|
|
// PeerUserKey returns the path to the private key for the specified user in
|
|
// the peer organization.
|
|
func (n *Network) PeerUserKey(p *Peer, user string) string {
|
|
org := n.Organization(p.Organization)
|
|
Expect(org).NotTo(BeNil())
|
|
|
|
keystore := filepath.Join(
|
|
n.PeerUserMSPDir(p, user),
|
|
"keystore",
|
|
)
|
|
|
|
// file names are the SKI and non-deterministic
|
|
keys, err := ioutil.ReadDir(keystore)
|
|
Expect(err).NotTo(HaveOccurred())
|
|
Expect(keys).To(HaveLen(1))
|
|
|
|
return filepath.Join(keystore, keys[0].Name())
|
|
}
|
|
|
|
// OrdererUserKey returns the path to the private key for the specified user in
|
|
// the orderer organization.
|
|
func (n *Network) OrdererUserKey(o *Orderer, user string) string {
|
|
org := n.Organization(o.Organization)
|
|
Expect(org).NotTo(BeNil())
|
|
|
|
keystore := filepath.Join(
|
|
n.OrdererUserMSPDir(o, user),
|
|
"keystore",
|
|
)
|
|
|
|
// file names are the SKI and non-deterministic
|
|
keys, err := ioutil.ReadDir(keystore)
|
|
Expect(err).NotTo(HaveOccurred())
|
|
Expect(keys).To(HaveLen(1))
|
|
|
|
return filepath.Join(keystore, keys[0].Name())
|
|
}
|
|
|
|
// PeerUserSigner returns a SigningIdentity representing the specified user in
|
|
// the peer organization.
|
|
func (n *Network) PeerUserSigner(p *Peer, user string) *SigningIdentity {
|
|
return &SigningIdentity{
|
|
CertPath: n.PeerUserCert(p, user),
|
|
KeyPath: n.PeerUserKey(p, user),
|
|
MSPID: n.Organization(p.Organization).MSPID,
|
|
}
|
|
}
|
|
|
|
// OrdererUserSigner returns a SigningIdentity representing the specified user in
|
|
// the orderer organization.
|
|
func (n *Network) OrdererUserSigner(o *Orderer, user string) *SigningIdentity {
|
|
return &SigningIdentity{
|
|
CertPath: n.OrdererUserCert(o, user),
|
|
KeyPath: n.OrdererUserKey(o, user),
|
|
MSPID: n.Organization(o.Organization).MSPID,
|
|
}
|
|
}
|
|
|
|
// peerLocalCryptoDir returns the path to the local crypto directory for the peer.
|
|
func (n *Network) peerLocalCryptoDir(p *Peer, cryptoType string) string {
|
|
org := n.Organization(p.Organization)
|
|
Expect(org).NotTo(BeNil())
|
|
|
|
return filepath.Join(
|
|
n.RootDir,
|
|
"crypto",
|
|
"peerOrganizations",
|
|
org.Domain,
|
|
"peers",
|
|
fmt.Sprintf("%s.%s", p.Name, org.Domain),
|
|
cryptoType,
|
|
)
|
|
}
|
|
|
|
// PeerLocalMSPDir returns the path to the local MSP directory for the peer.
|
|
func (n *Network) PeerLocalMSPDir(p *Peer) string {
|
|
return n.peerLocalCryptoDir(p, "msp")
|
|
}
|
|
|
|
// PeerLocalTLSDir returns the path to the local TLS directory for the peer.
|
|
func (n *Network) PeerLocalTLSDir(p *Peer) string {
|
|
return n.peerLocalCryptoDir(p, "tls")
|
|
}
|
|
|
|
// PeerCert returns the path to the peer's certificate.
|
|
func (n *Network) PeerCert(p *Peer) string {
|
|
org := n.Organization(p.Organization)
|
|
Expect(org).NotTo(BeNil())
|
|
|
|
return filepath.Join(
|
|
n.PeerLocalMSPDir(p),
|
|
"signcerts",
|
|
fmt.Sprintf("%s.%s-cert.pem", p.Name, org.Domain),
|
|
)
|
|
}
|
|
|
|
// PeerOrgMSPDir returns the path to the MSP directory of the Peer organization.
|
|
func (n *Network) PeerOrgMSPDir(org *Organization) string {
|
|
return filepath.Join(
|
|
n.RootDir,
|
|
"crypto",
|
|
"peerOrganizations",
|
|
org.Domain,
|
|
"msp",
|
|
)
|
|
}
|
|
|
|
func (n *Network) IdemixOrgMSPDir(org *Organization) string {
|
|
return filepath.Join(
|
|
n.RootDir,
|
|
"crypto",
|
|
"peerOrganizations",
|
|
org.Domain,
|
|
)
|
|
}
|
|
|
|
// OrdererOrgMSPDir returns the path to the MSP directory of the Orderer
|
|
// organization.
|
|
func (n *Network) OrdererOrgMSPDir(o *Organization) string {
|
|
return filepath.Join(
|
|
n.RootDir,
|
|
"crypto",
|
|
"ordererOrganizations",
|
|
o.Domain,
|
|
"msp",
|
|
)
|
|
}
|
|
|
|
// OrdererLocalCryptoDir returns the path to the local crypto directory for the
|
|
// Orderer.
|
|
func (n *Network) OrdererLocalCryptoDir(o *Orderer, cryptoType string) string {
|
|
org := n.Organization(o.Organization)
|
|
Expect(org).NotTo(BeNil())
|
|
|
|
return filepath.Join(
|
|
n.RootDir,
|
|
"crypto",
|
|
"ordererOrganizations",
|
|
org.Domain,
|
|
"orderers",
|
|
fmt.Sprintf("%s.%s", o.Name, org.Domain),
|
|
cryptoType,
|
|
)
|
|
}
|
|
|
|
// OrdererLocalMSPDir returns the path to the local MSP directory for the
|
|
// Orderer.
|
|
func (n *Network) OrdererLocalMSPDir(o *Orderer) string {
|
|
return n.OrdererLocalCryptoDir(o, "msp")
|
|
}
|
|
|
|
func (n *Network) OrdererSignCert(o *Orderer) string {
|
|
dirName := filepath.Join(n.OrdererLocalCryptoDir(o, "msp"), "signcerts")
|
|
fileName := fmt.Sprintf("%s.%s-cert.pem", o.Name, n.Organization(o.Organization).Domain)
|
|
return filepath.Join(dirName, fileName)
|
|
}
|
|
|
|
// OrdererLocalTLSDir returns the path to the local TLS directory for the
|
|
// Orderer.
|
|
func (n *Network) OrdererLocalTLSDir(o *Orderer) string {
|
|
return n.OrdererLocalCryptoDir(o, "tls")
|
|
}
|
|
|
|
// ProfileForChannel gets the configtxgen profile name associated with the
|
|
// specified channel.
|
|
func (n *Network) ProfileForChannel(channelName string) string {
|
|
for _, ch := range n.Channels {
|
|
if ch.Name == channelName {
|
|
return ch.Profile
|
|
}
|
|
}
|
|
return ""
|
|
}
|
|
|
|
// CACertsBundlePath returns the path to the bundle of CA certificates for the
|
|
// network. This bundle is used when connecting to peers.
|
|
func (n *Network) CACertsBundlePath() string {
|
|
return filepath.Join(
|
|
n.RootDir,
|
|
"crypto",
|
|
"ca-certs.pem",
|
|
)
|
|
}
|
|
|
|
// GenerateConfigTree generates the configuration documents required to
|
|
// bootstrap a fabric network. A configuration file will be generated for
|
|
// cryptogen, configtxgen, and for each peer and orderer. The contents of the
|
|
// documents will be based on the Config used to create the Network.
|
|
//
|
|
// When this method completes, the resulting tree will look something like
|
|
// this:
|
|
//
|
|
// ${rootDir}/configtx.yaml
|
|
// ${rootDir}/crypto-config.yaml
|
|
// ${rootDir}/orderers/orderer0.orderer-org/orderer.yaml
|
|
// ${rootDir}/peers/peer0.org1/core.yaml
|
|
// ${rootDir}/peers/peer0.org2/core.yaml
|
|
// ${rootDir}/peers/peer1.org1/core.yaml
|
|
// ${rootDir}/peers/peer1.org2/core.yaml
|
|
func (n *Network) GenerateConfigTree() {
|
|
n.GenerateCryptoConfig()
|
|
n.GenerateConfigTxConfig()
|
|
for _, o := range n.Orderers {
|
|
n.GenerateOrdererConfig(o)
|
|
}
|
|
for _, p := range n.Peers {
|
|
n.GenerateCoreConfig(p)
|
|
}
|
|
}
|
|
|
|
// Bootstrap generates the cryptographic material and create application channels genesis blocks
|
|
// needed to run a fabric network.
|
|
//
|
|
// The cryptogen tool is used to create crypto material from the contents of
|
|
// ${rootDir}/crypto-config.yaml. The generated artifacts will be placed in
|
|
// ${rootDir}/crypto/...
|
|
//
|
|
// The genesis block is generated from the profile referenced by the channel's Profile attribute.
|
|
// The block is written to ${rootDir}/${Channel.Name}_block.pb.
|
|
//
|
|
// The create channel transactions are generated for each Channel referenced by
|
|
// the Network using the channel's Profile attribute. The transactions are
|
|
// written to ${rootDir}/${Channel.Name}_tx.pb.
|
|
func (n *Network) Bootstrap() {
|
|
if n.DockerClient != nil {
|
|
n.CreateDockerNetwork()
|
|
}
|
|
|
|
sess, err := n.Cryptogen(commands.Generate{
|
|
Config: n.CryptoConfigPath(),
|
|
Output: n.CryptoPath(),
|
|
})
|
|
Expect(err).NotTo(HaveOccurred())
|
|
Eventually(sess, n.EventuallyTimeout).Should(gexec.Exit(0))
|
|
|
|
n.bootstrapIdemix()
|
|
|
|
for _, c := range n.Channels {
|
|
sess, err := n.ConfigTxGen(commands.OutputBlock{
|
|
ChannelID: c.Name,
|
|
Profile: c.Profile,
|
|
ConfigPath: n.RootDir,
|
|
OutputBlock: n.OutputBlockPath(c.Name),
|
|
})
|
|
Expect(err).NotTo(HaveOccurred())
|
|
Eventually(sess, n.EventuallyTimeout).Should(gexec.Exit(0))
|
|
}
|
|
|
|
n.ConcatenateTLSCACertificates()
|
|
}
|
|
|
|
func (n *Network) CreateDockerNetwork() {
|
|
_, err := n.DockerClient.CreateNetwork(
|
|
docker.CreateNetworkOptions{
|
|
Name: n.NetworkID,
|
|
Driver: "bridge",
|
|
},
|
|
)
|
|
Expect(err).NotTo(HaveOccurred())
|
|
|
|
if runtime.GOOS == "darwin" {
|
|
n.checkDockerNetworks()
|
|
}
|
|
}
|
|
|
|
// checkDockerNetworks attempts to discover if the docker network configuration
|
|
// will prevent a container from accessing the host. This commonly happens when
|
|
// using Docker for Mac on home networks because most home routers provide DHCP
|
|
// addresses from 192.168.1.0/24 and the default Docker daemon config uses
|
|
// 192.168.0.0/20 as one of the default local address pools.
|
|
//
|
|
// https://github.com/moby/libnetwork/blob/1a17fb36132631a95fe6bb055b91e24a516ad81d/ipamutils/utils.go#L18-L20
|
|
//
|
|
// Docker can be configured to use different addresses by adding an
|
|
// appropriate default-address-pools configuration element to "daemon.json".
|
|
//
|
|
// For example:
|
|
//
|
|
// "default-address-pools":[
|
|
// {"base":"172.30.0.0/16","size":24},
|
|
// {"base":"172.31.0.0/16","size":24}
|
|
// ]
|
|
func (n *Network) checkDockerNetworks() {
|
|
hostAddrs := hostIPv4Addrs()
|
|
for _, nw := range n.dockerIPNets() {
|
|
for _, a := range hostAddrs {
|
|
if nw.Contains(a) {
|
|
fmt.Fprintf(ginkgo.GinkgoWriter, "\x1b[01;37;41mWARNING: docker network %s overlaps with host address %s.\x1b[0m\n", nw, a)
|
|
fmt.Fprintf(ginkgo.GinkgoWriter, "\x1b[01;37;41mDocker containers may not have connectivity causing chaincode registration to fail with 'no route to host'.\x1b[0m\n")
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
func (n *Network) dockerIPNets() []*net.IPNet {
|
|
dockerNetworks, err := n.DockerClient.ListNetworks()
|
|
Expect(err).NotTo(HaveOccurred())
|
|
|
|
var nets []*net.IPNet
|
|
for _, nw := range dockerNetworks {
|
|
for _, ipconf := range nw.IPAM.Config {
|
|
if ipconf.Subnet != "" {
|
|
_, ipn, err := net.ParseCIDR(ipconf.Subnet)
|
|
Expect(err).NotTo(HaveOccurred())
|
|
nets = append(nets, ipn)
|
|
}
|
|
}
|
|
}
|
|
return nets
|
|
}
|
|
|
|
func hostIPv4Addrs() []net.IP {
|
|
interfaces, err := net.Interfaces()
|
|
Expect(err).NotTo(HaveOccurred())
|
|
|
|
var addresses []net.IP
|
|
for _, i := range interfaces {
|
|
addrs, err := i.Addrs()
|
|
Expect(err).NotTo(HaveOccurred())
|
|
|
|
for _, a := range addrs {
|
|
a := a
|
|
switch v := a.(type) {
|
|
case *net.IPAddr:
|
|
if v.IP.To4() != nil {
|
|
addresses = append(addresses, v.IP)
|
|
}
|
|
case *net.IPNet:
|
|
if v.IP.To4() != nil {
|
|
addresses = append(addresses, v.IP)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return addresses
|
|
}
|
|
|
|
// bootstrapIdemix creates the idemix-related crypto material
|
|
func (n *Network) bootstrapIdemix() {
|
|
for j, org := range n.IdemixOrgs() {
|
|
output := n.IdemixOrgMSPDir(org)
|
|
// - ca-keygen
|
|
sess, err := n.Idemixgen(commands.CAKeyGen{
|
|
Output: output,
|
|
})
|
|
Expect(err).NotTo(HaveOccurred())
|
|
Eventually(sess, n.EventuallyTimeout).Should(gexec.Exit(0))
|
|
|
|
// - signerconfig
|
|
usersOutput := filepath.Join(n.IdemixOrgMSPDir(org), "users")
|
|
userOutput := filepath.Join(usersOutput, "User1@"+org.Domain)
|
|
sess, err = n.Idemixgen(commands.SignerConfig{
|
|
CAInput: output,
|
|
Output: userOutput,
|
|
OrgUnit: org.Domain,
|
|
EnrollmentID: "User1",
|
|
RevocationHandle: fmt.Sprintf("11%d", j),
|
|
})
|
|
Expect(err).NotTo(HaveOccurred())
|
|
Eventually(sess, n.EventuallyTimeout).Should(gexec.Exit(0))
|
|
}
|
|
}
|
|
|
|
// ConcatenateTLSCACertificates concatenates all TLS CA certificates into a
|
|
// single file to be used by peer CLI.
|
|
func (n *Network) ConcatenateTLSCACertificates() {
|
|
bundle := &bytes.Buffer{}
|
|
for _, tlsCertPath := range n.listTLSCACertificates() {
|
|
certBytes, err := ioutil.ReadFile(tlsCertPath)
|
|
Expect(err).NotTo(HaveOccurred())
|
|
bundle.Write(certBytes)
|
|
}
|
|
err := ioutil.WriteFile(n.CACertsBundlePath(), bundle.Bytes(), 0o660)
|
|
Expect(err).NotTo(HaveOccurred())
|
|
}
|
|
|
|
// listTLSCACertificates returns the paths of all TLS CA certificates in the
|
|
// network, across all organizations.
|
|
func (n *Network) listTLSCACertificates() []string {
|
|
fileName2Path := make(map[string]string)
|
|
filepath.Walk(filepath.Join(n.RootDir, "crypto"), func(path string, info os.FileInfo, err error) error {
|
|
// File starts with "tlsca" and has "-cert.pem" in it
|
|
if strings.HasPrefix(info.Name(), "tlsca") && strings.Contains(info.Name(), "-cert.pem") {
|
|
fileName2Path[info.Name()] = path
|
|
}
|
|
return nil
|
|
})
|
|
|
|
var tlsCACertificates []string
|
|
for _, path := range fileName2Path {
|
|
tlsCACertificates = append(tlsCACertificates, path)
|
|
}
|
|
return tlsCACertificates
|
|
}
|
|
|
|
// Cleanup attempts to cleanup docker related artifacts that may
|
|
// have been created by the network.
|
|
func (n *Network) Cleanup() {
|
|
if n == nil || n.DockerClient == nil {
|
|
return
|
|
}
|
|
|
|
nw, err := n.DockerClient.NetworkInfo(n.NetworkID)
|
|
Expect(err).NotTo(HaveOccurred())
|
|
|
|
err = n.DockerClient.RemoveNetwork(nw.ID)
|
|
Expect(err).NotTo(HaveOccurred())
|
|
|
|
containers, err := n.DockerClient.ListContainers(docker.ListContainersOptions{All: true})
|
|
Expect(err).NotTo(HaveOccurred())
|
|
for _, c := range containers {
|
|
for _, name := range c.Names {
|
|
if strings.HasPrefix(name, "/"+n.NetworkID) {
|
|
err := n.DockerClient.RemoveContainer(docker.RemoveContainerOptions{ID: c.ID, Force: true})
|
|
Expect(err).NotTo(HaveOccurred())
|
|
break
|
|
}
|
|
}
|
|
}
|
|
|
|
images, err := n.DockerClient.ListImages(docker.ListImagesOptions{All: true})
|
|
Expect(err).NotTo(HaveOccurred())
|
|
for _, i := range images {
|
|
for _, tag := range i.RepoTags {
|
|
if strings.HasPrefix(tag, n.NetworkID) {
|
|
err := n.DockerClient.RemoveImage(i.ID)
|
|
Expect(err).NotTo(HaveOccurred())
|
|
break
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// CreateAndJoinChannels will create all channels specified in the config that
|
|
// are referenced by peers. The referencing peers will then be joined to the
|
|
// channel(s).
|
|
//
|
|
// The network must be running before this is called.
|
|
func (n *Network) CreateAndJoinChannels(o *Orderer) {
|
|
for _, c := range n.Channels {
|
|
n.CreateAndJoinChannel(o, c.Name)
|
|
}
|
|
}
|
|
|
|
// CreateAndJoinChannel will create the specified channel. The referencing
|
|
// peers will then be joined to the channel.
|
|
//
|
|
// The network must be running before this is called.
|
|
func (n *Network) CreateAndJoinChannel(o *Orderer, channelName string) {
|
|
peers := n.PeersWithChannel(channelName)
|
|
if len(peers) == 0 {
|
|
return
|
|
}
|
|
|
|
n.CreateChannel(channelName, o, peers[0])
|
|
n.JoinChannel(channelName, o, peers...)
|
|
}
|
|
|
|
// UpdateChannelAnchors determines the anchor peers for the specified channel,
|
|
// creates an anchor peer update transaction for each organization, and submits
|
|
// the update transactions to the orderer.
|
|
//
|
|
// TODO using configtxgen with -outputAnchorPeersUpdate to update the anchor peers is deprecated and does not work
|
|
// with channel participation API. We'll have to generate the channel update explicitly (see UpdateOrgAnchorPeers).
|
|
func (n *Network) UpdateChannelAnchors(o *Orderer, channelName string) {
|
|
tempFile, err := ioutil.TempFile("", "update-anchors")
|
|
Expect(err).NotTo(HaveOccurred())
|
|
tempFile.Close()
|
|
defer os.Remove(tempFile.Name())
|
|
|
|
peersByOrg := map[string]*Peer{}
|
|
for _, p := range n.AnchorsForChannel(channelName) {
|
|
peersByOrg[p.Organization] = p
|
|
}
|
|
|
|
for orgName, p := range peersByOrg {
|
|
anchorUpdate := commands.OutputAnchorPeersUpdate{
|
|
OutputAnchorPeersUpdate: tempFile.Name(),
|
|
ChannelID: channelName,
|
|
Profile: n.ProfileForChannel(channelName),
|
|
ConfigPath: n.RootDir,
|
|
AsOrg: orgName,
|
|
}
|
|
sess, err := n.ConfigTxGen(anchorUpdate)
|
|
Expect(err).NotTo(HaveOccurred())
|
|
Eventually(sess, n.EventuallyTimeout).Should(gexec.Exit(0))
|
|
|
|
sess, err = n.PeerAdminSession(p, commands.ChannelUpdate{
|
|
ChannelID: channelName,
|
|
Orderer: n.OrdererAddress(o, ListenPort),
|
|
File: tempFile.Name(),
|
|
ClientAuth: n.ClientAuthRequired,
|
|
})
|
|
Expect(err).NotTo(HaveOccurred())
|
|
Eventually(sess, n.EventuallyTimeout).Should(gexec.Exit(0))
|
|
}
|
|
}
|
|
|
|
// UpdateOrgAnchorPeers sets the anchor peers of an organization on a channel using a config update tx, and waits for
|
|
// the update to be complete.
|
|
func (n *Network) UpdateOrgAnchorPeers(o *Orderer, channelName, orgName string, anchorPeersForOrg []*Peer) {
|
|
peersInOrg := n.PeersInOrg(orgName)
|
|
Expect(peersInOrg).ToNot(BeEmpty())
|
|
currentConfig := GetConfig(n, peersInOrg[0], o, channelName)
|
|
updatedConfig := proto.Clone(currentConfig).(*common.Config)
|
|
orgConfigGroup := updatedConfig.ChannelGroup.Groups["Application"].GetGroups()[orgName]
|
|
Expect(orgConfigGroup).NotTo(BeNil())
|
|
|
|
updatedAnchorPeers := &pb.AnchorPeers{}
|
|
for _, p := range anchorPeersForOrg {
|
|
updatedAnchorPeers.AnchorPeers = append(updatedAnchorPeers.AnchorPeers, &pb.AnchorPeer{
|
|
Host: "127.0.0.1",
|
|
Port: int32(n.PeerPort(p, ListenPort)),
|
|
})
|
|
}
|
|
|
|
value, err := protoutil.Marshal(updatedAnchorPeers)
|
|
Expect(err).NotTo(HaveOccurred())
|
|
updatedConfig.ChannelGroup.Groups["Application"].GetGroups()[orgName].GetValues()["AnchorPeers"] = &common.ConfigValue{
|
|
Value: value,
|
|
ModPolicy: "Admins",
|
|
}
|
|
|
|
UpdateConfig(n, o, channelName, currentConfig, updatedConfig, false, peersInOrg[0], peersInOrg[0])
|
|
}
|
|
|
|
// VerifyMembership checks that each peer has discovered the expected peers in
|
|
// the network.
|
|
func (n *Network) VerifyMembership(expectedPeers []*Peer, channel string, chaincodes ...string) {
|
|
// all peers currently include _lifecycle as an available chaincode
|
|
chaincodes = append(chaincodes, "_lifecycle")
|
|
expectedDiscoveredPeerMatchers := make([]types.GomegaMatcher, len(expectedPeers))
|
|
for i, peer := range expectedPeers {
|
|
expectedDiscoveredPeerMatchers[i] = n.discoveredPeerMatcher(peer, chaincodes...)
|
|
}
|
|
for _, peer := range expectedPeers {
|
|
Eventually(DiscoverPeers(n, peer, "User1", channel), n.EventuallyTimeout).Should(ConsistOf(expectedDiscoveredPeerMatchers))
|
|
}
|
|
}
|
|
|
|
func (n *Network) discoveredPeerMatcher(p *Peer, chaincodes ...string) types.GomegaMatcher {
|
|
peerCert, err := ioutil.ReadFile(n.PeerCert(p))
|
|
Expect(err).NotTo(HaveOccurred())
|
|
|
|
var ccs []interface{}
|
|
for _, cc := range chaincodes {
|
|
ccs = append(ccs, cc)
|
|
}
|
|
return gstruct.MatchAllFields(gstruct.Fields{
|
|
"MSPID": Equal(n.Organization(p.Organization).MSPID),
|
|
"Endpoint": Equal(fmt.Sprintf("127.0.0.1:%d", n.PeerPort(p, ListenPort))),
|
|
"Identity": Equal(string(peerCert)),
|
|
"Chaincodes": ContainElements(ccs...),
|
|
})
|
|
}
|
|
|
|
// CreateChannel will submit an existing create channel transaction to the
|
|
// specified orderer. The channel transaction must exist at the location
|
|
// returned by CreateChannelTxPath. Optionally, additional signers may be
|
|
// included in the case where the channel creation tx modifies other aspects of
|
|
// the channel config for the new channel.
|
|
//
|
|
// The orderer must be running when this is called.
|
|
func (n *Network) CreateChannel(channelName string, o *Orderer, p *Peer, additionalSigners ...interface{}) {
|
|
channelCreateTxPath := n.CreateChannelTxPath(channelName)
|
|
n.signConfigTransaction(channelCreateTxPath, p, additionalSigners...)
|
|
|
|
createChannel := func() int {
|
|
sess, err := n.PeerAdminSession(p, commands.ChannelCreate{
|
|
ChannelID: channelName,
|
|
Orderer: n.OrdererAddress(o, ListenPort),
|
|
File: channelCreateTxPath,
|
|
OutputBlock: "/dev/null",
|
|
ClientAuth: n.ClientAuthRequired,
|
|
})
|
|
Expect(err).NotTo(HaveOccurred())
|
|
return sess.Wait(n.EventuallyTimeout).ExitCode()
|
|
}
|
|
Eventually(createChannel, n.EventuallyTimeout).Should(Equal(0))
|
|
}
|
|
|
|
// CreateChannelExitCode will submit an existing create channel transaction to
|
|
// the specified orderer, wait for the operation to complete, and return the
|
|
// exit status of the "peer channel create" command.
|
|
//
|
|
// The channel transaction must exist at the location returned by
|
|
// CreateChannelTxPath and the orderer must be running when this is called.
|
|
func (n *Network) CreateChannelExitCode(channelName string, o *Orderer, p *Peer, additionalSigners ...interface{}) int {
|
|
channelCreateTxPath := n.CreateChannelTxPath(channelName)
|
|
n.signConfigTransaction(channelCreateTxPath, p, additionalSigners...)
|
|
|
|
sess, err := n.PeerAdminSession(p, commands.ChannelCreate{
|
|
ChannelID: channelName,
|
|
Orderer: n.OrdererAddress(o, ListenPort),
|
|
File: channelCreateTxPath,
|
|
OutputBlock: "/dev/null",
|
|
ClientAuth: n.ClientAuthRequired,
|
|
})
|
|
Expect(err).NotTo(HaveOccurred())
|
|
return sess.Wait(n.EventuallyTimeout).ExitCode()
|
|
}
|
|
|
|
func (n *Network) signConfigTransaction(channelTxPath string, submittingPeer *Peer, signers ...interface{}) {
|
|
for _, signer := range signers {
|
|
switch signer := signer.(type) {
|
|
case *Peer:
|
|
sess, err := n.PeerAdminSession(signer, commands.SignConfigTx{
|
|
File: channelTxPath,
|
|
ClientAuth: n.ClientAuthRequired,
|
|
})
|
|
Expect(err).NotTo(HaveOccurred())
|
|
Eventually(sess, n.EventuallyTimeout).Should(gexec.Exit(0))
|
|
|
|
case *Orderer:
|
|
sess, err := n.OrdererAdminSession(signer, submittingPeer, commands.SignConfigTx{
|
|
File: channelTxPath,
|
|
ClientAuth: n.ClientAuthRequired,
|
|
})
|
|
Expect(err).NotTo(HaveOccurred())
|
|
Eventually(sess, n.EventuallyTimeout).Should(gexec.Exit(0))
|
|
|
|
default:
|
|
panic(fmt.Sprintf("unknown signer type %T, expect Peer or Orderer", signer))
|
|
}
|
|
}
|
|
}
|
|
|
|
// JoinChannel will join peers to the specified channel. The orderer is used to
|
|
// obtain the current configuration block for the channel.
|
|
//
|
|
// The orderer and listed peers must be running before this is called.
|
|
func (n *Network) JoinChannel(name string, o *Orderer, peers ...*Peer) {
|
|
if len(peers) == 0 {
|
|
return
|
|
}
|
|
|
|
tempFile, err := ioutil.TempFile("", "genesis-block")
|
|
Expect(err).NotTo(HaveOccurred())
|
|
tempFile.Close()
|
|
defer os.Remove(tempFile.Name())
|
|
|
|
sess, err := n.PeerAdminSession(peers[0], commands.ChannelFetch{
|
|
Block: "0",
|
|
ChannelID: name,
|
|
Orderer: n.OrdererAddress(o, ListenPort),
|
|
OutputFile: tempFile.Name(),
|
|
ClientAuth: n.ClientAuthRequired,
|
|
})
|
|
Expect(err).NotTo(HaveOccurred())
|
|
Eventually(sess, n.EventuallyTimeout).Should(gexec.Exit(0))
|
|
|
|
for _, p := range peers {
|
|
sess, err := n.PeerAdminSession(p, commands.ChannelJoin{
|
|
BlockPath: tempFile.Name(),
|
|
ClientAuth: n.ClientAuthRequired,
|
|
})
|
|
Expect(err).NotTo(HaveOccurred())
|
|
Eventually(sess, n.EventuallyTimeout).Should(gexec.Exit(0))
|
|
}
|
|
}
|
|
|
|
func (n *Network) JoinChannelBySnapshot(snapshotDir string, peers ...*Peer) {
|
|
if len(peers) == 0 {
|
|
return
|
|
}
|
|
|
|
for _, p := range peers {
|
|
sess, err := n.PeerAdminSession(p, commands.ChannelJoinBySnapshot{
|
|
SnapshotPath: snapshotDir,
|
|
ClientAuth: n.ClientAuthRequired,
|
|
})
|
|
Expect(err).NotTo(HaveOccurred())
|
|
Eventually(sess, n.EventuallyTimeout).Should(gexec.Exit(0))
|
|
}
|
|
}
|
|
|
|
func (n *Network) JoinBySnapshotStatus(p *Peer) string {
|
|
sess, err := n.PeerAdminSession(p, commands.ChannelJoinBySnapshotStatus{
|
|
ClientAuth: n.ClientAuthRequired,
|
|
})
|
|
Expect(err).NotTo(HaveOccurred())
|
|
Eventually(sess, n.EventuallyTimeout).Should(gexec.Exit(0))
|
|
return string(sess.Out.Contents())
|
|
}
|
|
|
|
// Cryptogen starts a gexec.Session for the provided cryptogen command.
|
|
func (n *Network) Cryptogen(command Command) (*gexec.Session, error) {
|
|
cmd := NewCommand(n.Components.Cryptogen(), command)
|
|
return n.StartSession(cmd, command.SessionName())
|
|
}
|
|
|
|
// Idemixgen starts a gexec.Session for the provided idemixgen command.
|
|
func (n *Network) Idemixgen(command Command) (*gexec.Session, error) {
|
|
cmd := NewCommand(n.Components.Idemixgen(), command)
|
|
return n.StartSession(cmd, command.SessionName())
|
|
}
|
|
|
|
// ConfigTxGen starts a gexec.Session for the provided configtxgen command.
|
|
func (n *Network) ConfigTxGen(command Command) (*gexec.Session, error) {
|
|
cmd := NewCommand(n.Components.ConfigTxGen(), command)
|
|
return n.StartSession(cmd, command.SessionName())
|
|
}
|
|
|
|
// Discover starts a gexec.Session for the provided discover command.
|
|
func (n *Network) Discover(command Command) (*gexec.Session, error) {
|
|
cmd := NewCommand(n.Components.Discover(), command)
|
|
cmd.Args = append(cmd.Args, "--peerTLSCA", n.CACertsBundlePath())
|
|
return n.StartSession(cmd, command.SessionName())
|
|
}
|
|
|
|
// Osnadmin starts a gexec.Session for the provided osnadmin command.
|
|
func (n *Network) Osnadmin(command Command) (*gexec.Session, error) {
|
|
cmd := NewCommand(n.Components.Osnadmin(), command)
|
|
return n.StartSession(cmd, command.SessionName())
|
|
}
|
|
|
|
// OrdererRunner returns an ifrit.Runner for the specified orderer. The runner
|
|
// can be used to start and manage an orderer process.
|
|
func (n *Network) OrdererRunner(o *Orderer, env ...string) *ginkgomon.Runner {
|
|
cmd := exec.Command(n.Components.Orderer())
|
|
cmd.Env = os.Environ()
|
|
cmd.Env = append(cmd.Env, fmt.Sprintf("FABRIC_CFG_PATH=%s", n.OrdererDir(o)))
|
|
cmd.Env = append(cmd.Env, env...)
|
|
|
|
config := ginkgomon.Config{
|
|
AnsiColorCode: n.nextColor(),
|
|
Name: o.ID(),
|
|
Command: cmd,
|
|
StartCheck: "Beginning to serve requests",
|
|
StartCheckTimeout: 15 * time.Second,
|
|
}
|
|
|
|
return ginkgomon.New(config)
|
|
}
|
|
|
|
// OrdererGroupRunner returns a runner that can be used to start and stop all
|
|
// orderers in a network.
|
|
func (n *Network) OrdererGroupRunner() ifrit.Runner {
|
|
members := grouper.Members{}
|
|
for _, o := range n.Orderers {
|
|
members = append(members, grouper.Member{Name: o.ID(), Runner: n.OrdererRunner(o)})
|
|
}
|
|
return grouper.NewParallel(syscall.SIGTERM, members)
|
|
}
|
|
|
|
// PeerRunner returns an ifrit.Runner for the specified peer. The runner can be
|
|
// used to start and manage a peer process.
|
|
func (n *Network) PeerRunner(p *Peer, env ...string) *ginkgomon.Runner {
|
|
cmd := n.peerCommand(
|
|
commands.NodeStart{PeerID: p.ID(), DevMode: p.DevMode},
|
|
"",
|
|
"FABRIC_CFG_PATH="+n.PeerDir(p),
|
|
"CORE_LEDGER_STATE_COUCHDBCONFIG_USERNAME=admin",
|
|
"CORE_LEDGER_STATE_COUCHDBCONFIG_PASSWORD=adminpw",
|
|
)
|
|
cmd.Env = append(cmd.Env, env...)
|
|
|
|
return ginkgomon.New(ginkgomon.Config{
|
|
AnsiColorCode: n.nextColor(),
|
|
Name: p.ID(),
|
|
Command: cmd,
|
|
StartCheck: `Started peer with ID=.*, .*, address=`,
|
|
StartCheckTimeout: 15 * time.Second,
|
|
})
|
|
}
|
|
|
|
// PeerGroupRunner returns a runner that can be used to start and stop all
|
|
// peers in a network.
|
|
func (n *Network) PeerGroupRunner() ifrit.Runner {
|
|
members := grouper.Members{}
|
|
for _, p := range n.Peers {
|
|
members = append(members, grouper.Member{Name: p.ID(), Runner: n.PeerRunner(p)})
|
|
}
|
|
return grouper.NewParallel(syscall.SIGTERM, members)
|
|
}
|
|
|
|
// NetworkGroupRunner returns a runner that can be used to start and stop an
|
|
// entire fabric network.
|
|
func (n *Network) NetworkGroupRunner() ifrit.Runner {
|
|
members := grouper.Members{
|
|
{Name: "orderers", Runner: n.OrdererGroupRunner()},
|
|
{Name: "peers", Runner: n.PeerGroupRunner()},
|
|
}
|
|
return grouper.NewOrdered(syscall.SIGTERM, members)
|
|
}
|
|
|
|
func (n *Network) peerCommand(command Command, tlsDir string, env ...string) *exec.Cmd {
|
|
cmd := NewCommand(n.Components.Peer(), command)
|
|
cmd.Env = append(cmd.Env, env...)
|
|
|
|
if connectsToOrderer(command) && n.TLSEnabled {
|
|
cmd.Args = append(cmd.Args, "--tls")
|
|
cmd.Args = append(cmd.Args, "--cafile", n.CACertsBundlePath())
|
|
}
|
|
|
|
if clientAuthEnabled(command) {
|
|
certfilePath := filepath.Join(tlsDir, "client.crt")
|
|
keyfilePath := filepath.Join(tlsDir, "client.key")
|
|
|
|
cmd.Args = append(cmd.Args, "--certfile", certfilePath)
|
|
cmd.Args = append(cmd.Args, "--keyfile", keyfilePath)
|
|
}
|
|
|
|
// In case we have a peer invoke with multiple certificates, we need to mimic
|
|
// the correct peer CLI usage, so we count the number of --peerAddresses
|
|
// usages we have, and add the same (concatenated TLS CA certificates file)
|
|
// the same number of times to bypass the peer CLI sanity checks
|
|
requiredPeerAddresses := flagCount("--peerAddresses", cmd.Args)
|
|
for i := 0; i < requiredPeerAddresses; i++ {
|
|
cmd.Args = append(cmd.Args, "--tlsRootCertFiles")
|
|
cmd.Args = append(cmd.Args, n.CACertsBundlePath())
|
|
}
|
|
|
|
// If there is --peerAddress, add --tlsRootCertFile parameter
|
|
requiredPeerAddress := flagCount("--peerAddress", cmd.Args)
|
|
if requiredPeerAddress > 0 {
|
|
cmd.Args = append(cmd.Args, "--tlsRootCertFile")
|
|
cmd.Args = append(cmd.Args, n.CACertsBundlePath())
|
|
}
|
|
return cmd
|
|
}
|
|
|
|
func connectsToOrderer(c Command) bool {
|
|
for _, arg := range c.Args() {
|
|
if arg == "--orderer" {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
func clientAuthEnabled(c Command) bool {
|
|
for _, arg := range c.Args() {
|
|
if arg == "--clientauth" {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
func flagCount(flag string, args []string) int {
|
|
var c int
|
|
for _, arg := range args {
|
|
if arg == flag {
|
|
c++
|
|
}
|
|
}
|
|
return c
|
|
}
|
|
|
|
// PeerAdminSession starts a gexec.Session as a peer admin for the provided
|
|
// peer command. This is intended to be used by short running peer cli commands
|
|
// that execute in the context of a peer configuration.
|
|
func (n *Network) PeerAdminSession(p *Peer, command Command) (*gexec.Session, error) {
|
|
return n.PeerUserSession(p, "Admin", command)
|
|
}
|
|
|
|
// PeerUserSession starts a gexec.Session as a peer user for the provided peer
|
|
// command. This is intended to be used by short running peer cli commands that
|
|
// execute in the context of a peer configuration.
|
|
func (n *Network) PeerUserSession(p *Peer, user string, command Command) (*gexec.Session, error) {
|
|
cmd := n.peerCommand(
|
|
command,
|
|
n.PeerUserTLSDir(p, user),
|
|
fmt.Sprintf("FABRIC_CFG_PATH=%s", n.PeerDir(p)),
|
|
fmt.Sprintf("CORE_PEER_MSPCONFIGPATH=%s", n.PeerUserMSPDir(p, user)),
|
|
)
|
|
return n.StartSession(cmd, command.SessionName())
|
|
}
|
|
|
|
// PeerClientConn returns a grpc.ClientConn configured to connect to the
|
|
// provided peer. This connection can be used to create clients for the peer
|
|
// services. The client connection should be closed when the tests are done
|
|
// using it.
|
|
func (n *Network) PeerClientConn(p *Peer) *grpc.ClientConn {
|
|
return n.newClientConn(
|
|
n.PeerAddress(p, ListenPort),
|
|
filepath.Join(n.PeerLocalTLSDir(p), "ca.crt"),
|
|
)
|
|
}
|
|
|
|
// OrdererClientConn returns a grpc.ClientConn configured to connect to the
|
|
// provided orderer. This connection can be used to create clients for the
|
|
// orderer services. The client connection should be closed when the tests are
|
|
// done using it.
|
|
func (n *Network) OrdererClientConn(o *Orderer) *grpc.ClientConn {
|
|
return n.newClientConn(
|
|
n.OrdererAddress(o, ListenPort),
|
|
filepath.Join(n.OrdererLocalTLSDir(o), "ca.crt"),
|
|
)
|
|
}
|
|
|
|
func (n *Network) newClientConn(address, ca string) *grpc.ClientConn {
|
|
fingerprint := "grpc::" + address + "::" + ca
|
|
if d := n.throttleDuration(fingerprint); d > 0 {
|
|
time.Sleep(d)
|
|
}
|
|
|
|
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
|
|
defer cancel()
|
|
|
|
creds, err := credentials.NewClientTLSFromFile(ca, "")
|
|
Expect(err).NotTo(HaveOccurred())
|
|
|
|
conn, err := grpc.DialContext(
|
|
ctx,
|
|
address,
|
|
grpc.WithBlock(),
|
|
grpc.FailOnNonTempDialError(true),
|
|
grpc.WithTransportCredentials(creds),
|
|
)
|
|
Expect(err).NotTo(HaveOccurred())
|
|
|
|
return conn
|
|
}
|
|
|
|
// IdemixUserSession starts a gexec.Session as a idemix user for the provided peer
|
|
// command. This is intended to be used by short running peer cli commands that
|
|
// execute in the context of a peer configuration.
|
|
func (n *Network) IdemixUserSession(p *Peer, idemixOrg *Organization, user string, command Command) (*gexec.Session, error) {
|
|
cmd := n.peerCommand(
|
|
command,
|
|
n.PeerUserTLSDir(p, user),
|
|
fmt.Sprintf("FABRIC_CFG_PATH=%s", n.PeerDir(p)),
|
|
fmt.Sprintf("CORE_PEER_MSPCONFIGPATH=%s", n.IdemixUserMSPDir(idemixOrg, user)),
|
|
fmt.Sprintf("CORE_PEER_LOCALMSPTYPE=%s", "idemix"),
|
|
fmt.Sprintf("CORE_PEER_LOCALMSPID=%s", idemixOrg.MSPID),
|
|
)
|
|
return n.StartSession(cmd, command.SessionName())
|
|
}
|
|
|
|
// OrdererAdminSession starts a gexec.Session as an orderer admin user. This
|
|
// is used primarily to generate orderer configuration updates.
|
|
func (n *Network) OrdererAdminSession(o *Orderer, p *Peer, command Command) (*gexec.Session, error) {
|
|
cmd := n.peerCommand(
|
|
command,
|
|
n.ordererUserCryptoDir(o, "Admin", "tls"),
|
|
fmt.Sprintf("CORE_PEER_LOCALMSPID=%s", n.Organization(o.Organization).MSPID),
|
|
fmt.Sprintf("FABRIC_CFG_PATH=%s", n.PeerDir(p)),
|
|
fmt.Sprintf("CORE_PEER_MSPCONFIGPATH=%s", n.OrdererUserMSPDir(o, "Admin")),
|
|
)
|
|
return n.StartSession(cmd, command.SessionName())
|
|
}
|
|
|
|
// Peer returns the information about the named Peer in the named organization.
|
|
func (n *Network) Peer(orgName, peerName string) *Peer {
|
|
for _, p := range n.PeersInOrg(orgName) {
|
|
if p.Name == peerName {
|
|
return p
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// DiscoveredPeer creates a new DiscoveredPeer from the peer and chaincodes
|
|
// passed as arguments.
|
|
func (n *Network) DiscoveredPeer(p *Peer, chaincodes ...string) DiscoveredPeer {
|
|
peerCert, err := ioutil.ReadFile(n.PeerCert(p))
|
|
Expect(err).NotTo(HaveOccurred())
|
|
|
|
return DiscoveredPeer{
|
|
MSPID: n.Organization(p.Organization).MSPID,
|
|
Endpoint: fmt.Sprintf("127.0.0.1:%d", n.PeerPort(p, ListenPort)),
|
|
Identity: string(peerCert),
|
|
Chaincodes: chaincodes,
|
|
}
|
|
}
|
|
|
|
// Orderer returns the information about the named Orderer.
|
|
func (n *Network) Orderer(name string) *Orderer {
|
|
for _, o := range n.Orderers {
|
|
if o.Name == name {
|
|
return o
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// Organization returns the information about the named Organization.
|
|
func (n *Network) Organization(orgName string) *Organization {
|
|
for _, org := range n.Organizations {
|
|
if org.Name == orgName {
|
|
return org
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// PeerOrgs returns all Organizations associated with at least one Peer.
|
|
func (n *Network) PeerOrgs() []*Organization {
|
|
orgsByName := map[string]*Organization{}
|
|
for _, p := range n.Peers {
|
|
if n.Organization(p.Organization).MSPType != "idemix" {
|
|
orgsByName[p.Organization] = n.Organization(p.Organization)
|
|
}
|
|
}
|
|
|
|
orgs := []*Organization{}
|
|
for _, org := range orgsByName {
|
|
orgs = append(orgs, org)
|
|
}
|
|
return orgs
|
|
}
|
|
|
|
// IdemixOrgs returns all Organizations of type idemix.
|
|
func (n *Network) IdemixOrgs() []*Organization {
|
|
orgs := []*Organization{}
|
|
for _, org := range n.Organizations {
|
|
if org.MSPType == "idemix" {
|
|
orgs = append(orgs, org)
|
|
}
|
|
}
|
|
return orgs
|
|
}
|
|
|
|
// PeersWithChannel returns all Peer instances that have joined the named
|
|
// channel.
|
|
func (n *Network) PeersWithChannel(chanName string) []*Peer {
|
|
peers := []*Peer{}
|
|
for _, p := range n.Peers {
|
|
for _, c := range p.Channels {
|
|
if c.Name == chanName {
|
|
peers = append(peers, p)
|
|
}
|
|
}
|
|
}
|
|
|
|
// This is a bit of a hack to make the output of this function deterministic.
|
|
// When this function's output is supplied as input to functions such as ApproveChaincodeForMyOrg
|
|
// it causes a different subset of peers to be picked, which can create flakiness in tests.
|
|
sort.Slice(peers, func(i, j int) bool {
|
|
if peers[i].Organization < peers[j].Organization {
|
|
return true
|
|
}
|
|
|
|
return peers[i].Organization == peers[j].Organization && peers[i].Name < peers[j].Name
|
|
})
|
|
return peers
|
|
}
|
|
|
|
// AnchorsForChannel returns all Peer instances that are anchors for the
|
|
// named channel.
|
|
func (n *Network) AnchorsForChannel(chanName string) []*Peer {
|
|
anchors := []*Peer{}
|
|
for _, p := range n.Peers {
|
|
for _, pc := range p.Channels {
|
|
if pc.Name == chanName && pc.Anchor {
|
|
anchors = append(anchors, p)
|
|
}
|
|
}
|
|
}
|
|
return anchors
|
|
}
|
|
|
|
// AnchorsInOrg returns all peers that are an anchor for at least one channel
|
|
// in the named organization.
|
|
func (n *Network) AnchorsInOrg(orgName string) []*Peer {
|
|
anchors := []*Peer{}
|
|
for _, p := range n.PeersInOrg(orgName) {
|
|
if p.Anchor() {
|
|
anchors = append(anchors, p)
|
|
}
|
|
}
|
|
|
|
return anchors
|
|
}
|
|
|
|
// OrderersInOrg returns all Orderer instances owned by the named organization.
|
|
func (n *Network) OrderersInOrg(orgName string) []*Orderer {
|
|
orderers := []*Orderer{}
|
|
for _, o := range n.Orderers {
|
|
if o.Organization == orgName {
|
|
orderers = append(orderers, o)
|
|
}
|
|
}
|
|
return orderers
|
|
}
|
|
|
|
// OrgsForOrderers returns all Organization instances that own at least one of
|
|
// the named orderers.
|
|
func (n *Network) OrgsForOrderers(ordererNames []string) []*Organization {
|
|
orgsByName := map[string]*Organization{}
|
|
for _, name := range ordererNames {
|
|
orgName := n.Orderer(name).Organization
|
|
orgsByName[orgName] = n.Organization(orgName)
|
|
}
|
|
orgs := []*Organization{}
|
|
for _, org := range orgsByName {
|
|
orgs = append(orgs, org)
|
|
}
|
|
return orgs
|
|
}
|
|
|
|
// OrdererOrgs returns all Organization instances that own at least one
|
|
// orderer.
|
|
func (n *Network) OrdererOrgs() []*Organization {
|
|
orgsByName := map[string]*Organization{}
|
|
for _, o := range n.Orderers {
|
|
orgsByName[o.Organization] = n.Organization(o.Organization)
|
|
}
|
|
|
|
orgs := []*Organization{}
|
|
for _, org := range orgsByName {
|
|
orgs = append(orgs, org)
|
|
}
|
|
return orgs
|
|
}
|
|
|
|
// PeersInOrg returns all Peer instances that are owned by the named
|
|
// organization.
|
|
func (n *Network) PeersInOrg(orgName string) []*Peer {
|
|
peers := []*Peer{}
|
|
for _, o := range n.Peers {
|
|
if o.Organization == orgName {
|
|
peers = append(peers, o)
|
|
}
|
|
}
|
|
return peers
|
|
}
|
|
|
|
// ReservePort allocates the next available port.
|
|
func (n *Network) ReservePort() uint16 {
|
|
n.StartPort++
|
|
return n.StartPort - 1
|
|
}
|
|
|
|
type (
|
|
PortName string
|
|
Ports map[PortName]uint16
|
|
)
|
|
|
|
const (
|
|
ChaincodePort PortName = "Chaincode"
|
|
EventsPort PortName = "Events"
|
|
HostPort PortName = "HostPort"
|
|
ListenPort PortName = "Listen"
|
|
ProfilePort PortName = "Profile"
|
|
OperationsPort PortName = "Operations"
|
|
ClusterPort PortName = "Cluster"
|
|
AdminPort PortName = "Admin"
|
|
)
|
|
|
|
// PeerPortNames returns the list of ports that need to be reserved for a Peer.
|
|
func PeerPortNames() []PortName {
|
|
return []PortName{ListenPort, ChaincodePort, EventsPort, ProfilePort, OperationsPort}
|
|
}
|
|
|
|
// OrdererPortNames returns the list of ports that need to be reserved for an
|
|
// Orderer.
|
|
func OrdererPortNames() []PortName {
|
|
return []PortName{ListenPort, ProfilePort, OperationsPort, ClusterPort, AdminPort}
|
|
}
|
|
|
|
// OrdererAddress returns the address (host and port) exposed by the Orderer
|
|
// for the named port. Command line tools should use the returned address when
|
|
// connecting to the orderer.
|
|
//
|
|
// This assumes that the orderer is listening on 0.0.0.0 or 127.0.0.1 and is
|
|
// available on the loopback address.
|
|
func (n *Network) OrdererAddress(o *Orderer, portName PortName) string {
|
|
return fmt.Sprintf("127.0.0.1:%d", n.OrdererPort(o, portName))
|
|
}
|
|
|
|
// OrdererPort returns the named port reserved for the Orderer instance.
|
|
func (n *Network) OrdererPort(o *Orderer, portName PortName) uint16 {
|
|
ordererPorts := n.PortsByOrdererID[o.ID()]
|
|
Expect(ordererPorts).NotTo(BeNil())
|
|
return ordererPorts[portName]
|
|
}
|
|
|
|
// PeerAddress returns the address (host and port) exposed by the Peer for the
|
|
// named port. Command line tools should use the returned address when
|
|
// connecting to a peer.
|
|
//
|
|
// This assumes that the peer is listening on 0.0.0.0 and is available on the
|
|
// loopback address.
|
|
func (n *Network) PeerAddress(p *Peer, portName PortName) string {
|
|
return fmt.Sprintf("127.0.0.1:%d", n.PeerPort(p, portName))
|
|
}
|
|
|
|
// PeerPort returns the named port reserved for the Peer instance.
|
|
func (n *Network) PeerPort(p *Peer, portName PortName) uint16 {
|
|
peerPorts := n.PortsByPeerID[p.ID()]
|
|
Expect(peerPorts).NotTo(BeNil())
|
|
return peerPorts[portName]
|
|
}
|
|
|
|
func (n *Network) nextColor() string {
|
|
n.mutex.Lock()
|
|
defer n.mutex.Unlock()
|
|
color := n.colorIndex%14 + 31
|
|
if color > 37 {
|
|
color = color + 90 - 37
|
|
}
|
|
|
|
n.colorIndex++
|
|
return fmt.Sprintf("%dm", color)
|
|
}
|
|
|
|
// StartSession executes a command session. This should be used to launch
|
|
// command line tools that are expected to run to completion.
|
|
func (n *Network) StartSession(cmd *exec.Cmd, name string) (*gexec.Session, error) {
|
|
if d := n.throttleDuration(commandFingerprint(cmd)); d > 0 {
|
|
time.Sleep(d)
|
|
}
|
|
|
|
ansiColorCode := n.nextColor()
|
|
fmt.Fprintf(
|
|
ginkgo.GinkgoWriter,
|
|
"\x1b[33m[d]\x1b[%s[%s]\x1b[0m starting %s %s\n",
|
|
ansiColorCode,
|
|
name,
|
|
filepath.Base(cmd.Args[0]),
|
|
strings.Join(cmd.Args[1:], " "),
|
|
)
|
|
return gexec.Start(
|
|
cmd,
|
|
gexec.NewPrefixedWriter(
|
|
fmt.Sprintf("\x1b[32m[o]\x1b[%s[%s]\x1b[0m ", ansiColorCode, name),
|
|
ginkgo.GinkgoWriter,
|
|
),
|
|
gexec.NewPrefixedWriter(
|
|
fmt.Sprintf("\x1b[91m[e]\x1b[%s[%s]\x1b[0m ", ansiColorCode, name),
|
|
ginkgo.GinkgoWriter,
|
|
),
|
|
)
|
|
}
|
|
|
|
// commandFingerprint creates a string containing the program, args, and environment
|
|
// of an exec.Cmd. This fingerprint is used to identify repeated calls to the
|
|
// same command for throttling.
|
|
func commandFingerprint(cmd *exec.Cmd) string {
|
|
buf := bytes.NewBuffer(nil)
|
|
_, err := buf.WriteString(cmd.Dir)
|
|
Expect(err).NotTo(HaveOccurred())
|
|
_, err = buf.WriteString(cmd.Path)
|
|
Expect(err).NotTo(HaveOccurred())
|
|
|
|
// sort the environment since it's not positional.
|
|
env := append([]string(nil), cmd.Env...)
|
|
sort.Strings(env)
|
|
for _, e := range env {
|
|
_, err := buf.WriteString(e)
|
|
Expect(err).NotTo(HaveOccurred())
|
|
}
|
|
|
|
// grab the args but ignore references to temporary files
|
|
for _, arg := range cmd.Args {
|
|
if strings.HasPrefix(arg, os.TempDir()) {
|
|
continue
|
|
}
|
|
_, err := buf.WriteString(arg)
|
|
Expect(err).NotTo(HaveOccurred())
|
|
}
|
|
|
|
return buf.String()
|
|
}
|
|
|
|
// throttleDuration returns the time to wait before performing some operation
|
|
// representted by the fingerprint.
|
|
//
|
|
// The duration is determined by looking at when the fingerprinted operation
|
|
// was last executed by the network. If the time between now and the last
|
|
// execution was less than SessionCreateInterval, the difference between now
|
|
// and (execution + SessionCreateInterval) is returned. If more than
|
|
// SessionCreateInterval has elapsed since the last command execution, a
|
|
// duration of 0 is returned.
|
|
func (n *Network) throttleDuration(fingerprint string) time.Duration {
|
|
now := time.Now()
|
|
n.mutex.Lock()
|
|
last := n.lastExecuted[fingerprint]
|
|
n.lastExecuted[fingerprint] = now
|
|
n.mutex.Unlock()
|
|
|
|
if diff := last.Add(n.SessionCreateInterval).Sub(now); diff > 0 {
|
|
return diff
|
|
}
|
|
|
|
return 0
|
|
}
|
|
|
|
// GenerateCryptoConfig creates the `crypto-config.yaml` configuration file
|
|
// provided to `cryptogen` when running Bootstrap. The path to the generated
|
|
// file can be obtained from CryptoConfigPath.
|
|
func (n *Network) GenerateCryptoConfig() {
|
|
crypto, err := os.Create(n.CryptoConfigPath())
|
|
Expect(err).NotTo(HaveOccurred())
|
|
defer crypto.Close()
|
|
|
|
t, err := template.New("crypto").Parse(n.Templates.CryptoTemplate())
|
|
Expect(err).NotTo(HaveOccurred())
|
|
|
|
pw := gexec.NewPrefixedWriter("[crypto-config.yaml] ", ginkgo.GinkgoWriter)
|
|
err = t.Execute(io.MultiWriter(crypto, pw), n)
|
|
Expect(err).NotTo(HaveOccurred())
|
|
}
|
|
|
|
// GenerateConfigTxConfig creates the `configtx.yaml` configuration file
|
|
// provided to `configtxgen` when running Bootstrap. The path to the generated
|
|
// file can be obtained from ConfigTxConfigPath.
|
|
func (n *Network) GenerateConfigTxConfig() {
|
|
config, err := os.Create(n.ConfigTxConfigPath())
|
|
Expect(err).NotTo(HaveOccurred())
|
|
defer config.Close()
|
|
|
|
t, err := template.New("configtx").Parse(n.Templates.ConfigTxTemplate())
|
|
Expect(err).NotTo(HaveOccurred())
|
|
|
|
pw := gexec.NewPrefixedWriter("[configtx.yaml] ", ginkgo.GinkgoWriter)
|
|
err = t.Execute(io.MultiWriter(config, pw), n)
|
|
Expect(err).NotTo(HaveOccurred())
|
|
}
|
|
|
|
// GenerateOrdererConfig creates the `orderer.yaml` configuration file for the
|
|
// specified orderer. The path to the generated file can be obtained from
|
|
// OrdererConfigPath.
|
|
func (n *Network) GenerateOrdererConfig(o *Orderer) {
|
|
err := os.MkdirAll(n.OrdererDir(o), 0o755)
|
|
Expect(err).NotTo(HaveOccurred())
|
|
|
|
orderer, err := os.Create(n.OrdererConfigPath(o))
|
|
Expect(err).NotTo(HaveOccurred())
|
|
defer orderer.Close()
|
|
|
|
t, err := template.New("orderer").Funcs(template.FuncMap{
|
|
"Orderer": func() *Orderer { return o },
|
|
"ToLower": func(s string) string { return strings.ToLower(s) },
|
|
"ReplaceAll": func(s, old, new string) string { return strings.Replace(s, old, new, -1) },
|
|
}).Parse(n.Templates.OrdererTemplate())
|
|
Expect(err).NotTo(HaveOccurred())
|
|
|
|
pw := gexec.NewPrefixedWriter(fmt.Sprintf("[%s#orderer.yaml] ", o.ID()), ginkgo.GinkgoWriter)
|
|
err = t.Execute(io.MultiWriter(orderer, pw), n)
|
|
Expect(err).NotTo(HaveOccurred())
|
|
}
|
|
|
|
// GenerateCoreConfig creates the `core.yaml` configuration file for the
|
|
// specified peer. The path to the generated file can be obtained from
|
|
// PeerConfigPath.
|
|
func (n *Network) GenerateCoreConfig(p *Peer) {
|
|
err := os.MkdirAll(n.PeerDir(p), 0o755)
|
|
Expect(err).NotTo(HaveOccurred())
|
|
|
|
core, err := os.Create(n.PeerConfigPath(p))
|
|
Expect(err).NotTo(HaveOccurred())
|
|
defer core.Close()
|
|
|
|
t, err := template.New("peer").Funcs(template.FuncMap{
|
|
"Peer": func() *Peer { return p },
|
|
"ToLower": func(s string) string { return strings.ToLower(s) },
|
|
"ReplaceAll": func(s, old, new string) string { return strings.Replace(s, old, new, -1) },
|
|
}).Parse(n.Templates.CoreTemplate())
|
|
Expect(err).NotTo(HaveOccurred())
|
|
|
|
pw := gexec.NewPrefixedWriter(fmt.Sprintf("[%s#core.yaml] ", p.ID()), ginkgo.GinkgoWriter)
|
|
err = t.Execute(io.MultiWriter(core, pw), n)
|
|
Expect(err).NotTo(HaveOccurred())
|
|
}
|
|
|
|
func (n *Network) LoadAppChannelGenesisBlock(channelID string) *common.Block {
|
|
appGenesisPath := n.OutputBlockPath(channelID)
|
|
appGenesisBytes, err := ioutil.ReadFile(appGenesisPath)
|
|
Expect(err).NotTo(HaveOccurred())
|
|
appGenesisBlock, err := protoutil.UnmarshalBlock(appGenesisBytes)
|
|
Expect(err).NotTo(HaveOccurred())
|
|
return appGenesisBlock
|
|
}
|
|
|
|
// StartSingleOrdererNetwork starts the fabric processes assuming a single orderer.
|
|
func (n *Network) StartSingleOrdererNetwork(ordererName string) (*ginkgomon.Runner, ifrit.Process, ifrit.Process) {
|
|
ordererRunner, ordererProcess := n.StartOrderer(ordererName)
|
|
|
|
peerGroupRunner := n.PeerGroupRunner()
|
|
peerProcess := ifrit.Invoke(peerGroupRunner)
|
|
Eventually(peerProcess.Ready(), n.EventuallyTimeout).Should(BeClosed())
|
|
|
|
return ordererRunner, ordererProcess, peerProcess
|
|
}
|
|
|
|
func RestartSingleOrdererNetwork(ordererProcess, peerProcess ifrit.Process, network *Network) (*ginkgomon.Runner, ifrit.Process, ifrit.Process) {
|
|
peerProcess.Signal(syscall.SIGTERM)
|
|
Eventually(peerProcess.Wait(), network.EventuallyTimeout).Should(Receive())
|
|
ordererProcess.Signal(syscall.SIGTERM)
|
|
Eventually(ordererProcess.Wait(), network.EventuallyTimeout).Should(Receive())
|
|
|
|
ordererRunner := network.OrdererRunner(network.Orderer("orderer"))
|
|
ordererProcess = ifrit.Invoke(ordererRunner)
|
|
Eventually(ordererProcess.Ready(), network.EventuallyTimeout).Should(BeClosed())
|
|
Eventually(ordererRunner.Err(), network.EventuallyTimeout, time.Second).Should(gbytes.Say("Raft leader changed: 0 -> 1 channel=testchannel node=1"))
|
|
|
|
peerGroupRunner := network.PeerGroupRunner()
|
|
peerProcess = ifrit.Invoke(peerGroupRunner)
|
|
Eventually(peerProcess.Ready(), network.EventuallyTimeout).Should(BeClosed())
|
|
|
|
return ordererRunner, ordererProcess, peerProcess
|
|
}
|
|
|
|
func (n *Network) StartOrderer(ordererName string) (*ginkgomon.Runner, ifrit.Process) {
|
|
ordererRunner := n.OrdererRunner(n.Orderer(ordererName))
|
|
ordererProcess := ifrit.Invoke(ordererRunner)
|
|
Eventually(ordererProcess.Ready(), n.EventuallyTimeout).Should(BeClosed())
|
|
|
|
return ordererRunner, ordererProcess
|
|
}
|
|
|
|
// OrdererCert returns the path to the orderer's certificate.
|
|
func (n *Network) OrdererCert(o *Orderer) string {
|
|
org := n.Organization(o.Organization)
|
|
Expect(org).NotTo(BeNil())
|
|
|
|
return filepath.Join(
|
|
n.OrdererLocalMSPDir(o),
|
|
"signcerts",
|
|
fmt.Sprintf("%s.%s-cert.pem", o.Name, org.Domain),
|
|
)
|
|
}
|