378 lines
14 KiB
Go
378 lines
14 KiB
Go
/*
|
|
Copyright IBM Corp All Rights Reserved.
|
|
|
|
SPDX-License-Identifier: Apache-2.0
|
|
*/
|
|
|
|
package nwo
|
|
|
|
import (
|
|
"io/ioutil"
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
|
|
"github.com/hyperledger/fabric/common/channelconfig"
|
|
"github.com/hyperledger/fabric/common/policies"
|
|
|
|
"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/integration/nwo/commands"
|
|
"github.com/hyperledger/fabric/internal/configtxlator/update"
|
|
"github.com/hyperledger/fabric/protoutil"
|
|
. "github.com/onsi/gomega"
|
|
"github.com/onsi/gomega/gbytes"
|
|
"github.com/onsi/gomega/gexec"
|
|
)
|
|
|
|
// GetConfigBlock retrieves the current config block for a channel.
|
|
func GetConfigBlock(n *Network, peer *Peer, orderer *Orderer, channel string) *common.Block {
|
|
tempDir, err := ioutil.TempDir(n.RootDir, "getConfigBlock")
|
|
Expect(err).NotTo(HaveOccurred())
|
|
defer os.RemoveAll(tempDir)
|
|
|
|
// fetch the config block
|
|
output := filepath.Join(tempDir, "config_block.pb")
|
|
FetchConfigBlock(n, peer, orderer, channel, output)
|
|
|
|
// unmarshal the config block bytes
|
|
configBlock := UnmarshalBlockFromFile(output)
|
|
return configBlock
|
|
}
|
|
|
|
// FetchConfigBlock fetches latest config block.
|
|
func FetchConfigBlock(n *Network, peer *Peer, orderer *Orderer, channel string, output string) {
|
|
fetch := func() int {
|
|
sess, err := n.OrdererAdminSession(orderer, peer, commands.ChannelFetch{
|
|
ChannelID: channel,
|
|
Block: "config",
|
|
Orderer: n.OrdererAddress(orderer, ListenPort),
|
|
OutputFile: output,
|
|
ClientAuth: n.ClientAuthRequired,
|
|
})
|
|
Expect(err).NotTo(HaveOccurred())
|
|
code := sess.Wait(n.EventuallyTimeout).ExitCode()
|
|
if code == 0 {
|
|
Expect(sess.Err).To(gbytes.Say("Received block: "))
|
|
}
|
|
return code
|
|
}
|
|
Eventually(fetch, n.EventuallyTimeout).Should(Equal(0))
|
|
}
|
|
|
|
// GetConfig retrieves the last config of the given channel.
|
|
func GetConfig(n *Network, peer *Peer, orderer *Orderer, channel string) *common.Config {
|
|
configBlock := GetConfigBlock(n, peer, orderer, channel)
|
|
// unmarshal the envelope bytes
|
|
envelope, err := protoutil.GetEnvelopeFromBlock(configBlock.Data.Data[0])
|
|
Expect(err).NotTo(HaveOccurred())
|
|
|
|
// unmarshal the payload bytes
|
|
payload, err := protoutil.UnmarshalPayload(envelope.Payload)
|
|
Expect(err).NotTo(HaveOccurred())
|
|
|
|
// unmarshal the config envelope bytes
|
|
configEnv := &common.ConfigEnvelope{}
|
|
err = proto.Unmarshal(payload.Data, configEnv)
|
|
Expect(err).NotTo(HaveOccurred())
|
|
|
|
// clone the config
|
|
return configEnv.Config
|
|
}
|
|
|
|
// UpdateConfig computes, signs, and submits a configuration update and waits
|
|
// for the update to complete.
|
|
func UpdateConfig(n *Network, orderer *Orderer, channel string, current, updated *common.Config, getConfigBlockFromOrderer bool, submitter *Peer, additionalSigners ...*Peer) {
|
|
tempDir, err := ioutil.TempDir("", "updateConfig")
|
|
Expect(err).NotTo(HaveOccurred())
|
|
defer os.RemoveAll(tempDir)
|
|
|
|
// compute update
|
|
configUpdate, err := update.Compute(current, updated)
|
|
Expect(err).NotTo(HaveOccurred())
|
|
configUpdate.ChannelId = channel
|
|
|
|
signedEnvelope, err := protoutil.CreateSignedEnvelope(
|
|
common.HeaderType_CONFIG_UPDATE,
|
|
channel,
|
|
nil, // local signer
|
|
&common.ConfigUpdateEnvelope{ConfigUpdate: protoutil.MarshalOrPanic(configUpdate)},
|
|
0, // message version
|
|
0, // epoch
|
|
)
|
|
Expect(err).NotTo(HaveOccurred())
|
|
Expect(signedEnvelope).NotTo(BeNil())
|
|
|
|
updateFile := filepath.Join(tempDir, "update.pb")
|
|
err = ioutil.WriteFile(updateFile, protoutil.MarshalOrPanic(signedEnvelope), 0o600)
|
|
Expect(err).NotTo(HaveOccurred())
|
|
|
|
for _, signer := range additionalSigners {
|
|
sess, err := n.PeerAdminSession(signer, commands.SignConfigTx{
|
|
File: updateFile,
|
|
ClientAuth: n.ClientAuthRequired,
|
|
})
|
|
Expect(err).NotTo(HaveOccurred())
|
|
Eventually(sess, n.EventuallyTimeout).Should(gexec.Exit(0))
|
|
}
|
|
|
|
var currentBlockNumber uint64
|
|
// get current configuration block number
|
|
if getConfigBlockFromOrderer {
|
|
currentBlockNumber = CurrentConfigBlockNumber(n, submitter, orderer, channel)
|
|
} else {
|
|
currentBlockNumber = CurrentConfigBlockNumber(n, submitter, nil, channel)
|
|
}
|
|
|
|
sess, err := n.PeerAdminSession(submitter, commands.ChannelUpdate{
|
|
ChannelID: channel,
|
|
Orderer: n.OrdererAddress(orderer, ListenPort),
|
|
File: updateFile,
|
|
ClientAuth: n.ClientAuthRequired,
|
|
})
|
|
Expect(err).NotTo(HaveOccurred())
|
|
Eventually(sess, n.EventuallyTimeout).Should(gexec.Exit(0))
|
|
Expect(sess.Err).To(gbytes.Say("Successfully submitted channel update"))
|
|
|
|
if getConfigBlockFromOrderer {
|
|
ccb := func() uint64 { return CurrentConfigBlockNumber(n, submitter, orderer, channel) }
|
|
Eventually(ccb, n.EventuallyTimeout).Should(BeNumerically(">", currentBlockNumber))
|
|
return
|
|
}
|
|
// wait for the block to be committed to all peers that
|
|
// have joined the channel
|
|
for _, peer := range n.PeersWithChannel(channel) {
|
|
ccb := func() uint64 { return CurrentConfigBlockNumber(n, peer, nil, channel) }
|
|
Eventually(ccb, n.EventuallyTimeout).Should(BeNumerically(">", currentBlockNumber))
|
|
}
|
|
}
|
|
|
|
// CurrentConfigBlockNumber retrieves the block number from the header of the
|
|
// current config block. This can be used to detect when configuration change
|
|
// has completed. If an orderer is not provided, the current config block will
|
|
// be fetched from the peer.
|
|
func CurrentConfigBlockNumber(n *Network, peer *Peer, orderer *Orderer, channel string) uint64 {
|
|
tempDir, err := ioutil.TempDir(n.RootDir, "currentConfigBlock")
|
|
Expect(err).NotTo(HaveOccurred())
|
|
defer os.RemoveAll(tempDir)
|
|
|
|
// fetch the config block
|
|
output := filepath.Join(tempDir, "config_block.pb")
|
|
if orderer == nil {
|
|
return CurrentConfigBlockNumberFromPeer(n, peer, channel, output)
|
|
}
|
|
|
|
FetchConfigBlock(n, peer, orderer, channel, output)
|
|
|
|
// unmarshal the config block bytes
|
|
configBlock := UnmarshalBlockFromFile(output)
|
|
|
|
return configBlock.Header.Number
|
|
}
|
|
|
|
// CurrentConfigBlockNumberFromPeer retrieves the block number from the header
|
|
// of the peer's current config block.
|
|
func CurrentConfigBlockNumberFromPeer(n *Network, peer *Peer, channel, output string) uint64 {
|
|
sess, err := n.PeerAdminSession(peer, commands.ChannelFetch{
|
|
ChannelID: channel,
|
|
Block: "config",
|
|
OutputFile: output,
|
|
ClientAuth: n.ClientAuthRequired,
|
|
})
|
|
Expect(err).NotTo(HaveOccurred())
|
|
Eventually(sess, n.EventuallyTimeout).Should(gexec.Exit(0))
|
|
Expect(sess.Err).To(gbytes.Say("Received block: "))
|
|
|
|
configBlock := UnmarshalBlockFromFile(output)
|
|
|
|
return configBlock.Header.Number
|
|
}
|
|
|
|
// UpdateOrdererConfig computes, signs, and submits a configuration update
|
|
// which requires orderers signature and waits for the update to complete.
|
|
func UpdateOrdererConfig(n *Network, orderer *Orderer, channel string, current, updated *common.Config, submitter *Peer, additionalSigners ...*Orderer) {
|
|
tempDir, err := ioutil.TempDir(n.RootDir, "updateConfig")
|
|
Expect(err).NotTo(HaveOccurred())
|
|
updateFile := filepath.Join(tempDir, "update.pb")
|
|
defer os.RemoveAll(tempDir)
|
|
|
|
currentBlockNumber := CurrentConfigBlockNumber(n, submitter, orderer, channel)
|
|
ComputeUpdateOrdererConfig(updateFile, n, channel, current, updated, submitter, additionalSigners...)
|
|
|
|
Eventually(func() bool {
|
|
sess, err := n.OrdererAdminSession(orderer, submitter, commands.ChannelUpdate{
|
|
ChannelID: channel,
|
|
Orderer: n.OrdererAddress(orderer, ListenPort),
|
|
File: updateFile,
|
|
ClientAuth: n.ClientAuthRequired,
|
|
})
|
|
Expect(err).NotTo(HaveOccurred())
|
|
|
|
sess.Wait(n.EventuallyTimeout)
|
|
if sess.ExitCode() != 0 {
|
|
return false
|
|
}
|
|
|
|
return strings.Contains(string(sess.Err.Contents()), "Successfully submitted channel update")
|
|
}, n.EventuallyTimeout).Should(BeTrue())
|
|
|
|
// wait for the block to be committed
|
|
ccb := func() uint64 { return CurrentConfigBlockNumber(n, submitter, orderer, channel) }
|
|
Eventually(ccb, n.EventuallyTimeout).Should(BeNumerically(">", currentBlockNumber))
|
|
}
|
|
|
|
// UpdateOrdererConfigSession computes, signs, and submits a configuration
|
|
// update which requires orderer signatures. The caller should wait on the
|
|
// returned seession retrieve the exit code.
|
|
func UpdateOrdererConfigSession(n *Network, orderer *Orderer, channel string, current, updated *common.Config, submitter *Peer, additionalSigners ...*Orderer) *gexec.Session {
|
|
tempDir, err := ioutil.TempDir(n.RootDir, "updateConfig")
|
|
Expect(err).NotTo(HaveOccurred())
|
|
updateFile := filepath.Join(tempDir, "update.pb")
|
|
|
|
ComputeUpdateOrdererConfig(updateFile, n, channel, current, updated, submitter, additionalSigners...)
|
|
|
|
// session should not return with a zero exit code nor with a success response
|
|
sess, err := n.OrdererAdminSession(orderer, submitter, commands.ChannelUpdate{
|
|
ChannelID: channel,
|
|
Orderer: n.OrdererAddress(orderer, ListenPort),
|
|
File: updateFile,
|
|
ClientAuth: n.ClientAuthRequired,
|
|
})
|
|
Expect(err).NotTo(HaveOccurred())
|
|
return sess
|
|
}
|
|
|
|
func ComputeUpdateOrdererConfig(updateFile string, n *Network, channel string, current, updated *common.Config, submitter *Peer, additionalSigners ...*Orderer) {
|
|
// compute update
|
|
configUpdate, err := update.Compute(current, updated)
|
|
Expect(err).NotTo(HaveOccurred())
|
|
configUpdate.ChannelId = channel
|
|
|
|
signedEnvelope, err := protoutil.CreateSignedEnvelope(
|
|
common.HeaderType_CONFIG_UPDATE,
|
|
channel,
|
|
nil, // local signer
|
|
&common.ConfigUpdateEnvelope{ConfigUpdate: protoutil.MarshalOrPanic(configUpdate)},
|
|
0, // message version
|
|
0, // epoch
|
|
)
|
|
Expect(err).NotTo(HaveOccurred())
|
|
Expect(signedEnvelope).NotTo(BeNil())
|
|
|
|
err = ioutil.WriteFile(updateFile, protoutil.MarshalOrPanic(signedEnvelope), 0o600)
|
|
Expect(err).NotTo(HaveOccurred())
|
|
|
|
for _, signer := range additionalSigners {
|
|
sess, err := n.OrdererAdminSession(signer, submitter, commands.SignConfigTx{File: updateFile})
|
|
Expect(err).NotTo(HaveOccurred())
|
|
Eventually(sess, n.EventuallyTimeout).Should(gexec.Exit(0))
|
|
}
|
|
}
|
|
|
|
// UnmarshalBlockFromFile unmarshals a proto encoded block from a file.
|
|
func UnmarshalBlockFromFile(blockFile string) *common.Block {
|
|
blockBytes, err := ioutil.ReadFile(blockFile)
|
|
Expect(err).NotTo(HaveOccurred())
|
|
|
|
block, err := protoutil.UnmarshalBlock(blockBytes)
|
|
Expect(err).NotTo(HaveOccurred())
|
|
|
|
return block
|
|
}
|
|
|
|
// ConsensusMetadataMutator receives ConsensusType.Metadata and mutates it.
|
|
type ConsensusMetadataMutator func([]byte) []byte
|
|
|
|
// MSPMutator receives FabricMSPConfig and mutates it.
|
|
type MSPMutator func(config msp.FabricMSPConfig) msp.FabricMSPConfig
|
|
|
|
// UpdateConsensusMetadata executes a config update that updates the consensus
|
|
// metadata according to the given ConsensusMetadataMutator.
|
|
func UpdateConsensusMetadata(network *Network, peer *Peer, orderer *Orderer, channel string, mutateMetadata ConsensusMetadataMutator) {
|
|
config := GetConfig(network, 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())
|
|
|
|
consensusTypeValue.Metadata = mutateMetadata(consensusTypeValue.Metadata)
|
|
|
|
updatedConfig.ChannelGroup.Groups["Orderer"].Values["ConsensusType"] = &common.ConfigValue{
|
|
ModPolicy: "Admins",
|
|
Value: protoutil.MarshalOrPanic(consensusTypeValue),
|
|
}
|
|
|
|
UpdateOrdererConfig(network, orderer, channel, config, updatedConfig, peer, orderer)
|
|
}
|
|
|
|
func UpdateOrdererMSP(network *Network, peer *Peer, orderer *Orderer, channel, orgID string, mutateMSP MSPMutator) {
|
|
config := 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)
|
|
|
|
UpdateOrdererConfig(network, orderer, channel, config, updatedConfig, peer, orderer)
|
|
}
|
|
|
|
func UpdateConsenters(network *Network, peer *Peer, orderer *Orderer, channel string, f func(orderers *common.Orderers)) {
|
|
config := GetConfig(network, peer, orderer, channel)
|
|
|
|
updatedConfig := proto.Clone(config).(*common.Config)
|
|
|
|
rawOrderers := updatedConfig.ChannelGroup.Groups["Orderer"].Values["Orderers"]
|
|
|
|
orderersVal := &common.Orderers{}
|
|
err := proto.Unmarshal(rawOrderers.Value, orderersVal)
|
|
Expect(err).NotTo(HaveOccurred())
|
|
|
|
f(orderersVal)
|
|
|
|
policies.EncodeBFTBlockVerificationPolicy(orderersVal.ConsenterMapping, updatedConfig.ChannelGroup.Groups["Orderer"])
|
|
|
|
rawOrderers.Value, err = proto.Marshal(orderersVal)
|
|
Expect(err).NotTo(HaveOccurred())
|
|
|
|
updatedConfig.ChannelGroup.Groups["Orderer"].Values["Orderers"].Value = protoutil.MarshalOrPanic(orderersVal)
|
|
|
|
UpdateOrdererConfig(network, orderer, channel, config, updatedConfig, peer, orderer)
|
|
}
|
|
|
|
// UpdateOrdererEndpoints executes a config update that updates the orderer metadata according to the given endpoints
|
|
func UpdateOrdererEndpoints(network *Network, peer *Peer, orderer *Orderer, channel string, endpoints ...string) {
|
|
config := GetConfig(network, peer, orderer, channel)
|
|
updatedConfig := proto.Clone(config).(*common.Config)
|
|
|
|
ordererGrp := updatedConfig.ChannelGroup.Groups[channelconfig.OrdererGroupKey].Groups
|
|
// Get the first orderer org config
|
|
var firstOrdererConfig *common.ConfigGroup
|
|
for _, grp := range ordererGrp {
|
|
firstOrdererConfig = grp
|
|
break
|
|
}
|
|
|
|
firstOrdererConfig.Values["Endpoints"].Value = protoutil.MarshalOrPanic(&common.OrdererAddresses{
|
|
Addresses: endpoints,
|
|
})
|
|
|
|
UpdateOrdererConfig(network, orderer, channel, config, updatedConfig, peer, orderer)
|
|
}
|