2135 lines
81 KiB
Go
2135 lines
81 KiB
Go
/*
|
|
Copyright IBM Corp All Rights Reserved.
|
|
|
|
SPDX-License-Identifier: Apache-2.0
|
|
*/
|
|
|
|
package raft
|
|
|
|
import (
|
|
"bytes"
|
|
"crypto/x509"
|
|
"encoding/pem"
|
|
"fmt"
|
|
"io/ioutil"
|
|
"net/http"
|
|
"os"
|
|
"path"
|
|
"path/filepath"
|
|
"strings"
|
|
"syscall"
|
|
"time"
|
|
|
|
docker "github.com/fsouza/go-dockerclient"
|
|
"github.com/golang/protobuf/proto"
|
|
"github.com/hyperledger/fabric-protos-go/common"
|
|
"github.com/hyperledger/fabric-protos-go/msp"
|
|
protosorderer "github.com/hyperledger/fabric-protos-go/orderer"
|
|
"github.com/hyperledger/fabric-protos-go/orderer/etcdraft"
|
|
"github.com/hyperledger/fabric/common/crypto/tlsgen"
|
|
"github.com/hyperledger/fabric/integration/channelparticipation"
|
|
"github.com/hyperledger/fabric/integration/nwo"
|
|
"github.com/hyperledger/fabric/integration/nwo/commands"
|
|
"github.com/hyperledger/fabric/integration/ordererclient"
|
|
"github.com/hyperledger/fabric/protoutil"
|
|
. "github.com/onsi/ginkgo/v2"
|
|
. "github.com/onsi/gomega"
|
|
"github.com/onsi/gomega/gbytes"
|
|
"github.com/onsi/gomega/gexec"
|
|
"github.com/tedsuo/ifrit"
|
|
ginkgomon "github.com/tedsuo/ifrit/ginkgomon_v2"
|
|
)
|
|
|
|
var _ = Describe("EndToEnd reconfiguration and onboarding", func() {
|
|
var (
|
|
testDir string
|
|
client *docker.Client
|
|
network *nwo.Network
|
|
peer *nwo.Peer
|
|
|
|
networkProcess ifrit.Process
|
|
ordererProcesses []ifrit.Process
|
|
ordererRunners []*ginkgomon.Runner
|
|
)
|
|
|
|
BeforeEach(func() {
|
|
ordererRunners = nil
|
|
ordererProcesses = nil
|
|
|
|
var err error
|
|
testDir, err = ioutil.TempDir("", "e2e-etcdraft_reconfig")
|
|
Expect(err).NotTo(HaveOccurred())
|
|
|
|
client, err = docker.NewClientFromEnv()
|
|
Expect(err).NotTo(HaveOccurred())
|
|
})
|
|
|
|
AfterEach(func() {
|
|
if networkProcess != nil {
|
|
networkProcess.Signal(syscall.SIGTERM)
|
|
Eventually(networkProcess.Wait(), network.EventuallyTimeout).Should(Receive())
|
|
}
|
|
if network != nil {
|
|
network.Cleanup()
|
|
}
|
|
for _, ordererInstance := range ordererProcesses {
|
|
ordererInstance.Signal(syscall.SIGTERM)
|
|
Eventually(ordererInstance.Wait(), network.EventuallyTimeout).Should(Receive())
|
|
}
|
|
os.RemoveAll(testDir)
|
|
})
|
|
|
|
Describe("three node etcdraft network with 2 orgs", func() {
|
|
BeforeEach(func() {
|
|
network = nwo.New(nwo.MultiNodeEtcdRaft(), testDir, client, StartPort(), components)
|
|
network.GenerateConfigTree()
|
|
network.Bootstrap()
|
|
|
|
for _, o := range network.Orderers {
|
|
runner, process := network.StartOrderer(o.Name)
|
|
ordererRunners = append(ordererRunners, runner)
|
|
ordererProcesses = append(ordererProcesses, process)
|
|
}
|
|
|
|
networkRunner := network.PeerGroupRunner()
|
|
networkProcess = ifrit.Invoke(networkRunner)
|
|
Eventually(networkProcess.Ready(), network.EventuallyTimeout).Should(BeClosed())
|
|
})
|
|
|
|
// This tests:
|
|
//
|
|
// 1. channel creation with raft orderer,
|
|
// 2. all the nodes on three-node raft cluster are in sync wrt blocks,
|
|
// 3. raft orderer processes type A config updates and delivers the
|
|
// config blocks to the peers.
|
|
It("executes an etcdraft network with 2 orgs and three orderer nodes", func() {
|
|
orderer1 := network.Orderer("orderer1")
|
|
orderer2 := network.Orderer("orderer2")
|
|
orderer3 := network.Orderer("orderer3")
|
|
peer := network.Peer("Org1", "peer0")
|
|
blockFile1 := filepath.Join(testDir, "newest_orderer1_block.pb")
|
|
blockFile2 := filepath.Join(testDir, "newest_orderer2_block.pb")
|
|
blockFile3 := filepath.Join(testDir, "newest_orderer3_block.pb")
|
|
|
|
fetchLatestBlock := func(targetOrderer *nwo.Orderer, blockFile string) {
|
|
c := commands.ChannelFetch{
|
|
ChannelID: "testchannel",
|
|
Block: "newest",
|
|
OutputFile: blockFile,
|
|
}
|
|
if targetOrderer != nil {
|
|
c.Orderer = network.OrdererAddress(targetOrderer, nwo.ListenPort)
|
|
}
|
|
sess, err := network.PeerAdminSession(peer, c)
|
|
Expect(err).NotTo(HaveOccurred())
|
|
Eventually(sess, network.EventuallyTimeout).Should(gexec.Exit(0))
|
|
}
|
|
|
|
By("Creating a new channel, joining all orderers")
|
|
channelparticipation.JoinOrderersAppChannelCluster(network, "testchannel", network.Orderers...)
|
|
FindLeader(ordererRunners)
|
|
|
|
// the above can work even if the orderer nodes are not in the same Raft
|
|
// cluster; we need to verify all the three orderer nodes are in sync wrt
|
|
// blocks.
|
|
By("Fetching the latest blocks from all the orderer nodes and testing them for equality")
|
|
fetchLatestBlock(orderer1, blockFile1)
|
|
fetchLatestBlock(orderer2, blockFile2)
|
|
fetchLatestBlock(orderer3, blockFile3)
|
|
b1 := nwo.UnmarshalBlockFromFile(blockFile1)
|
|
b2 := nwo.UnmarshalBlockFromFile(blockFile2)
|
|
b3 := nwo.UnmarshalBlockFromFile(blockFile3)
|
|
Expect(protoutil.BlockHeaderBytes(b1.Header)).To(Equal(protoutil.BlockHeaderBytes(b2.Header)))
|
|
Expect(protoutil.BlockHeaderBytes(b2.Header)).To(Equal(protoutil.BlockHeaderBytes(b3.Header)))
|
|
})
|
|
})
|
|
|
|
Describe("Invalid Raft config metadata", func() {
|
|
It("refuses to join orderer to channel", func() {
|
|
By("Creating malformed genesis block")
|
|
network = nwo.New(nwo.BasicEtcdRaft(), testDir, client, StartPort(), components)
|
|
network.GenerateConfigTree()
|
|
network.Bootstrap()
|
|
|
|
// Change the genesis block
|
|
genesisPath := network.OutputBlockPath("testchannel")
|
|
genesisBlock := nwo.UnmarshalBlockFromFile(genesisPath)
|
|
envelope, err := protoutil.GetEnvelopeFromBlock(genesisBlock.Data.Data[0])
|
|
Expect(err).NotTo(HaveOccurred())
|
|
payload, err := protoutil.UnmarshalPayload(envelope.Payload)
|
|
Expect(err).NotTo(HaveOccurred())
|
|
configEnv := &common.ConfigEnvelope{}
|
|
err = proto.Unmarshal(payload.Data, configEnv)
|
|
Expect(err).NotTo(HaveOccurred())
|
|
genesisConfig := configEnv.GetConfig()
|
|
|
|
consensusTypeConfigValue := genesisConfig.ChannelGroup.Groups["Orderer"].Values["ConsensusType"]
|
|
consensusTypeValue := &protosorderer.ConsensusType{}
|
|
Expect(proto.Unmarshal(consensusTypeConfigValue.Value, consensusTypeValue)).To(Succeed())
|
|
|
|
metadata := &etcdraft.ConfigMetadata{}
|
|
Expect(proto.Unmarshal(consensusTypeValue.Metadata, metadata)).To(Succeed())
|
|
|
|
metadata.Options.HeartbeatTick = 10
|
|
metadata.Options.ElectionTick = 10
|
|
|
|
newMetadata, err := proto.Marshal(metadata)
|
|
Expect(err).NotTo(HaveOccurred())
|
|
consensusTypeValue.Metadata = newMetadata
|
|
|
|
genesisConfig.ChannelGroup.Groups["Orderer"].Values["ConsensusType"] = &common.ConfigValue{
|
|
ModPolicy: "Admins",
|
|
Value: protoutil.MarshalOrPanic(consensusTypeValue),
|
|
}
|
|
|
|
configEnv.Config = genesisConfig
|
|
payload.Data, err = protoutil.Marshal(configEnv)
|
|
Expect(err).NotTo(HaveOccurred())
|
|
envelope.Payload, err = protoutil.Marshal(payload)
|
|
Expect(err).NotTo(HaveOccurred())
|
|
genesisBlock.Data.Data[0], err = protoutil.Marshal(envelope)
|
|
Expect(err).NotTo(HaveOccurred())
|
|
genesisBlock.Header.DataHash = protoutil.BlockDataHash(genesisBlock.Data)
|
|
genesisBlockBytes, err := protoutil.Marshal(genesisBlock)
|
|
Expect(err).NotTo(HaveOccurred())
|
|
err = ioutil.WriteFile(network.OutputBlockPath("testchannel"), genesisBlockBytes, 0o644)
|
|
Expect(err).NotTo(HaveOccurred())
|
|
|
|
By("Starting orderer")
|
|
_, ordererProcess, peerProcess := network.StartSingleOrdererNetwork("orderer")
|
|
ordererProcesses = append(ordererProcesses, ordererProcess)
|
|
networkProcess = peerProcess
|
|
|
|
By("Joining the orderer to a channel with malformed genesis block")
|
|
orderer := network.Orderer("orderer")
|
|
channelparticipationJoinFailure(network, orderer, "testchannel", genesisBlock, http.StatusBadRequest,
|
|
"cannot join: failed to determine cluster membership from join-block: failed to validate config metadata of ordering config: ElectionTick (10) must be greater than HeartbeatTick (10)")
|
|
})
|
|
|
|
It("rejects config update", func() {
|
|
network = nwo.New(nwo.BasicEtcdRaft(), testDir, client, StartPort(), components)
|
|
network.GenerateConfigTree()
|
|
network.Bootstrap()
|
|
|
|
By("Starting orderer")
|
|
odererRunner, ordererProcess, peerProcess := network.StartSingleOrdererNetwork("orderer")
|
|
ordererProcesses = append(ordererProcesses, ordererProcess)
|
|
networkProcess = peerProcess
|
|
|
|
By("Joining the orderer to a channel")
|
|
orderer := network.Orderer("orderer")
|
|
channelparticipation.JoinOrdererJoinPeersAppChannel(network, "testchannel", orderer, odererRunner)
|
|
|
|
By("Submitting channel config update with illegal value")
|
|
org1Peer0 := network.Peer("Org1", "peer0")
|
|
config := nwo.GetConfig(network, org1Peer0, orderer, "testchannel")
|
|
updatedConfig := proto.Clone(config).(*common.Config)
|
|
|
|
consensusTypeConfigValue := updatedConfig.ChannelGroup.Groups["Orderer"].Values["ConsensusType"]
|
|
consensusTypeValue := &protosorderer.ConsensusType{}
|
|
Expect(proto.Unmarshal(consensusTypeConfigValue.Value, consensusTypeValue)).To(Succeed())
|
|
|
|
metadata := &etcdraft.ConfigMetadata{}
|
|
Expect(proto.Unmarshal(consensusTypeValue.Metadata, metadata)).To(Succeed())
|
|
|
|
metadata.Options.HeartbeatTick = 10
|
|
metadata.Options.ElectionTick = 10
|
|
|
|
newMetadata, err := proto.Marshal(metadata)
|
|
Expect(err).NotTo(HaveOccurred())
|
|
consensusTypeValue.Metadata = newMetadata
|
|
|
|
updatedConfig.ChannelGroup.Groups["Orderer"].Values["ConsensusType"] = &common.ConfigValue{
|
|
ModPolicy: "Admins",
|
|
Value: protoutil.MarshalOrPanic(consensusTypeValue),
|
|
}
|
|
|
|
sess := nwo.UpdateOrdererConfigSession(network, orderer, "testchannel", config, updatedConfig, org1Peer0, orderer)
|
|
Eventually(sess, network.EventuallyTimeout).Should(gexec.Exit(1))
|
|
Expect(sess.Err).To(gbytes.Say(`invalid new config metadata: ElectionTick \(10\) must be greater than HeartbeatTick \(10\)`))
|
|
})
|
|
})
|
|
|
|
When("a single node cluster is expanded", func() {
|
|
It("is still possible to onboard the new cluster member and then another one with a different TLS root CA", func() {
|
|
launch := func(o *nwo.Orderer) {
|
|
runner := network.OrdererRunner(o)
|
|
ordererRunners = append(ordererRunners, runner)
|
|
|
|
process := ifrit.Invoke(runner)
|
|
Eventually(process.Ready(), network.EventuallyTimeout).Should(BeClosed())
|
|
ordererProcesses = append(ordererProcesses, process)
|
|
}
|
|
|
|
network = nwo.New(nwo.BasicEtcdRaft(), testDir, client, StartPort(), components)
|
|
network.GenerateConfigTree()
|
|
network.Bootstrap()
|
|
|
|
orderer := network.Orderer("orderer")
|
|
peer = network.Peer("Org1", "peer0")
|
|
|
|
By("Launching the orderer")
|
|
launch(orderer)
|
|
|
|
By("Joining the orderer to a channel")
|
|
channelparticipation.JoinOrderersAppChannelCluster(network, "testchannel", orderer)
|
|
|
|
By("Checking that it elected itself as a leader")
|
|
FindLeader(ordererRunners)
|
|
|
|
By("Extending the network configuration to add a new orderer")
|
|
orderer2 := &nwo.Orderer{
|
|
Name: "orderer2",
|
|
Organization: "OrdererOrg",
|
|
}
|
|
ports := nwo.Ports{}
|
|
for _, portName := range nwo.OrdererPortNames() {
|
|
ports[portName] = network.ReservePort()
|
|
}
|
|
network.PortsByOrdererID[orderer2.ID()] = ports
|
|
network.Orderers = append(network.Orderers, orderer2)
|
|
network.GenerateOrdererConfig(orderer2)
|
|
extendNetwork(network)
|
|
|
|
secondOrdererCertificatePath := filepath.Join(network.OrdererLocalTLSDir(orderer2), "server.crt")
|
|
secondOrdererCertificate, err := ioutil.ReadFile(secondOrdererCertificatePath)
|
|
Expect(err).NotTo(HaveOccurred())
|
|
|
|
By("Adding the second orderer")
|
|
addConsenter(network, peer, orderer, "testchannel", etcdraft.Consenter{
|
|
ServerTlsCert: secondOrdererCertificate,
|
|
ClientTlsCert: secondOrdererCertificate,
|
|
Host: "127.0.0.1",
|
|
Port: uint32(network.OrdererPort(orderer2, nwo.ClusterPort)),
|
|
})
|
|
|
|
By("Obtaining the last config block from the orderer")
|
|
configBlock := nwo.GetConfigBlock(network, peer, orderer, "testchannel")
|
|
|
|
By("Waiting for the existing orderer to relinquish its leadership")
|
|
Eventually(ordererRunners[0].Err(), network.EventuallyTimeout).Should(gbytes.Say("1 stepped down to follower since quorum is not active"))
|
|
Eventually(ordererRunners[0].Err(), network.EventuallyTimeout).Should(gbytes.Say("No leader is present, cluster size is 2"))
|
|
|
|
By("Launching the second orderer")
|
|
launch(orderer2)
|
|
|
|
By("Joining the second orderer to the channel, as consenter")
|
|
expectedChannelInfo := channelparticipation.ChannelInfo{
|
|
Name: "testchannel",
|
|
URL: "/participation/v1/channels/testchannel",
|
|
Status: "onboarding",
|
|
ConsensusRelation: "consenter",
|
|
Height: 0,
|
|
}
|
|
channelparticipation.Join(network, orderer2, "testchannel", configBlock, expectedChannelInfo)
|
|
|
|
By("Waiting for a leader to be re-elected")
|
|
FindLeader(ordererRunners)
|
|
|
|
// In the next part of the test we're going to bring up a third node
|
|
// with a different TLS root CA. We're then going to remove the TLS
|
|
// root CA and restart the orderer, to ensure that we can dynamically
|
|
// update TLS root CAs in Raft while membership stays the same.
|
|
|
|
By("Creating configuration for a third orderer with a different TLS root CA")
|
|
orderer3 := &nwo.Orderer{
|
|
Name: "orderer3",
|
|
Organization: "OrdererOrg",
|
|
}
|
|
ports = nwo.Ports{}
|
|
for _, portName := range nwo.OrdererPortNames() {
|
|
ports[portName] = network.ReservePort()
|
|
}
|
|
network.PortsByOrdererID[orderer3.ID()] = ports
|
|
network.Orderers = append(network.Orderers, orderer3)
|
|
network.GenerateOrdererConfig(orderer3)
|
|
|
|
tmpDir, err := ioutil.TempDir("", "e2e-etcfraft_reconfig")
|
|
Expect(err).NotTo(HaveOccurred())
|
|
defer os.RemoveAll(tmpDir)
|
|
|
|
sess, err := network.Cryptogen(commands.Generate{
|
|
Config: network.CryptoConfigPath(),
|
|
Output: tmpDir,
|
|
})
|
|
Expect(err).NotTo(HaveOccurred())
|
|
Eventually(sess, network.EventuallyTimeout).Should(gexec.Exit(0))
|
|
|
|
name := network.Orderers[0].Name
|
|
domain := network.Organization(network.Orderers[0].Organization).Domain
|
|
nameDomain := fmt.Sprintf("%s.%s", name, domain)
|
|
ordererTLSPath := filepath.Join(tmpDir, "ordererOrganizations", domain, "orderers", nameDomain, "tls")
|
|
|
|
caCertPath := filepath.Join(tmpDir, "ordererOrganizations", domain, "tlsca", fmt.Sprintf("tlsca.%s-cert.pem", domain))
|
|
|
|
caCert, err := ioutil.ReadFile(caCertPath)
|
|
Expect(err).NotTo(HaveOccurred())
|
|
|
|
thirdOrdererCertificatePath := filepath.Join(ordererTLSPath, "server.crt")
|
|
thirdOrdererCertificate, err := ioutil.ReadFile(thirdOrdererCertificatePath)
|
|
Expect(err).NotTo(HaveOccurred())
|
|
|
|
By("Updating it on the file system")
|
|
err = ioutil.WriteFile(caCertPath, caCert, 0o644)
|
|
Expect(err).NotTo(HaveOccurred())
|
|
err = ioutil.WriteFile(thirdOrdererCertificatePath, thirdOrdererCertificate, 0o644)
|
|
Expect(err).NotTo(HaveOccurred())
|
|
|
|
By("Overwriting the TLS directory of the new orderer")
|
|
for _, fileName := range []string{"server.crt", "server.key", "ca.crt"} {
|
|
dst := filepath.Join(network.OrdererLocalTLSDir(orderer3), fileName)
|
|
|
|
data, err := ioutil.ReadFile(filepath.Join(ordererTLSPath, fileName))
|
|
Expect(err).NotTo(HaveOccurred())
|
|
|
|
err = ioutil.WriteFile(dst, data, 0o644)
|
|
Expect(err).NotTo(HaveOccurred())
|
|
}
|
|
|
|
By("Launching orderer3")
|
|
launch(orderer3)
|
|
|
|
By("Obtaining the last config block from the orderer")
|
|
configBlock = nwo.GetConfigBlock(network, peer, orderer, "testchannel")
|
|
|
|
By("Joining the third orderer to the channel, as follower")
|
|
expectedChannelInfo = channelparticipation.ChannelInfo{
|
|
Name: "testchannel",
|
|
URL: "/participation/v1/channels/testchannel",
|
|
Status: "onboarding",
|
|
ConsensusRelation: "follower",
|
|
Height: 0,
|
|
}
|
|
channelparticipation.Join(network, orderer3, "testchannel", configBlock, expectedChannelInfo)
|
|
|
|
By("Expanding the TLS root CA certificates and adding orderer3 to the channel")
|
|
updateOrdererMSPAndConsensusMetadata(network, peer, orderer, "testchannel", "OrdererOrg",
|
|
func(config msp.FabricMSPConfig) msp.FabricMSPConfig { // MSP mutator
|
|
config.TlsRootCerts = append(config.TlsRootCerts, caCert)
|
|
return config
|
|
},
|
|
func(metadata *etcdraft.ConfigMetadata) { // etcdraft mutator
|
|
metadata.Consenters = append(metadata.Consenters, &etcdraft.Consenter{
|
|
ServerTlsCert: thirdOrdererCertificate,
|
|
ClientTlsCert: thirdOrdererCertificate,
|
|
Host: "127.0.0.1",
|
|
Port: uint32(network.OrdererPort(orderer3, nwo.ClusterPort)),
|
|
})
|
|
},
|
|
)
|
|
|
|
By("Waiting for orderer3 to see the leader")
|
|
FindLeader([]*ginkgomon.Runner{ordererRunners[2]})
|
|
|
|
Expect(channelparticipation.ListOne(network, orderer3, "testchannel")).To(Equal(
|
|
channelparticipation.ChannelInfo{
|
|
Name: "testchannel",
|
|
URL: "/participation/v1/channels/testchannel",
|
|
Status: "active",
|
|
ConsensusRelation: "consenter",
|
|
Height: 3,
|
|
}))
|
|
|
|
By("Attemping to add a consenter with invalid certs")
|
|
// create new certs that are not in the channel config
|
|
ca, err := tlsgen.NewCA()
|
|
Expect(err).NotTo(HaveOccurred())
|
|
client, err := ca.NewClientCertKeyPair()
|
|
Expect(err).NotTo(HaveOccurred())
|
|
|
|
newConsenterCertPem, _ := pem.Decode(client.Cert)
|
|
newConsenterCert, err := x509.ParseCertificate(newConsenterCertPem.Bytes)
|
|
Expect(err).NotTo(HaveOccurred())
|
|
|
|
newConsenterHost := "127.0.0.1"
|
|
newConsenterPort := uint32(network.OrdererPort(orderer3, nwo.ListenPort))
|
|
|
|
current, updated := consenterAdder(
|
|
network,
|
|
peer,
|
|
orderer,
|
|
"testchannel",
|
|
etcdraft.Consenter{
|
|
ServerTlsCert: client.Cert,
|
|
ClientTlsCert: client.Cert,
|
|
Host: newConsenterHost,
|
|
Port: newConsenterPort,
|
|
},
|
|
)
|
|
sess = nwo.UpdateOrdererConfigSession(network, orderer, "testchannel", current, updated, peer, orderer)
|
|
Eventually(sess, network.EventuallyTimeout).Should(gexec.Exit(1))
|
|
Expect(sess.Err).To(gbytes.Say(fmt.Sprintf("BAD_REQUEST -- error applying config update to existing channel 'testchannel': consensus metadata update for channel config update is invalid: invalid new config metadata: consenter %s:%d has invalid certificate: verifying tls client cert with serial number %d: x509: certificate signed by unknown authority", newConsenterHost, newConsenterPort, newConsenterCert.SerialNumber)))
|
|
})
|
|
})
|
|
|
|
When("a single node cluster has the tick interval overridden", func() {
|
|
It("reflects this in its startup logs", func() {
|
|
network = nwo.New(nwo.BasicEtcdRaft(), testDir, client, StartPort(), components)
|
|
network.GenerateConfigTree()
|
|
network.Bootstrap()
|
|
|
|
orderer := network.Orderer("orderer")
|
|
ordererConfig := network.ReadOrdererConfig(orderer)
|
|
ordererConfig.Consensus["TickIntervalOverride"] = "642ms"
|
|
network.WriteOrdererConfig(orderer, ordererConfig)
|
|
|
|
By("Launching the orderer")
|
|
runner := network.OrdererRunner(orderer)
|
|
ordererRunners = append(ordererRunners, runner)
|
|
|
|
process := ifrit.Invoke(runner)
|
|
Eventually(process.Ready(), network.EventuallyTimeout).Should(BeClosed())
|
|
ordererProcesses = append(ordererProcesses, process)
|
|
|
|
By("Joining the orderer to a channel")
|
|
channelparticipation.JoinOrderersAppChannelCluster(network, "testchannel", orderer)
|
|
|
|
Eventually(runner.Err()).Should(gbytes.Say("TickIntervalOverride is set, overriding channel configuration tick interval to 642ms"))
|
|
})
|
|
})
|
|
|
|
When("the orderer certificates are all rotated", func() {
|
|
It("is possible to rotate certificate by adding & removing cert in single config", func() {
|
|
layout := nwo.MultiNodeEtcdRaft()
|
|
network = nwo.New(layout, testDir, client, StartPort(), components)
|
|
network.GenerateConfigTree()
|
|
network.Bootstrap()
|
|
|
|
o1, o2, o3 := network.Orderer("orderer1"), network.Orderer("orderer2"), network.Orderer("orderer3")
|
|
orderers := []*nwo.Orderer{o1, o2, o3}
|
|
peer = network.Peer("Org1", "peer0")
|
|
|
|
By("Launching the orderers")
|
|
for _, o := range orderers {
|
|
runner := network.OrdererRunner(o)
|
|
ordererRunners = append(ordererRunners, runner)
|
|
process := ifrit.Invoke(runner)
|
|
ordererProcesses = append(ordererProcesses, process)
|
|
}
|
|
|
|
for _, ordererProc := range ordererProcesses {
|
|
Eventually(ordererProc.Ready(), network.EventuallyTimeout).Should(BeClosed())
|
|
}
|
|
|
|
By("Creating a new channel, joining all orderers")
|
|
channelparticipation.JoinOrderersAppChannelCluster(network, "testchannel", network.Orderers...)
|
|
|
|
By("Finding leader")
|
|
leader := FindLeader(ordererRunners)
|
|
leaderIndex := leader - 1
|
|
blockSeq := 0
|
|
|
|
By("Checking that all orderers are online")
|
|
assertBlockReception(map[string]int{
|
|
"testchannel": blockSeq,
|
|
}, orderers, peer, network)
|
|
|
|
By("Preparing new certificates for the orderer nodes")
|
|
extendNetwork(network)
|
|
certificateRotations := refreshOrdererPEMs(network)
|
|
|
|
swap := func(o *nwo.Orderer, certificate []byte, c etcdraft.Consenter) {
|
|
updateEtcdRaftMetadata(network, peer, o, "testchannel", func(metadata *etcdraft.ConfigMetadata) {
|
|
var newConsenters []*etcdraft.Consenter
|
|
for _, consenter := range metadata.Consenters {
|
|
if bytes.Equal(consenter.ClientTlsCert, certificate) || bytes.Equal(consenter.ServerTlsCert, certificate) {
|
|
continue
|
|
}
|
|
newConsenters = append(newConsenters, consenter)
|
|
}
|
|
newConsenters = append(newConsenters, &c)
|
|
|
|
metadata.Consenters = newConsenters
|
|
})
|
|
blockSeq++
|
|
}
|
|
|
|
rotate := func(target int) {
|
|
// submit a config tx to rotate the cert of an orderer.
|
|
// The orderer being rotated is going to be unavailable
|
|
// eventually, therefore submitter of tx is different
|
|
// from the target, so the configuration can be reliably
|
|
// checked.
|
|
submitter := (target + 1) % 3
|
|
rotation := certificateRotations[target]
|
|
targetOrderer := network.Orderers[target]
|
|
remainder := func() []*nwo.Orderer {
|
|
var ret []*nwo.Orderer
|
|
for i, o := range network.Orderers {
|
|
if i == target {
|
|
continue
|
|
}
|
|
ret = append(ret, o)
|
|
}
|
|
return ret
|
|
}()
|
|
submitterOrderer := network.Orderers[submitter]
|
|
port := network.OrdererPort(targetOrderer, nwo.ClusterPort)
|
|
|
|
fmt.Fprintf(GinkgoWriter, "Rotating certificate of orderer node %d\n", target+1)
|
|
swap(submitterOrderer, rotation.oldCert, etcdraft.Consenter{
|
|
ServerTlsCert: rotation.newCert,
|
|
ClientTlsCert: rotation.newCert,
|
|
Host: "127.0.0.1",
|
|
Port: uint32(port),
|
|
})
|
|
|
|
By("Waiting for all orderers to sync")
|
|
assertBlockReception(map[string]int{
|
|
"testchannel": blockSeq,
|
|
}, remainder, peer, network)
|
|
|
|
By("Waiting for rotated node to be unavailable")
|
|
c := commands.ChannelFetch{
|
|
ChannelID: "testchannel",
|
|
Block: "newest",
|
|
OutputFile: "/dev/null",
|
|
Orderer: network.OrdererAddress(targetOrderer, nwo.ClusterPort),
|
|
}
|
|
Eventually(func() string {
|
|
sess, err := network.OrdererAdminSession(targetOrderer, peer, c)
|
|
Expect(err).NotTo(HaveOccurred())
|
|
Eventually(sess, network.EventuallyTimeout).Should(gexec.Exit())
|
|
if sess.ExitCode() != 0 {
|
|
return fmt.Sprintf("exit code is %d: %s", sess.ExitCode(), string(sess.Err.Contents()))
|
|
}
|
|
sessErr := string(sess.Err.Contents())
|
|
expected := fmt.Sprintf("Received block: %d", blockSeq)
|
|
if strings.Contains(sessErr, expected) {
|
|
return ""
|
|
}
|
|
return sessErr
|
|
}, network.EventuallyTimeout, time.Second).ShouldNot(BeEmpty())
|
|
|
|
By("Killing the orderer")
|
|
ordererProcesses[target].Signal(syscall.SIGTERM)
|
|
Eventually(ordererProcesses[target].Wait(), network.EventuallyTimeout).Should(Receive())
|
|
|
|
By("Starting the orderer again")
|
|
ordererRunner := network.OrdererRunner(targetOrderer)
|
|
ordererRunners = append(ordererRunners, ordererRunner)
|
|
ordererProcesses[target] = ifrit.Invoke(ordererRunner)
|
|
Eventually(ordererProcesses[target].Ready(), network.EventuallyTimeout).Should(BeClosed())
|
|
|
|
By("And waiting for it to stabilize")
|
|
assertBlockReception(map[string]int{
|
|
"testchannel": blockSeq,
|
|
}, orderers, peer, network)
|
|
}
|
|
|
|
By(fmt.Sprintf("Rotating cert on leader %d", leader))
|
|
rotate(leaderIndex)
|
|
|
|
By("Rotating certificates of other orderer nodes")
|
|
for i := range certificateRotations {
|
|
if i != leaderIndex {
|
|
rotate(i)
|
|
}
|
|
}
|
|
})
|
|
|
|
It("is still possible to onboard new orderers", func() {
|
|
// In this test, we have 3 OSNs and we rotate their TLS certificates one by one,
|
|
// by adding the future certificate to the channel, killing the OSN to make it
|
|
// grab the new certificate, and then removing the old certificate from the channel.
|
|
|
|
// After we completely rotate all the certificates, we put the last config block
|
|
// of the system channel into the file system of orderer4, and then launch it,
|
|
// and ensure it onboards and pulls channels testchannel only, and not testchannel2
|
|
// which it is not part of.
|
|
|
|
// Consenter i after its certificate is rotated is denoted as consenter i'
|
|
// The blocks of channels contain the following updates:
|
|
// | testchannel height | update description
|
|
// ------------------------------------------------------------------------
|
|
// 0 | 1 | adding consenter 1'
|
|
// 1 | 2 | removing consenter 1
|
|
// 2 | 3 | adding consenter 2'
|
|
// 3 | 4 | removing consenter 2
|
|
// 4 | 5 | adding consenter 3'
|
|
// 5 | 6 | removing consenter 3
|
|
// 6 | 7 | adding consenter 4
|
|
|
|
layout := nwo.MultiNodeEtcdRaft()
|
|
layout.Channels = append(layout.Channels, &nwo.Channel{
|
|
Name: "testchannel2",
|
|
Profile: "TwoOrgsAppChannelEtcdRaft",
|
|
}, &nwo.Channel{
|
|
Name: "testchannel3",
|
|
Profile: "TwoOrgsAppChannelEtcdRaft",
|
|
})
|
|
|
|
network = nwo.New(layout, testDir, client, StartPort(), components)
|
|
network.GenerateConfigTree()
|
|
network.Bootstrap()
|
|
|
|
o1, o2, o3 := network.Orderer("orderer1"), network.Orderer("orderer2"), network.Orderer("orderer3")
|
|
orderers := []*nwo.Orderer{o1, o2, o3}
|
|
peer = network.Peer("Org1", "peer0")
|
|
|
|
By("Launching the orderers")
|
|
for _, o := range orderers {
|
|
runner := network.OrdererRunner(o)
|
|
ordererRunners = append(ordererRunners, runner)
|
|
process := ifrit.Invoke(runner)
|
|
ordererProcesses = append(ordererProcesses, process)
|
|
}
|
|
|
|
for _, ordererProc := range ordererProcesses {
|
|
Eventually(ordererProc.Ready(), network.EventuallyTimeout).Should(BeClosed())
|
|
}
|
|
|
|
By("Creating a channel, joining all orderers")
|
|
channelparticipation.JoinOrderersAppChannelCluster(network, "testchannel", network.Orderers...)
|
|
|
|
By("Finding leader")
|
|
_ = FindLeader(ordererRunners)
|
|
|
|
By("Checking that all orderers are online")
|
|
assertBlockReception(map[string]int{
|
|
"testchannel": 0,
|
|
}, orderers, peer, network)
|
|
|
|
By("Preparing new certificates for the orderer nodes")
|
|
extendNetwork(network)
|
|
certificateRotations := refreshOrdererPEMs(network)
|
|
|
|
expectedBlockNumPerChannel := []map[string]int{
|
|
{"testchannel": 1},
|
|
{"testchannel": 2},
|
|
{"testchannel": 3},
|
|
{"testchannel": 4},
|
|
{"testchannel": 5},
|
|
{"testchannel": 6},
|
|
}
|
|
|
|
for i, rotation := range certificateRotations {
|
|
o := network.Orderers[i]
|
|
port := network.OrdererPort(o, nwo.ClusterPort)
|
|
|
|
By(fmt.Sprintf("Adding the future certificate of orderer node %d", i))
|
|
for _, channelName := range []string{"testchannel"} {
|
|
addConsenter(network, peer, o, channelName, etcdraft.Consenter{
|
|
ServerTlsCert: rotation.newCert,
|
|
ClientTlsCert: rotation.newCert,
|
|
Host: "127.0.0.1",
|
|
Port: uint32(port),
|
|
})
|
|
}
|
|
|
|
By("Waiting for all orderers to sync")
|
|
assertBlockReception(expectedBlockNumPerChannel[i*2], orderers, peer, network)
|
|
|
|
By("Killing the orderer")
|
|
ordererProcesses[i].Signal(syscall.SIGTERM)
|
|
Eventually(ordererProcesses[i].Wait(), network.EventuallyTimeout).Should(Receive())
|
|
|
|
By("Starting the orderer again")
|
|
ordererRunner := network.OrdererRunner(orderers[i])
|
|
ordererRunners = append(ordererRunners, ordererRunner)
|
|
ordererProcesses[i] = ifrit.Invoke(ordererRunner)
|
|
Eventually(ordererProcesses[i].Ready(), network.EventuallyTimeout).Should(BeClosed())
|
|
|
|
By("And waiting for it to stabilize")
|
|
assertBlockReception(expectedBlockNumPerChannel[i*2], orderers, peer, network)
|
|
|
|
By("Removing the previous certificate of the old orderer")
|
|
for _, channelName := range []string{"testchannel"} {
|
|
removeConsenter(network, peer, network.Orderers[(i+1)%len(network.Orderers)], channelName, rotation.oldCert)
|
|
}
|
|
|
|
By("Waiting for all orderers to sync")
|
|
assertBlockReception(expectedBlockNumPerChannel[i*2+1], orderers, peer, network)
|
|
}
|
|
|
|
By("Getting the last config block from testchannel and using it as a template for testchannel2 and testchannel3 genesis block")
|
|
blockFile := filepath.Join(testDir, "testchannel_last_config_block.pb")
|
|
c := commands.ChannelFetch{
|
|
ChannelID: "testchannel",
|
|
Block: "newest",
|
|
OutputFile: blockFile,
|
|
}
|
|
c.Orderer = network.OrdererAddress(o1, nwo.ListenPort)
|
|
sess, err := network.PeerAdminSession(peer, c)
|
|
Expect(err).NotTo(HaveOccurred())
|
|
Eventually(sess, network.EventuallyTimeout).Should(gexec.Exit(0))
|
|
lastConfigBlock := nwo.UnmarshalBlockFromFile(blockFile)
|
|
|
|
genesisBlock2 := configToGenesisBlock(lastConfigBlock, "testchannel2")
|
|
genesisBytes2, err := protoutil.Marshal(genesisBlock2)
|
|
Expect(err).NotTo(HaveOccurred())
|
|
path2 := network.OutputBlockPath("testchannel2")
|
|
err = ioutil.WriteFile(path2, genesisBytes2, 0o644)
|
|
Expect(err).NotTo(HaveOccurred())
|
|
|
|
genesisBlock3 := configToGenesisBlock(lastConfigBlock, "testchannel3")
|
|
genesisBytes3, err := protoutil.Marshal(genesisBlock3)
|
|
Expect(err).NotTo(HaveOccurred())
|
|
path3 := network.OutputBlockPath("testchannel3")
|
|
err = ioutil.WriteFile(path3, genesisBytes3, 0o644)
|
|
Expect(err).NotTo(HaveOccurred())
|
|
|
|
By("Creating a testchannel2, joining all orderers")
|
|
channelparticipation.JoinOrderersAppChannelCluster(network, "testchannel2", network.Orderers...)
|
|
|
|
By("Creating a testchannel3, joining all orderers")
|
|
channelparticipation.JoinOrderersAppChannelCluster(network, "testchannel3", network.Orderers...)
|
|
|
|
assertBlockReception(map[string]int{
|
|
"testchannel2": 0,
|
|
"testchannel3": 0,
|
|
}, orderers, peer, network)
|
|
|
|
o4 := &nwo.Orderer{
|
|
Name: "orderer4",
|
|
Organization: "OrdererOrg",
|
|
}
|
|
|
|
By("Configuring orderer4 in the network")
|
|
ports := nwo.Ports{}
|
|
for _, portName := range nwo.OrdererPortNames() {
|
|
ports[portName] = network.ReservePort()
|
|
}
|
|
network.PortsByOrdererID[o4.ID()] = ports
|
|
|
|
network.Orderers = append(network.Orderers, o4)
|
|
network.GenerateOrdererConfig(network.Orderer("orderer4"))
|
|
|
|
By("Adding orderer4 to the channel")
|
|
orderer4CertificatePath := filepath.Join(network.OrdererLocalTLSDir(o4), "server.crt")
|
|
orderer4Certificate, err := ioutil.ReadFile(orderer4CertificatePath)
|
|
Expect(err).NotTo(HaveOccurred())
|
|
|
|
addConsenter(network, peer, o1, "testchannel", etcdraft.Consenter{
|
|
ServerTlsCert: orderer4Certificate,
|
|
ClientTlsCert: orderer4Certificate,
|
|
Host: "127.0.0.1",
|
|
Port: uint32(network.OrdererPort(o4, nwo.ClusterPort)),
|
|
})
|
|
|
|
By("Ensuring all orderers know about orderer4's addition")
|
|
assertBlockReception(map[string]int{
|
|
"testchannel": 7,
|
|
}, orderers, peer, network)
|
|
|
|
By("Broadcasting envelope to testchannel")
|
|
env := CreateBroadcastEnvelope(network, peer, "testchannel", []byte("hello"))
|
|
resp, err := ordererclient.Broadcast(network, o1, env)
|
|
Expect(err).NotTo(HaveOccurred())
|
|
Expect(resp.Status).To(Equal(common.Status_SUCCESS))
|
|
|
|
assertBlockReception(map[string]int{
|
|
"testchannel": 8,
|
|
}, orderers, peer, network)
|
|
|
|
By("Corrupting the readers policy of testchannel3")
|
|
revokeReaderAccess(network, "testchannel3", o3, peer)
|
|
|
|
By("Launching orderer4")
|
|
orderers = append(orderers, o4)
|
|
orderer4Runner := network.OrdererRunner(o4)
|
|
ordererRunners = append(ordererRunners, orderer4Runner)
|
|
// Spawn orderer4's process
|
|
o4process := ifrit.Invoke(orderer4Runner)
|
|
Eventually(o4process.Ready(), network.EventuallyTimeout).Should(BeClosed())
|
|
ordererProcesses = append(ordererProcesses, o4process)
|
|
// Get the last config block of the testchannel
|
|
configBlock := nwo.GetConfigBlock(network, peer, o1, "testchannel")
|
|
|
|
By("Joining the fourth orderer to the channel, as consenter")
|
|
expectedChannelInfo := channelparticipation.ChannelInfo{
|
|
Name: "testchannel",
|
|
URL: "/participation/v1/channels/testchannel",
|
|
Status: "onboarding",
|
|
ConsensusRelation: "consenter",
|
|
Height: 0,
|
|
}
|
|
channelparticipation.Join(network, o4, "testchannel", configBlock, expectedChannelInfo)
|
|
|
|
By("And waiting for it to sync with the rest of the orderers")
|
|
assertBlockReception(map[string]int{
|
|
"testchannel": 8,
|
|
}, orderers, peer, network)
|
|
|
|
By("Ensuring orderer4 doesn't serve testchannel2 and testchannel3")
|
|
env = CreateBroadcastEnvelope(network, peer, "testchannel2", []byte("hello"))
|
|
resp, err = ordererclient.Broadcast(network, o4, env)
|
|
Expect(err).NotTo(HaveOccurred())
|
|
Expect(resp.Status).To(Equal(common.Status_BAD_REQUEST))
|
|
Expect(orderer4Runner.Err()).To(gbytes.Say("channel does not exist"))
|
|
|
|
env = CreateBroadcastEnvelope(network, peer, "testchannel3", []byte("hello"))
|
|
resp, err = ordererclient.Broadcast(network, o4, env)
|
|
Expect(err).NotTo(HaveOccurred())
|
|
Expect(resp.Status).To(Equal(common.Status_BAD_REQUEST))
|
|
Expect(orderer4Runner.Err()).To(gbytes.Say("channel does not exist"))
|
|
|
|
By("Adding orderer4 to testchannel2")
|
|
addConsenter(network, peer, o1, "testchannel2", etcdraft.Consenter{
|
|
ServerTlsCert: orderer4Certificate,
|
|
ClientTlsCert: orderer4Certificate,
|
|
Host: "127.0.0.1",
|
|
Port: uint32(network.OrdererPort(o4, nwo.ClusterPort)),
|
|
})
|
|
|
|
// Get the last config block of the testchannel
|
|
configBlock = nwo.GetConfigBlock(network, peer, o1, "testchannel2")
|
|
expectedChannelInfo = channelparticipation.ChannelInfo{
|
|
Name: "testchannel2",
|
|
URL: "/participation/v1/channels/testchannel2",
|
|
Status: "onboarding",
|
|
ConsensusRelation: "consenter",
|
|
Height: 0,
|
|
}
|
|
channelparticipation.Join(network, o4, "testchannel2", configBlock, expectedChannelInfo)
|
|
|
|
By("Waiting for orderer4 and to replicate testchannel2")
|
|
assertBlockReception(map[string]int{
|
|
"testchannel2": 1,
|
|
}, []*nwo.Orderer{o4}, peer, network)
|
|
|
|
By("Ensuring orderer4 doesn't have any errors in the logs")
|
|
Consistently(orderer4Runner.Err()).ShouldNot(gbytes.Say("ERRO"))
|
|
|
|
By("Submitting a transaction through orderer4")
|
|
env = CreateBroadcastEnvelope(network, peer, "testchannel2", []byte("hello"))
|
|
resp, err = ordererclient.Broadcast(network, o4, env)
|
|
Expect(err).NotTo(HaveOccurred())
|
|
Expect(resp.Status).To(Equal(common.Status_SUCCESS))
|
|
|
|
By("And ensuring it is propagated amongst all orderers")
|
|
assertBlockReception(map[string]int{
|
|
"testchannel2": 2,
|
|
}, orderers, peer, network)
|
|
|
|
By("Adding orderer4 to testchannel3")
|
|
addConsenter(network, peer, o1, "testchannel3", etcdraft.Consenter{
|
|
ServerTlsCert: orderer4Certificate,
|
|
ClientTlsCert: orderer4Certificate,
|
|
Host: "127.0.0.1",
|
|
Port: uint32(network.OrdererPort(o4, nwo.ClusterPort)),
|
|
})
|
|
|
|
// Get the last config block of the testchannel3
|
|
configBlock = nwo.GetConfigBlock(network, peer, o1, "testchannel3")
|
|
expectedChannelInfo = channelparticipation.ChannelInfo{
|
|
Name: "testchannel3",
|
|
URL: "/participation/v1/channels/testchannel3",
|
|
Status: "onboarding",
|
|
ConsensusRelation: "consenter",
|
|
Height: 0,
|
|
}
|
|
channelparticipation.Join(network, o4, "testchannel3", configBlock, expectedChannelInfo)
|
|
|
|
By("checking the logs that indicate that channel3 cannot be pulled using the delivery service, as the read policy is corrupt")
|
|
Eventually(orderer4Runner.Err(), network.EventuallyTimeout).Should(gbytes.Say("Received status:FORBIDDEN from 127\\.0\\.0\\.1:[0-9]+: forbidden pulling the channel channel=testchannel3"))
|
|
})
|
|
})
|
|
|
|
When("an orderer channel is created with a subset of nodes", func() {
|
|
It("is still possible to onboard a new orderer to the channel", func() {
|
|
network = nwo.New(nwo.MultiNodeEtcdRaft(), testDir, client, StartPort(), components)
|
|
network.Profiles = append(network.Profiles, &nwo.Profile{
|
|
Name: "myprofile",
|
|
Consortium: "MySampleConsortium",
|
|
Orderers: []string{"orderer1"},
|
|
Organizations: []string{"Org1"},
|
|
})
|
|
network.Channels = append(network.Channels, &nwo.Channel{
|
|
Name: "mychannel",
|
|
Profile: "myprofile",
|
|
})
|
|
|
|
network.GenerateConfigTree()
|
|
network.Bootstrap()
|
|
|
|
o1, o2, o3 := network.Orderer("orderer1"), network.Orderer("orderer2"), network.Orderer("orderer3")
|
|
orderers := []*nwo.Orderer{o1, o2, o3}
|
|
peer = network.Peer("Org1", "peer0")
|
|
|
|
By("Launching the orderers")
|
|
for _, o := range orderers {
|
|
runner := network.OrdererRunner(o)
|
|
ordererRunners = append(ordererRunners, runner)
|
|
process := ifrit.Invoke(runner)
|
|
ordererProcesses = append(ordererProcesses, process)
|
|
}
|
|
|
|
for _, ordererProc := range ordererProcesses {
|
|
Eventually(ordererProc.Ready(), network.EventuallyTimeout).Should(BeClosed())
|
|
}
|
|
|
|
By("Creating testchannel, joining all orderers")
|
|
channelparticipation.JoinOrderersAppChannelCluster(network, "testchannel", network.Orderers...)
|
|
|
|
By("Finding leader testchannel")
|
|
_ = FindLeader(ordererRunners)
|
|
|
|
By("Creating mychannel with a subset of orderers")
|
|
channelparticipation.JoinOrderersAppChannelCluster(network, "mychannel", o1)
|
|
|
|
By("Finding leader mychannel")
|
|
_ = FindLeader(ordererRunners[:1])
|
|
|
|
By("Waiting for the channel to be available")
|
|
assertBlockReception(map[string]int{
|
|
"mychannel": 0,
|
|
}, []*nwo.Orderer{o1}, peer, network)
|
|
|
|
By("Ensuring only orderer1 services the channel")
|
|
env := CreateBroadcastEnvelope(network, peer, "mychannel", []byte("hello"))
|
|
resp, err := ordererclient.Broadcast(network, o2, env)
|
|
Expect(err).NotTo(HaveOccurred())
|
|
Expect(resp.Status).To(Equal(common.Status_BAD_REQUEST))
|
|
Expect(ordererRunners[1].Err()).To(gbytes.Say("channel does not exist"))
|
|
resp, err = ordererclient.Broadcast(network, o3, env)
|
|
Expect(err).NotTo(HaveOccurred())
|
|
Expect(resp.Status).To(Equal(common.Status_BAD_REQUEST))
|
|
Expect(ordererRunners[2].Err()).To(gbytes.Say("channel does not exist"))
|
|
// With channel participation API, an orderer returns NOT_FOUND for channels it does not serve.
|
|
ensureNotFound(o2, peer, network, "mychannel")
|
|
ensureNotFound(o3, peer, network, "mychannel")
|
|
|
|
By("Adding orderer2 to mychannel")
|
|
ordererCertificatePath := filepath.Join(network.OrdererLocalTLSDir(o2), "server.crt")
|
|
ordererCertificate, err := ioutil.ReadFile(ordererCertificatePath)
|
|
Expect(err).NotTo(HaveOccurred())
|
|
|
|
addConsenter(network, peer, o1, "mychannel", etcdraft.Consenter{
|
|
ServerTlsCert: ordererCertificate,
|
|
ClientTlsCert: ordererCertificate,
|
|
Host: "127.0.0.1",
|
|
Port: uint32(network.OrdererPort(o2, nwo.ClusterPort)),
|
|
})
|
|
|
|
configBlock := nwo.GetConfigBlock(network, peer, o1, "mychannel")
|
|
By("Joining orderer2 to mychannel, as consenter")
|
|
expectedChannelInfo := channelparticipation.ChannelInfo{
|
|
Name: "mychannel",
|
|
URL: "/participation/v1/channels/mychannel",
|
|
Status: "onboarding",
|
|
ConsensusRelation: "consenter",
|
|
Height: 0,
|
|
}
|
|
channelparticipation.Join(network, o2, "mychannel", configBlock, expectedChannelInfo)
|
|
|
|
By("Waiting for orderer2 to join the channel")
|
|
assertBlockReception(map[string]int{
|
|
"mychannel": 1,
|
|
}, []*nwo.Orderer{o1, o2}, peer, network)
|
|
|
|
By("Adding orderer3 to the channel")
|
|
ordererCertificatePath = filepath.Join(network.OrdererLocalTLSDir(o3), "server.crt")
|
|
ordererCertificate, err = ioutil.ReadFile(ordererCertificatePath)
|
|
Expect(err).NotTo(HaveOccurred())
|
|
addConsenter(network, peer, o1, "mychannel", etcdraft.Consenter{
|
|
ServerTlsCert: ordererCertificate,
|
|
ClientTlsCert: ordererCertificate,
|
|
Host: "127.0.0.1",
|
|
Port: uint32(network.OrdererPort(o3, nwo.ClusterPort)),
|
|
})
|
|
|
|
configBlock = nwo.GetConfigBlock(network, peer, o1, "mychannel")
|
|
By("Joining orderer3 to mychannel, as consenter")
|
|
expectedChannelInfo = channelparticipation.ChannelInfo{
|
|
Name: "mychannel",
|
|
URL: "/participation/v1/channels/mychannel",
|
|
Status: "onboarding",
|
|
ConsensusRelation: "consenter",
|
|
Height: 0,
|
|
}
|
|
channelparticipation.Join(network, o3, "mychannel", configBlock, expectedChannelInfo)
|
|
|
|
By("Waiting for orderer3 to join the channel")
|
|
assertBlockReception(map[string]int{
|
|
"mychannel": 2,
|
|
}, orderers, peer, network)
|
|
})
|
|
})
|
|
|
|
When("orderer cluster is not healthy", func() {
|
|
var o1, o2 *nwo.Orderer
|
|
|
|
BeforeEach(func() {
|
|
network = nwo.New(nwo.MultiNodeEtcdRaft(), testDir, client, StartPort(), components)
|
|
network.GenerateConfigTree()
|
|
network.Bootstrap()
|
|
|
|
o1, o2 = network.Orderer("orderer1"), network.Orderer("orderer2")
|
|
orderers := []*nwo.Orderer{o1, o2}
|
|
By("Launching the orderers")
|
|
for _, o := range orderers {
|
|
runner := network.OrdererRunner(o)
|
|
ordererRunners = append(ordererRunners, runner)
|
|
process := ifrit.Invoke(runner)
|
|
ordererProcesses = append(ordererProcesses, process)
|
|
}
|
|
|
|
for _, ordererProc := range ordererProcesses {
|
|
Eventually(ordererProc.Ready(), network.EventuallyTimeout).Should(BeClosed())
|
|
}
|
|
|
|
channelparticipation.JoinOrderersAppChannelCluster(network, "testchannel", o1, o2)
|
|
})
|
|
|
|
AfterEach(func() {
|
|
for _, ordererInstance := range ordererProcesses {
|
|
ordererInstance.Signal(syscall.SIGTERM)
|
|
Eventually(ordererInstance.Wait(), network.EventuallyTimeout).Should(Receive())
|
|
}
|
|
})
|
|
|
|
It("refuses to reconfig if it results in quorum loss", func() {
|
|
By("Waiting for them to elect a leader")
|
|
FindLeader(ordererRunners)
|
|
|
|
extendNetwork(network)
|
|
certificatesOfOrderers := refreshOrdererPEMs(network)
|
|
|
|
By("Removing alive node from 2/3 cluster")
|
|
peer := network.Peer("Org1", "peer0")
|
|
current, updated := consenterRemover(network, peer, o2, "testchannel", certificatesOfOrderers[1].oldCert)
|
|
Eventually(func() []byte {
|
|
sess := nwo.UpdateOrdererConfigSession(network, o2, "testchannel", current, updated, peer, o2)
|
|
Eventually(sess, network.EventuallyTimeout).Should(gexec.Exit(1))
|
|
return sess.Err.Contents()
|
|
}, network.EventuallyTimeout).Should(ContainSubstring("2 out of 3 nodes are alive, configuration will result in quorum loss"))
|
|
|
|
By("Adding node to 2/3 cluster")
|
|
current, updated = consenterAdder(
|
|
network,
|
|
peer,
|
|
o2,
|
|
"testchannel",
|
|
etcdraft.Consenter{
|
|
ServerTlsCert: certificatesOfOrderers[0].newCert,
|
|
ClientTlsCert: certificatesOfOrderers[0].newCert,
|
|
Host: "127.0.0.1",
|
|
Port: uint32(network.OrdererPort(o1, nwo.ListenPort)),
|
|
},
|
|
)
|
|
sess := nwo.UpdateOrdererConfigSession(network, o2, "testchannel", current, updated, peer, o2)
|
|
Eventually(sess, network.EventuallyTimeout).Should(gexec.Exit(1))
|
|
Expect(string(sess.Err.Contents())).To(ContainSubstring("2 out of 3 nodes are alive, configuration will result in quorum loss"))
|
|
})
|
|
})
|
|
|
|
When("an orderer node is evicted", func() {
|
|
BeforeEach(func() {
|
|
ordererRunners = nil
|
|
ordererProcesses = nil
|
|
|
|
network = nwo.New(nwo.MultiNodeEtcdRaft(), testDir, client, StartPort(), components)
|
|
network.GenerateConfigTree()
|
|
network.Bootstrap()
|
|
|
|
o1, o2, o3 := network.Orderer("orderer1"), network.Orderer("orderer2"), network.Orderer("orderer3")
|
|
orderers := []*nwo.Orderer{o1, o2, o3}
|
|
peer = network.Peer("Org1", "peer0")
|
|
|
|
By("Launching the orderers")
|
|
for _, o := range orderers {
|
|
runner := network.OrdererRunner(o, "FABRIC_LOGGING_SPEC=orderer.consensus.etcdraft=debug:info")
|
|
ordererRunners = append(ordererRunners, runner)
|
|
process := ifrit.Invoke(runner)
|
|
ordererProcesses = append(ordererProcesses, process)
|
|
}
|
|
|
|
for _, ordererProc := range ordererProcesses {
|
|
Eventually(ordererProc.Ready(), network.EventuallyTimeout).Should(BeClosed())
|
|
}
|
|
|
|
channelparticipation.JoinOrderersAppChannelCluster(network, "testchannel", o1, o2, o3)
|
|
})
|
|
|
|
AfterEach(func() {
|
|
for _, ordererInstance := range ordererProcesses {
|
|
ordererInstance.Signal(syscall.SIGTERM)
|
|
Eventually(ordererInstance.Wait(), network.EventuallyTimeout).Should(Receive())
|
|
}
|
|
})
|
|
|
|
It("doesn't complain and does it obediently", func() {
|
|
o1, o2, o3 := network.Orderer("orderer1"), network.Orderer("orderer2"), network.Orderer("orderer3")
|
|
orderers := []*nwo.Orderer{o1, o2, o3}
|
|
|
|
By("Waiting for them to elect a leader")
|
|
firstEvictedNode := FindLeader(ordererRunners) - 1
|
|
|
|
By("Removing the leader from 3-node channel")
|
|
server1CertBytes, err := ioutil.ReadFile(filepath.Join(network.OrdererLocalTLSDir(orderers[firstEvictedNode]), "server.crt"))
|
|
Expect(err).To(Not(HaveOccurred()))
|
|
|
|
ordererEvicted1st := network.Orderers[(firstEvictedNode+1)%3]
|
|
removeConsenter(network, peer, ordererEvicted1st, "testchannel", server1CertBytes)
|
|
|
|
var survivedOrdererRunners []*ginkgomon.Runner
|
|
for i := range orderers {
|
|
if i == firstEvictedNode {
|
|
continue
|
|
}
|
|
|
|
survivedOrdererRunners = append(survivedOrdererRunners, ordererRunners[i])
|
|
}
|
|
|
|
secondEvictedNode := FindLeader(survivedOrdererRunners) - 1
|
|
|
|
var survivor int
|
|
for i := range orderers {
|
|
if i != firstEvictedNode && i != secondEvictedNode {
|
|
survivor = i
|
|
break
|
|
}
|
|
}
|
|
|
|
By("Ensuring the evicted orderer stops rafting on channel testchannel and starts a follower")
|
|
const stopMsg = "Raft node stopped channel=testchannel"
|
|
const followerMsg = "Created and started a follower.Chain for channel testchannel"
|
|
Eventually(ordererRunners[firstEvictedNode].Err(), network.EventuallyTimeout, time.Second).Should(gbytes.Say(stopMsg))
|
|
Eventually(ordererRunners[firstEvictedNode].Err(), network.EventuallyTimeout, time.Second).Should(gbytes.Say(followerMsg))
|
|
|
|
By("Ensuring the evicted orderer now doesn't serve clients")
|
|
ensureNotFound(orderers[firstEvictedNode], peer, network, "testchannel")
|
|
|
|
assertFollower := func(expected channelparticipation.ChannelInfo, o *nwo.Orderer) bool {
|
|
current := channelparticipation.ListOne(network, o, "testchannel")
|
|
ok := current == expected
|
|
if !ok {
|
|
fmt.Fprintf(GinkgoWriter, ">>> Current ChannelInfo: %+v \n", current)
|
|
}
|
|
return ok
|
|
}
|
|
|
|
expectedInfo := channelparticipation.ChannelInfo{
|
|
Name: "testchannel",
|
|
URL: "/participation/v1/channels/testchannel",
|
|
Status: "active",
|
|
ConsensusRelation: "follower",
|
|
Height: 2,
|
|
}
|
|
Eventually(assertFollower(expectedInfo, orderers[firstEvictedNode]), network.EventuallyTimeout, 100*time.Millisecond).Should(BeTrue())
|
|
|
|
By("Removing the leader from 2-node channel")
|
|
server2CertBytes, err := ioutil.ReadFile(filepath.Join(network.OrdererLocalTLSDir(orderers[secondEvictedNode]), "server.crt"))
|
|
Expect(err).To(Not(HaveOccurred()))
|
|
|
|
removeConsenter(network, peer, orderers[survivor], "testchannel", server2CertBytes)
|
|
FindLeader([]*ginkgomon.Runner{ordererRunners[survivor]})
|
|
|
|
fmt.Fprintln(GinkgoWriter, "Ensuring the other orderer detect the eviction of the node on channel testchannel")
|
|
Eventually(ordererRunners[secondEvictedNode].Err(), network.EventuallyTimeout, time.Second).Should(gbytes.Say(stopMsg))
|
|
|
|
By("Ensuring the evicted orderer now doesn't serve clients")
|
|
ensureNotFound(orderers[secondEvictedNode], peer, network, "testchannel")
|
|
|
|
expectedInfo = channelparticipation.ChannelInfo{
|
|
Name: "testchannel",
|
|
URL: "/participation/v1/channels/testchannel",
|
|
Status: "active",
|
|
ConsensusRelation: "follower",
|
|
Height: 3,
|
|
}
|
|
Eventually(assertFollower(expectedInfo, orderers[secondEvictedNode]), network.EventuallyTimeout, 100*time.Millisecond).Should(BeTrue())
|
|
|
|
By("Re-adding first evicted orderer")
|
|
addConsenter(network, peer, network.Orderers[survivor], "testchannel", etcdraft.Consenter{
|
|
Host: "127.0.0.1",
|
|
Port: uint32(network.OrdererPort(orderers[firstEvictedNode], nwo.ClusterPort)),
|
|
ClientTlsCert: server1CertBytes,
|
|
ServerTlsCert: server1CertBytes,
|
|
})
|
|
|
|
By("Ensuring re-added orderer starts serving testchannel")
|
|
assertBlockReception(map[string]int{
|
|
"testchannel": 3,
|
|
}, []*nwo.Orderer{orderers[firstEvictedNode]}, peer, network)
|
|
|
|
By("Submitting tx")
|
|
env := CreateBroadcastEnvelope(network, orderers[survivor], "testchannel", []byte("foo"))
|
|
resp, err := ordererclient.Broadcast(network, orderers[survivor], env)
|
|
Expect(err).NotTo(HaveOccurred())
|
|
Expect(resp.Status).To(Equal(common.Status_SUCCESS))
|
|
|
|
By("Ensuring re-added orderer starts serving testchannel")
|
|
assertBlockReception(map[string]int{
|
|
"testchannel": 4,
|
|
}, []*nwo.Orderer{orderers[firstEvictedNode], orderers[survivor]}, peer, network)
|
|
})
|
|
|
|
When("an evicted node is added back while it's offline", func() {
|
|
It("can start with correct new raft id", func() {
|
|
o1 := network.Orderer("orderer1")
|
|
o2 := network.Orderer("orderer2")
|
|
o3 := network.Orderer("orderer3")
|
|
|
|
By("Waiting for them to elect a leader")
|
|
FindLeader(ordererRunners)
|
|
|
|
assertBlockReception(map[string]int{
|
|
"testchannel": 0,
|
|
}, []*nwo.Orderer{o1, o2, o3}, peer, network)
|
|
|
|
By("Killing the first orderer")
|
|
ordererProcesses[0].Signal(syscall.SIGTERM)
|
|
Eventually(ordererProcesses[0].Wait(), network.EventuallyTimeout).Should(Receive())
|
|
|
|
// We need to wait for stabilization, as we might have killed the leader OSN.
|
|
By("Waiting for the channel to stabilize after killing the orderer")
|
|
assertBlockReception(map[string]int{
|
|
"testchannel": 0,
|
|
}, []*nwo.Orderer{o2, o3}, peer, network)
|
|
|
|
By("observing active nodes to shrink")
|
|
o2Runner := ordererRunners[1]
|
|
Eventually(o2Runner.Err(), network.EventuallyTimeout).Should(gbytes.Say("Current active nodes in cluster are: \\[2 3\\]"))
|
|
|
|
By("Removing the first orderer from the application channel")
|
|
server1CertBytes, err := ioutil.ReadFile(filepath.Join(network.OrdererLocalTLSDir(o1), "server.crt"))
|
|
Expect(err).To(Not(HaveOccurred()))
|
|
removeConsenter(network, peer, o2, "testchannel", server1CertBytes)
|
|
|
|
By("Adding the evicted orderer back to the application channel")
|
|
addConsenter(network, peer, o2, "testchannel", etcdraft.Consenter{
|
|
ServerTlsCert: server1CertBytes,
|
|
ClientTlsCert: server1CertBytes,
|
|
Host: "127.0.0.1",
|
|
Port: uint32(network.OrdererPort(o1, nwo.ClusterPort)),
|
|
})
|
|
|
|
By("Removing the first orderer from the application channel again")
|
|
removeConsenter(network, peer, o2, "testchannel", server1CertBytes)
|
|
|
|
By("Adding the evicted orderer back to the application channel again")
|
|
addConsenter(network, peer, o2, "testchannel", etcdraft.Consenter{
|
|
ServerTlsCert: server1CertBytes,
|
|
ClientTlsCert: server1CertBytes,
|
|
Host: "127.0.0.1",
|
|
Port: uint32(network.OrdererPort(o1, nwo.ClusterPort)),
|
|
})
|
|
|
|
By("Starting the first orderer again")
|
|
o1Runner := network.OrdererRunner(o1)
|
|
ordererProcesses[0] = ifrit.Invoke(o1Runner)
|
|
Eventually(ordererProcesses[0].Ready(), network.EventuallyTimeout).Should(BeClosed())
|
|
|
|
// TODO the consenter will detect it has a stale RaftID (has 1, everyone else already know it as 5)
|
|
// and "cowardly halt", which will trigger the follower chain. The follower chain will detect using the
|
|
// last block that it is still a consenter, and will transfer control to the consenter, which creates a
|
|
// never ending loop. We need to fix this behavior.
|
|
// Currnetly, with channel participation, the only way to stop this is by explicitly removing the
|
|
// orderer from the channel, which causes the ledger to be deleted.
|
|
// See: https://github.com/hyperledger/fabric/issues/3992
|
|
for i := 0; i < 5; i++ {
|
|
Eventually(o1Runner.Err(), network.EventuallyTimeout).Should(gbytes.Say("Received msg to 5, my ID is probably wrong due to out of date, cowardly halting channel=testchannel node=1"))
|
|
Eventually(o1Runner.Err(), network.EventuallyTimeout).Should(gbytes.Say("Created and started a follower.Chain for channel testchannel"))
|
|
}
|
|
|
|
By("Removing channel from the first orderer")
|
|
// TODO the channelparticipation.Remove does not clean up the etcdraft folder. This may prevent the
|
|
// correct re-creation of the channel on this orderer.
|
|
// See: https://github.com/hyperledger/fabric/issues/3992
|
|
channelparticipation.Remove(network, o1, "testchannel")
|
|
Eventually(func() int { // Removal is async
|
|
channelList := channelparticipation.List(network, o1)
|
|
return len(channelList.Channels)
|
|
}()).Should(BeZero())
|
|
|
|
// TODO It is recommended to remove the etcdraft folder for the WAL to be re-created correctly
|
|
// See: https://github.com/hyperledger/fabric/issues/3992
|
|
err = os.RemoveAll(path.Join(network.OrdererDir(o1), "etcdraft", "wal", "testchannel"))
|
|
Expect(err).NotTo(HaveOccurred())
|
|
err = os.RemoveAll(path.Join(network.OrdererDir(o1), "etcdraft", "snapshot", "testchannel"))
|
|
Expect(err).NotTo(HaveOccurred())
|
|
|
|
By("Re-adding the channel to the first orderer")
|
|
configBlock := nwo.GetConfigBlock(network, peer, o2, "testchannel")
|
|
expectedInfo := channelparticipation.ChannelInfo{
|
|
Name: "testchannel",
|
|
URL: "/participation/v1/channels/testchannel",
|
|
Status: "onboarding",
|
|
ConsensusRelation: "consenter",
|
|
Height: 0,
|
|
}
|
|
channelparticipation.Join(network, o1, "testchannel", configBlock, expectedInfo)
|
|
|
|
By("Submitting tx")
|
|
env := CreateBroadcastEnvelope(network, o2, "testchannel", []byte("foo"))
|
|
resp, err := ordererclient.Broadcast(network, o2, env)
|
|
Expect(err).NotTo(HaveOccurred())
|
|
Expect(resp.Status).To(Equal(common.Status_SUCCESS))
|
|
|
|
By("Waiting for the channel to stabilize")
|
|
expectedInfo = channelparticipation.ChannelInfo{
|
|
Name: "testchannel",
|
|
URL: "/participation/v1/channels/testchannel",
|
|
Status: "active",
|
|
ConsensusRelation: "consenter",
|
|
Height: 6,
|
|
}
|
|
assertCatchup := func(expected channelparticipation.ChannelInfo) bool {
|
|
current := channelparticipation.ListOne(network, o1, "testchannel")
|
|
ok := current == expected
|
|
if !ok {
|
|
fmt.Fprintf(GinkgoWriter, "Current ChannelInfo: %+v", current)
|
|
}
|
|
return ok
|
|
}
|
|
|
|
Eventually(assertCatchup(expectedInfo), network.EventuallyTimeout, 100*time.Millisecond).Should(BeTrue())
|
|
|
|
assertBlockReception(map[string]int{
|
|
"testchannel": 5,
|
|
}, []*nwo.Orderer{o1, o2, o3}, peer, network)
|
|
})
|
|
})
|
|
|
|
It("notices it even if it is down at the time of its eviction", func() {
|
|
o1 := network.Orderer("orderer1")
|
|
o2 := network.Orderer("orderer2")
|
|
o3 := network.Orderer("orderer3")
|
|
|
|
orderers := []*nwo.Orderer{o1, o2, o3}
|
|
|
|
By("Waiting for them to elect a leader")
|
|
FindLeader(ordererRunners)
|
|
|
|
assertBlockReception(map[string]int{
|
|
"testchannel": 0,
|
|
}, []*nwo.Orderer{o1, o2, o3}, peer, network)
|
|
|
|
By("Killing the orderer")
|
|
ordererProcesses[0].Signal(syscall.SIGTERM)
|
|
Eventually(ordererProcesses[0].Wait(), network.EventuallyTimeout).Should(Receive())
|
|
|
|
// We need to wait for stabilization, as we might have killed the leader OSN.
|
|
By("Waiting for the channel to stabilize after killing the orderer")
|
|
assertBlockReception(map[string]int{
|
|
"testchannel": 0,
|
|
}, []*nwo.Orderer{o2, o3}, peer, network)
|
|
|
|
By("Removing the first orderer from an application channel")
|
|
o1cert, err := ioutil.ReadFile(path.Join(network.OrdererLocalTLSDir(o1), "server.crt"))
|
|
Expect(err).ToNot(HaveOccurred())
|
|
removeConsenter(network, peer, o2, "testchannel", o1cert)
|
|
|
|
By("Starting the orderer again")
|
|
ordererRunner := network.OrdererRunner(orderers[0])
|
|
ordererRunners[0] = ordererRunner
|
|
ordererProcesses[0] = ifrit.Invoke(ordererRunner)
|
|
Eventually(ordererProcesses[0].Ready(), network.EventuallyTimeout).Should(BeClosed())
|
|
|
|
By("Ensuring the remaining OSNs reject authentication")
|
|
Eventually(ordererRunners[1].Err(), time.Minute, time.Second).Should(gbytes.Say("certificate extracted from TLS connection isn't authorized"))
|
|
Eventually(ordererRunners[2].Err(), time.Minute, time.Second).Should(gbytes.Say("certificate extracted from TLS connection isn't authorized"))
|
|
|
|
By("Ensuring it detects its eviction")
|
|
evictionDetection := gbytes.Say(`Detected our own eviction from the channel in block \[1\] channel=testchannel`)
|
|
Eventually(ordererRunner.Err(), time.Minute, time.Second).Should(evictionDetection)
|
|
|
|
By("Ensuring all blocks are pulled up to the block that evicts the OSN")
|
|
Eventually(ordererRunner.Err(), time.Minute, time.Second).Should(gbytes.Say("Periodic check is stopping. channel=testchannel"))
|
|
Eventually(ordererRunner.Err(), time.Minute, time.Second).Should(gbytes.Say("Pulled all blocks up to eviction block. channel=testchannel"))
|
|
|
|
By("Killing the evicted orderer")
|
|
ordererProcesses[0].Signal(syscall.SIGTERM)
|
|
Eventually(ordererProcesses[0].Wait(), network.EventuallyTimeout).Should(Receive())
|
|
|
|
By("Starting the evicted orderer again")
|
|
ordererRunner = network.OrdererRunner(orderers[0])
|
|
ordererRunners[0] = ordererRunner
|
|
ordererProcesses[0] = ifrit.Invoke(ordererRunner)
|
|
Eventually(ordererProcesses[0].Ready(), network.EventuallyTimeout).Should(BeClosed())
|
|
|
|
By("Ensuring the evicted orderer starts up with the channel as a follower")
|
|
expectedChannelInfo := channelparticipation.ChannelInfo{
|
|
Name: "testchannel",
|
|
URL: "/participation/v1/channels/testchannel",
|
|
Status: "active",
|
|
ConsensusRelation: "follower",
|
|
Height: 2,
|
|
}
|
|
Eventually(func() channelparticipation.ChannelInfo {
|
|
return channelparticipation.ListOne(network, o1, "testchannel")
|
|
}, network.EventuallyTimeout).Should(Equal(expectedChannelInfo))
|
|
|
|
By("Adding the evicted orderer back to the application channel")
|
|
addConsenter(network, peer, o2, "testchannel", etcdraft.Consenter{
|
|
ServerTlsCert: o1cert,
|
|
ClientTlsCert: o1cert,
|
|
Host: "127.0.0.1",
|
|
Port: uint32(network.OrdererPort(orderers[0], nwo.ClusterPort)),
|
|
})
|
|
|
|
By("Ensuring the re-added orderer joins the Raft cluster")
|
|
FindLeader([]*ginkgomon.Runner{ordererRunner})
|
|
})
|
|
})
|
|
|
|
When("an orderer node is joined", func() {
|
|
It("isn't influenced by outdated orderers", func() {
|
|
// This test checks that if a lagged is not aware of newly added nodes,
|
|
// among which leader is present, it eventually pulls config block from
|
|
// the orderer it knows, gets the certificates from it and participate
|
|
// in consensus again.
|
|
//
|
|
// Steps:
|
|
// Initial nodes in cluster: <1, 2, 3, 4>
|
|
// - start <1, 2, 3>
|
|
// - add <5, 6, 7>, start <5, 6, 7>
|
|
// - kill <1>
|
|
// - submit a tx, so that Raft index on <1> is behind <5, 6, 7> and <2, 3>
|
|
// - kill <2, 3>
|
|
// - start <1> and <4>. Since <1> is behind <5, 6, 7>, leader is certainly
|
|
// going to be elected from <5, 6, 7>
|
|
// - assert that even <4> is not aware of leader, it can pull config block
|
|
// from <1>, and start participating in consensus.
|
|
|
|
orderers := make([]*nwo.Orderer, 7)
|
|
ordererRunners = make([]*ginkgomon.Runner, 7)
|
|
ordererProcesses = make([]ifrit.Process, 7)
|
|
|
|
for i := range orderers {
|
|
orderers[i] = &nwo.Orderer{
|
|
Name: fmt.Sprintf("orderer%d", i+1),
|
|
Organization: "OrdererOrg",
|
|
}
|
|
}
|
|
|
|
layout := nwo.MultiNodeEtcdRaft()
|
|
layout.Orderers = orderers[:4]
|
|
layout.Profiles[0].Orderers = []string{"orderer1", "orderer2", "orderer3", "orderer4"}
|
|
|
|
network = nwo.New(layout, testDir, client, StartPort(), components)
|
|
network.GenerateConfigTree()
|
|
network.Bootstrap()
|
|
|
|
peer = network.Peer("Org1", "peer0")
|
|
|
|
launch := func(i int) {
|
|
runner := network.OrdererRunner(orderers[i])
|
|
ordererRunners[i] = runner
|
|
process := ifrit.Invoke(runner)
|
|
Eventually(process.Ready(), network.EventuallyTimeout).Should(BeClosed())
|
|
ordererProcesses[i] = process
|
|
}
|
|
|
|
By("Launching 3 out of 4 orderers")
|
|
for i := range orderers[:3] {
|
|
launch(i)
|
|
}
|
|
channelparticipation.JoinOrderersAppChannelCluster(network, "testchannel", orderers[:3]...)
|
|
|
|
leader := FindLeader(ordererRunners[:3])
|
|
|
|
By("Checking that all orderers are online")
|
|
assertBlockReception(map[string]int{
|
|
"testchannel": 0,
|
|
}, orderers[:3], peer, network)
|
|
|
|
By("Configuring orderer[5, 6, 7] in the network")
|
|
extendNetwork(network)
|
|
|
|
for _, o := range orderers[4:7] {
|
|
ports := nwo.Ports{}
|
|
for _, portName := range nwo.OrdererPortNames() {
|
|
ports[portName] = network.ReservePort()
|
|
}
|
|
network.PortsByOrdererID[o.ID()] = ports
|
|
|
|
network.Orderers = append(network.Orderers, o)
|
|
network.GenerateOrdererConfig(o)
|
|
}
|
|
|
|
blockNum := 0 // there's only one block in channel - genesis
|
|
for _, i := range []int{4, 5, 6} {
|
|
By(fmt.Sprintf("Adding orderer%d", i+1))
|
|
ordererCertificatePath := filepath.Join(network.OrdererLocalTLSDir(orderers[i]), "server.crt")
|
|
ordererCertificate, err := ioutil.ReadFile(ordererCertificatePath)
|
|
Expect(err).NotTo(HaveOccurred())
|
|
|
|
addConsenter(network, peer, orderers[0], "testchannel", etcdraft.Consenter{
|
|
ServerTlsCert: ordererCertificate,
|
|
ClientTlsCert: ordererCertificate,
|
|
Host: "127.0.0.1",
|
|
Port: uint32(network.OrdererPort(orderers[i], nwo.ClusterPort)),
|
|
})
|
|
blockNum++
|
|
|
|
// Get the last config block of the system channel
|
|
configBlock := nwo.GetConfigBlock(network, peer, orderers[0], "testchannel")
|
|
|
|
By(fmt.Sprintf("Launching orderer%d", i+1))
|
|
launch(i)
|
|
|
|
expectedInfo := channelparticipation.ChannelInfo{
|
|
Name: "testchannel",
|
|
URL: "/participation/v1/channels/testchannel",
|
|
Status: "onboarding",
|
|
ConsensusRelation: "consenter",
|
|
Height: 0,
|
|
}
|
|
channelparticipation.Join(network, orderers[i], "testchannel", configBlock, expectedInfo)
|
|
|
|
By(fmt.Sprintf("Checking that orderer%d has onboarded the network", i+1))
|
|
assertBlockReception(map[string]int{
|
|
"testchannel": blockNum,
|
|
}, []*nwo.Orderer{orderers[i]}, peer, network)
|
|
}
|
|
|
|
Expect(FindLeader(ordererRunners[4:])).To(Equal(leader))
|
|
|
|
// Later on, when we start [1, 4, 5, 6, 7], we want to make sure that leader
|
|
// is elected from [5, 6, 7], who are unknown to [4]. So we can assert that
|
|
// [4] suspects its own eviction, pulls block from [1], and join the cluster.
|
|
// Otherwise, if [1] is elected, and all other nodes already knew it, [4] may
|
|
// simply replicate missing blocks with Raft, instead of pulling from others
|
|
// triggered by eviction suspector.
|
|
|
|
By("Killing orderer1")
|
|
ordererProcesses[0].Signal(syscall.SIGTERM)
|
|
Eventually(ordererProcesses[0].Wait(), network.EventuallyTimeout).Should(Receive())
|
|
|
|
By("Submitting another tx to increment Raft index on alive orderers")
|
|
if leader == 1 {
|
|
// if orderer1 was leader, we should expect a new leader being elected before going forward
|
|
FindLeader([]*ginkgomon.Runner{ordererRunners[4]})
|
|
}
|
|
|
|
env := CreateBroadcastEnvelope(network, orderers[4], "testchannel", []byte("hello"))
|
|
resp, err := ordererclient.Broadcast(network, orderers[4], env)
|
|
Expect(err).NotTo(HaveOccurred())
|
|
Expect(resp.Status).To(Equal(common.Status_SUCCESS))
|
|
blockNum++
|
|
|
|
assertBlockReception(map[string]int{
|
|
"testchannel": blockNum,
|
|
}, []*nwo.Orderer{orderers[1], orderers[2], orderers[4], orderers[5], orderers[6]}, peer, network) // alive orderers: 2, 3, 5, 6, 7
|
|
|
|
By("Killing orderer[2,3]")
|
|
for _, i := range []int{1, 2} {
|
|
ordererProcesses[i].Signal(syscall.SIGTERM)
|
|
Eventually(ordererProcesses[i].Wait(), network.EventuallyTimeout).Should(Receive())
|
|
}
|
|
|
|
By("Launching the orderer that was never started from the genesis block")
|
|
launch(3)
|
|
channelparticipation.JoinOrderersAppChannelCluster(network, "testchannel", orderers[3])
|
|
|
|
By("Launching orderer1")
|
|
launch(0)
|
|
|
|
By("Waiting until it suspects its eviction from the channel")
|
|
Eventually(ordererRunners[3].Err(), time.Minute, time.Second).Should(gbytes.Say("Suspecting our own eviction from the channel"))
|
|
|
|
By("Making sure 4/7 orderers form quorum and serve request")
|
|
assertBlockReception(map[string]int{
|
|
"testchannel": blockNum,
|
|
}, []*nwo.Orderer{orderers[0], orderers[3], orderers[4], orderers[5], orderers[6]}, peer, network) // alive orderers: 1, 4, 5, 6, 7
|
|
})
|
|
})
|
|
|
|
It("can create a channel that contains a subset of orderers in another channel", func() {
|
|
config := nwo.BasicEtcdRaft()
|
|
config.Orderers = []*nwo.Orderer{
|
|
{Name: "orderer1", Organization: "OrdererOrg"},
|
|
{Name: "orderer2", Organization: "OrdererOrg"},
|
|
{Name: "orderer3", Organization: "OrdererOrg"},
|
|
}
|
|
config.Profiles = []*nwo.Profile{
|
|
{
|
|
Name: "ThreeOrdererChannel",
|
|
Consortium: "SampleConsortium",
|
|
Organizations: []string{"Org1", "Org2"},
|
|
Orderers: []string{"orderer1", "orderer2", "orderer3"},
|
|
}, {
|
|
Name: "SingleOrdererChannel",
|
|
Consortium: "SampleConsortium",
|
|
Organizations: []string{"Org1", "Org2"},
|
|
Orderers: []string{"orderer1"},
|
|
},
|
|
}
|
|
config.Channels = []*nwo.Channel{
|
|
{Name: "single-orderer-channel", Profile: "SingleOrdererChannel", BaseProfile: "SampleDevModeEtcdRaft"},
|
|
{Name: "three-orderer-channel", Profile: "ThreeOrdererChannel"},
|
|
}
|
|
|
|
network = nwo.New(config, testDir, client, StartPort(), components)
|
|
o1, o2, o3 := network.Orderer("orderer1"), network.Orderer("orderer2"), network.Orderer("orderer3")
|
|
orderers := []*nwo.Orderer{o1, o2, o3}
|
|
peer = network.Peer("Org1", "peer0")
|
|
|
|
network.GenerateConfigTree()
|
|
network.Bootstrap()
|
|
|
|
By("Launching the orderers")
|
|
for _, o := range orderers {
|
|
runner := network.OrdererRunner(o)
|
|
ordererRunners = append(ordererRunners, runner)
|
|
process := ifrit.Invoke(runner)
|
|
ordererProcesses = append(ordererProcesses, process)
|
|
}
|
|
|
|
for _, ordererProc := range ordererProcesses {
|
|
Eventually(ordererProc.Ready(), network.EventuallyTimeout).Should(BeClosed())
|
|
}
|
|
|
|
channelparticipation.JoinOrderersAppChannelCluster(network, "three-orderer-channel", o1, o2, o3)
|
|
FindLeader(ordererRunners)
|
|
channelparticipation.JoinOrderersAppChannelCluster(network, "single-orderer-channel", o1)
|
|
FindLeader([]*ginkgomon.Runner{ordererRunners[0]})
|
|
|
|
channelparticipation.ChannelListMatcher(channelparticipation.List(network, o1),
|
|
[]string{"single-orderer-channel", "three-orderer-channel"})
|
|
for _, o := range orderers[1:] {
|
|
channelparticipation.ChannelListMatcher(channelparticipation.List(network, o),
|
|
[]string{"three-orderer-channel"})
|
|
}
|
|
})
|
|
|
|
It("can add a new orderer organization", func() {
|
|
network = nwo.New(nwo.MultiNodeEtcdRaft(), testDir, client, StartPort(), components)
|
|
o1, o2, o3 := network.Orderer("orderer1"), network.Orderer("orderer2"), network.Orderer("orderer3")
|
|
orderers := []*nwo.Orderer{o1, o2, o3}
|
|
|
|
network.GenerateConfigTree()
|
|
network.Bootstrap()
|
|
|
|
By("Launching the orderers")
|
|
for _, o := range orderers {
|
|
runner := network.OrdererRunner(o)
|
|
ordererRunners = append(ordererRunners, runner)
|
|
process := ifrit.Invoke(runner)
|
|
ordererProcesses = append(ordererProcesses, process)
|
|
}
|
|
|
|
for _, ordererProc := range ordererProcesses {
|
|
Eventually(ordererProc.Ready(), network.EventuallyTimeout).Should(BeClosed())
|
|
}
|
|
|
|
By("Creating the testchannel")
|
|
channelparticipation.JoinOrderersAppChannelCluster(network, "testchannel", o1, o2, o3)
|
|
|
|
By("Waiting for the testchannel to be ready")
|
|
FindLeader(ordererRunners)
|
|
|
|
peer := network.Peer("Org1", "peer0")
|
|
channel := "testchannel"
|
|
|
|
config := nwo.GetConfig(network, peer, o1, channel)
|
|
updatedConfig := proto.Clone(config).(*common.Config)
|
|
|
|
ordererOrg := updatedConfig.ChannelGroup.Groups["Orderer"].Groups["OrdererOrg"]
|
|
mspConfig := &msp.MSPConfig{}
|
|
err := proto.Unmarshal(ordererOrg.Values["MSP"].Value, mspConfig)
|
|
Expect(err).NotTo(HaveOccurred())
|
|
|
|
fabMSPConfig := &msp.FabricMSPConfig{}
|
|
err = proto.Unmarshal(mspConfig.Config, fabMSPConfig)
|
|
Expect(err).NotTo(HaveOccurred())
|
|
|
|
fabMSPConfig.Name = "OrdererMSP2"
|
|
|
|
mspConfig.Config, _ = proto.Marshal(fabMSPConfig)
|
|
updatedConfig.ChannelGroup.Groups["Orderer"].Groups["OrdererMSP2"] = &common.ConfigGroup{
|
|
Values: map[string]*common.ConfigValue{
|
|
"MSP": {
|
|
Value: protoutil.MarshalOrPanic(mspConfig),
|
|
ModPolicy: "Admins",
|
|
},
|
|
},
|
|
ModPolicy: "Admins",
|
|
}
|
|
|
|
nwo.UpdateOrdererConfig(network, o1, channel, config, updatedConfig, peer, o1)
|
|
})
|
|
})
|
|
|
|
// ensureNotFound verfies that the orderer responds with NOT_FOUND to a block fetch request.
|
|
// With channel participation API, an orderer returns NOT_FOUND for channels it does not serve.
|
|
// The old implementation with the system channel and inactive chain used to return SERVICE_UNAVAILABLE.
|
|
func ensureNotFound(evictedOrderer *nwo.Orderer, submitter *nwo.Peer, network *nwo.Network, channel string) {
|
|
c := commands.ChannelFetch{
|
|
ChannelID: channel,
|
|
Block: "newest",
|
|
OutputFile: "/dev/null",
|
|
Orderer: network.OrdererAddress(evictedOrderer, nwo.ListenPort),
|
|
}
|
|
|
|
sess, err := network.OrdererAdminSession(evictedOrderer, submitter, c)
|
|
Expect(err).NotTo(HaveOccurred())
|
|
|
|
Eventually(sess, network.EventuallyTimeout).Should(gexec.Exit())
|
|
Expect(sess.Err).To(gbytes.Say("NOT_FOUND"))
|
|
}
|
|
|
|
var extendedCryptoConfig = `---
|
|
OrdererOrgs:
|
|
- Name: OrdererOrg
|
|
Domain: example.com
|
|
EnableNodeOUs: false
|
|
CA:
|
|
Hostname: ca
|
|
Specs:
|
|
- Hostname: orderer1
|
|
SANS:
|
|
- localhost
|
|
- 127.0.0.1
|
|
- ::1
|
|
- Hostname: orderer1new
|
|
SANS:
|
|
- localhost
|
|
- 127.0.0.1
|
|
- ::1
|
|
- Hostname: orderer2
|
|
SANS:
|
|
- localhost
|
|
- 127.0.0.1
|
|
- ::1
|
|
- Hostname: orderer2new
|
|
SANS:
|
|
- localhost
|
|
- 127.0.0.1
|
|
- ::1
|
|
- Hostname: orderer3
|
|
SANS:
|
|
- localhost
|
|
- 127.0.0.1
|
|
- ::1
|
|
- Hostname: orderer3new
|
|
SANS:
|
|
- localhost
|
|
- 127.0.0.1
|
|
- ::1
|
|
- Hostname: orderer4
|
|
SANS:
|
|
- localhost
|
|
- 127.0.0.1
|
|
- ::1
|
|
- Hostname: orderer5
|
|
SANS:
|
|
- localhost
|
|
- 127.0.0.1
|
|
- ::1
|
|
- Hostname: orderer6
|
|
SANS:
|
|
- localhost
|
|
- 127.0.0.1
|
|
- ::1
|
|
- Hostname: orderer7
|
|
SANS:
|
|
- localhost
|
|
- 127.0.0.1
|
|
- ::1
|
|
`
|
|
|
|
type certificateChange struct {
|
|
srcFile string
|
|
dstFile string
|
|
oldCert []byte
|
|
oldKey []byte
|
|
newCert []byte
|
|
}
|
|
|
|
// extendNetwork rotates adds an additional orderer
|
|
func extendNetwork(n *nwo.Network) {
|
|
// Overwrite the current crypto-config with additional orderers
|
|
cryptoConfigYAML, err := ioutil.TempFile("", "crypto-config.yaml")
|
|
Expect(err).NotTo(HaveOccurred())
|
|
defer os.Remove(cryptoConfigYAML.Name())
|
|
|
|
err = ioutil.WriteFile(cryptoConfigYAML.Name(), []byte(extendedCryptoConfig), 0o644)
|
|
Expect(err).NotTo(HaveOccurred())
|
|
|
|
// Invoke cryptogen extend to add new orderers
|
|
sess, err := n.Cryptogen(commands.Extend{
|
|
Config: cryptoConfigYAML.Name(),
|
|
Input: n.CryptoPath(),
|
|
})
|
|
Expect(err).NotTo(HaveOccurred())
|
|
Eventually(sess, n.EventuallyTimeout).Should(gexec.Exit(0))
|
|
}
|
|
|
|
// refreshOrdererPEMs rotates all TLS certificates of all nodes,
|
|
// and returns the deltas
|
|
func refreshOrdererPEMs(n *nwo.Network) []*certificateChange {
|
|
var fileChanges []*certificateChange
|
|
// Populate source to destination files
|
|
err := filepath.Walk(n.CryptoPath(), func(path string, info os.FileInfo, err error) error {
|
|
if !strings.Contains(path, "/tls/") {
|
|
return nil
|
|
}
|
|
if strings.Contains(path, "new") {
|
|
fileChanges = append(fileChanges, &certificateChange{
|
|
srcFile: path,
|
|
dstFile: strings.Replace(path, "new", "", -1),
|
|
})
|
|
}
|
|
return nil
|
|
})
|
|
Expect(err).NotTo(HaveOccurred())
|
|
|
|
var serverCertChanges []*certificateChange
|
|
|
|
// Overwrite the destination files with the contents of the source files.
|
|
for _, certChange := range fileChanges {
|
|
previousCertBytes, err := ioutil.ReadFile(certChange.dstFile)
|
|
Expect(err).NotTo(HaveOccurred())
|
|
|
|
newCertBytes, err := ioutil.ReadFile(certChange.srcFile)
|
|
Expect(err).NotTo(HaveOccurred())
|
|
|
|
err = ioutil.WriteFile(certChange.dstFile, newCertBytes, 0o644)
|
|
Expect(err).NotTo(HaveOccurred())
|
|
|
|
if !strings.Contains(certChange.dstFile, "server.crt") {
|
|
continue
|
|
}
|
|
|
|
// Read the previous key file
|
|
previousKeyBytes, err := ioutil.ReadFile(strings.Replace(certChange.dstFile, "server.crt", "server.key", -1))
|
|
Expect(err).NotTo(HaveOccurred())
|
|
|
|
serverCertChanges = append(serverCertChanges, certChange)
|
|
certChange.newCert = newCertBytes
|
|
certChange.oldCert = previousCertBytes
|
|
certChange.oldKey = previousKeyBytes
|
|
}
|
|
return serverCertChanges
|
|
}
|
|
|
|
// assertBlockReception asserts that the given orderers have the expected
|
|
// newest block number for the specified channels
|
|
func assertBlockReception(expectedBlockNumPerChannel map[string]int, orderers []*nwo.Orderer, p *nwo.Peer, n *nwo.Network) {
|
|
for channelName, blockNum := range expectedBlockNumPerChannel {
|
|
for _, orderer := range orderers {
|
|
waitForBlockReception(orderer, p, n, channelName, blockNum)
|
|
}
|
|
}
|
|
}
|
|
|
|
func waitForBlockReception(o *nwo.Orderer, submitter *nwo.Peer, network *nwo.Network, channelName string, blockNum int) {
|
|
c := commands.ChannelFetch{
|
|
ChannelID: channelName,
|
|
Block: "newest",
|
|
OutputFile: "/dev/null",
|
|
Orderer: network.OrdererAddress(o, nwo.ListenPort),
|
|
}
|
|
Eventually(func() string {
|
|
sess, err := network.OrdererAdminSession(o, submitter, c)
|
|
Expect(err).NotTo(HaveOccurred())
|
|
Eventually(sess, network.EventuallyTimeout).Should(gexec.Exit())
|
|
if sess.ExitCode() != 0 {
|
|
return fmt.Sprintf("exit code is %d: %s", sess.ExitCode(), string(sess.Err.Contents()))
|
|
}
|
|
sessErr := string(sess.Err.Contents())
|
|
expected := fmt.Sprintf("Received block: %d", blockNum)
|
|
if strings.Contains(sessErr, expected) {
|
|
return ""
|
|
}
|
|
return sessErr
|
|
}, network.EventuallyTimeout, time.Second).Should(BeEmpty())
|
|
}
|
|
|
|
func revokeReaderAccess(network *nwo.Network, channel string, orderer *nwo.Orderer, peer *nwo.Peer) {
|
|
config := nwo.GetConfig(network, peer, orderer, channel)
|
|
updatedConfig := proto.Clone(config).(*common.Config)
|
|
|
|
// set the policy
|
|
adminPolicy := protoutil.MarshalOrPanic(&common.ImplicitMetaPolicy{
|
|
SubPolicy: "Admins",
|
|
Rule: common.ImplicitMetaPolicy_MAJORITY,
|
|
})
|
|
updatedConfig.ChannelGroup.Groups["Orderer"].Policies["Readers"].Policy.Value = adminPolicy
|
|
nwo.UpdateOrdererConfig(network, orderer, channel, config, updatedConfig, peer, orderer)
|
|
}
|
|
|
|
// consenterAdder constructs configs that can be used by `UpdateOrdererConfig`
|
|
// to add a consenter.
|
|
func consenterAdder(n *nwo.Network, peer *nwo.Peer, orderer *nwo.Orderer, channel string, consenter etcdraft.Consenter) (current, updated *common.Config) {
|
|
config := nwo.GetConfig(n, peer, orderer, channel)
|
|
updatedConfig := proto.Clone(config).(*common.Config)
|
|
|
|
consensusTypeConfigValue := updatedConfig.ChannelGroup.Groups["Orderer"].Values["ConsensusType"]
|
|
consensusTypeValue := &protosorderer.ConsensusType{}
|
|
err := proto.Unmarshal(consensusTypeConfigValue.Value, consensusTypeValue)
|
|
Expect(err).NotTo(HaveOccurred())
|
|
|
|
metadata := &etcdraft.ConfigMetadata{}
|
|
err = proto.Unmarshal(consensusTypeValue.Metadata, metadata)
|
|
Expect(err).NotTo(HaveOccurred())
|
|
|
|
metadata.Consenters = append(metadata.Consenters, &consenter)
|
|
|
|
consensusTypeValue.Metadata, err = proto.Marshal(metadata)
|
|
Expect(err).NotTo(HaveOccurred())
|
|
|
|
updatedConfig.ChannelGroup.Groups["Orderer"].Values["ConsensusType"] = &common.ConfigValue{
|
|
ModPolicy: "Admins",
|
|
Value: protoutil.MarshalOrPanic(consensusTypeValue),
|
|
}
|
|
|
|
return config, updatedConfig
|
|
}
|
|
|
|
// consenterRemover constructs configs that can be used by
|
|
// `UpdateOrdererConfig` to remove a consenter.
|
|
func consenterRemover(n *nwo.Network, peer *nwo.Peer, orderer *nwo.Orderer, channel string, certificate []byte) (current, updated *common.Config) {
|
|
config := nwo.GetConfig(n, peer, orderer, channel)
|
|
updatedConfig := proto.Clone(config).(*common.Config)
|
|
|
|
consensusTypeConfigValue := updatedConfig.ChannelGroup.Groups["Orderer"].Values["ConsensusType"]
|
|
consensusTypeValue := &protosorderer.ConsensusType{}
|
|
err := proto.Unmarshal(consensusTypeConfigValue.Value, consensusTypeValue)
|
|
Expect(err).NotTo(HaveOccurred())
|
|
|
|
metadata := &etcdraft.ConfigMetadata{}
|
|
err = proto.Unmarshal(consensusTypeValue.Metadata, metadata)
|
|
Expect(err).NotTo(HaveOccurred())
|
|
|
|
var newConsenters []*etcdraft.Consenter
|
|
for _, consenter := range metadata.Consenters {
|
|
if bytes.Equal(consenter.ClientTlsCert, certificate) || bytes.Equal(consenter.ServerTlsCert, certificate) {
|
|
continue
|
|
}
|
|
newConsenters = append(newConsenters, consenter)
|
|
}
|
|
|
|
metadata.Consenters = newConsenters
|
|
consensusTypeValue.Metadata, err = proto.Marshal(metadata)
|
|
Expect(err).NotTo(HaveOccurred())
|
|
|
|
updatedConfig.ChannelGroup.Groups["Orderer"].Values["ConsensusType"] = &common.ConfigValue{
|
|
ModPolicy: "Admins",
|
|
Value: protoutil.MarshalOrPanic(consensusTypeValue),
|
|
}
|
|
|
|
return config, updatedConfig
|
|
}
|
|
|
|
// addConsenter adds a new consenter to the given channel.
|
|
func addConsenter(n *nwo.Network, peer *nwo.Peer, orderer *nwo.Orderer, channel string, consenter etcdraft.Consenter) {
|
|
updateEtcdRaftMetadata(n, peer, orderer, channel, func(metadata *etcdraft.ConfigMetadata) {
|
|
metadata.Consenters = append(metadata.Consenters, &consenter)
|
|
})
|
|
}
|
|
|
|
// removeConsenter removes a consenter with the given certificate in PEM format
|
|
// from the given channel.
|
|
func removeConsenter(n *nwo.Network, peer *nwo.Peer, orderer *nwo.Orderer, channel string, certificate []byte) {
|
|
updateEtcdRaftMetadata(n, peer, orderer, channel, func(metadata *etcdraft.ConfigMetadata) {
|
|
var newConsenters []*etcdraft.Consenter
|
|
for _, consenter := range metadata.Consenters {
|
|
if bytes.Equal(consenter.ClientTlsCert, certificate) || bytes.Equal(consenter.ServerTlsCert, certificate) {
|
|
continue
|
|
}
|
|
newConsenters = append(newConsenters, consenter)
|
|
}
|
|
|
|
metadata.Consenters = newConsenters
|
|
})
|
|
}
|
|
|
|
// updateEtcdRaftMetadata executes a config update that updates the etcdraft
|
|
// metadata according to the given function f.
|
|
func updateEtcdRaftMetadata(network *nwo.Network, peer *nwo.Peer, orderer *nwo.Orderer, channel string, f func(md *etcdraft.ConfigMetadata)) {
|
|
nwo.UpdateConsensusMetadata(network, peer, orderer, channel, func(originalMetadata []byte) []byte {
|
|
metadata := &etcdraft.ConfigMetadata{}
|
|
err := proto.Unmarshal(originalMetadata, metadata)
|
|
Expect(err).NotTo(HaveOccurred())
|
|
|
|
f(metadata)
|
|
|
|
newMetadata, err := proto.Marshal(metadata)
|
|
Expect(err).NotTo(HaveOccurred())
|
|
return newMetadata
|
|
})
|
|
}
|
|
|
|
func mutateConsensusMetadata(originalMetadata []byte, f func(md *etcdraft.ConfigMetadata)) []byte {
|
|
metadata := &etcdraft.ConfigMetadata{}
|
|
err := proto.Unmarshal(originalMetadata, metadata)
|
|
Expect(err).NotTo(HaveOccurred())
|
|
|
|
f(metadata)
|
|
|
|
newMetadata, err := proto.Marshal(metadata)
|
|
Expect(err).NotTo(HaveOccurred())
|
|
return newMetadata
|
|
}
|
|
|
|
func updateOrdererMSPAndConsensusMetadata(network *nwo.Network, peer *nwo.Peer, orderer *nwo.Orderer, channel, orgID string, mutateMSP nwo.MSPMutator, f func(md *etcdraft.ConfigMetadata)) {
|
|
config := nwo.GetConfig(network, peer, orderer, channel)
|
|
updatedConfig := proto.Clone(config).(*common.Config)
|
|
|
|
// Unpack the MSP config
|
|
rawMSPConfig := updatedConfig.ChannelGroup.Groups["Orderer"].Groups[orgID].Values["MSP"]
|
|
mspConfig := &msp.MSPConfig{}
|
|
err := proto.Unmarshal(rawMSPConfig.Value, mspConfig)
|
|
Expect(err).NotTo(HaveOccurred())
|
|
|
|
fabricConfig := &msp.FabricMSPConfig{}
|
|
err = proto.Unmarshal(mspConfig.Config, fabricConfig)
|
|
Expect(err).NotTo(HaveOccurred())
|
|
|
|
// Mutate it as we are asked
|
|
*fabricConfig = mutateMSP(*fabricConfig)
|
|
|
|
// Wrap it back into the config
|
|
mspConfig.Config = protoutil.MarshalOrPanic(fabricConfig)
|
|
rawMSPConfig.Value = protoutil.MarshalOrPanic(mspConfig)
|
|
|
|
consensusTypeConfigValue := updatedConfig.ChannelGroup.Groups["Orderer"].Values["ConsensusType"]
|
|
consensusTypeValue := &protosorderer.ConsensusType{}
|
|
err = proto.Unmarshal(consensusTypeConfigValue.Value, consensusTypeValue)
|
|
Expect(err).NotTo(HaveOccurred())
|
|
|
|
consensusTypeValue.Metadata = mutateConsensusMetadata(consensusTypeValue.Metadata, f)
|
|
|
|
updatedConfig.ChannelGroup.Groups["Orderer"].Values["ConsensusType"] = &common.ConfigValue{
|
|
ModPolicy: "Admins",
|
|
Value: protoutil.MarshalOrPanic(consensusTypeValue),
|
|
}
|
|
|
|
nwo.UpdateOrdererConfig(network, orderer, channel, config, updatedConfig, peer, orderer)
|
|
}
|
|
|
|
func configToGenesisBlock(configBlock *common.Block, channelId string) *common.Block {
|
|
genesisBlock := proto.Clone(configBlock).(*common.Block)
|
|
|
|
genesisBlock.Header.Number = 0
|
|
genesisBlock.Header.PreviousHash = nil
|
|
|
|
var metadataContents [][]byte
|
|
for i := 0; i < len(common.BlockMetadataIndex_name); i++ {
|
|
metadataContents = append(metadataContents, []byte{})
|
|
}
|
|
genesisBlock.Metadata = &common.BlockMetadata{Metadata: metadataContents}
|
|
genesisBlock.Metadata.Metadata[common.BlockMetadataIndex_SIGNATURES] = protoutil.MarshalOrPanic(&common.Metadata{
|
|
Value: protoutil.MarshalOrPanic(&common.OrdererBlockMetadata{
|
|
LastConfig: &common.LastConfig{Index: 0},
|
|
}),
|
|
})
|
|
|
|
envelope, err := protoutil.GetEnvelopeFromBlock(genesisBlock.Data.Data[0])
|
|
Expect(err).NotTo(HaveOccurred())
|
|
payload, err := protoutil.UnmarshalPayload(envelope.Payload)
|
|
Expect(err).NotTo(HaveOccurred())
|
|
|
|
Expect(err).NotTo(HaveOccurred())
|
|
payloadChannelHeader, err := protoutil.UnmarshalChannelHeader(payload.Header.ChannelHeader)
|
|
Expect(err).NotTo(HaveOccurred())
|
|
payloadChannelHeader.ChannelId = channelId
|
|
payloadSignatureHeader := protoutil.MakeSignatureHeader(nil, protoutil.CreateNonceOrPanic())
|
|
protoutil.SetTxID(payloadChannelHeader, payloadSignatureHeader)
|
|
payload.Header = protoutil.MakePayloadHeader(payloadChannelHeader, payloadSignatureHeader)
|
|
|
|
envelope.Payload, err = protoutil.Marshal(payload)
|
|
Expect(err).NotTo(HaveOccurred())
|
|
genesisBlock.Data.Data[0], err = protoutil.Marshal(envelope)
|
|
Expect(err).NotTo(HaveOccurred())
|
|
|
|
genesisBlock.Header.DataHash = protoutil.BlockDataHash(genesisBlock.Data)
|
|
|
|
return genesisBlock
|
|
}
|