/* 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 }