go_study/fabric-main/orderer/consensus/etcdraft/membership_test.go

346 lines
9.6 KiB
Go

/*
Copyright IBM Corp. All Rights Reserved.
SPDX-License-Identifier: Apache-2.0
*/
package etcdraft_test
import (
"testing"
etcdraftproto "github.com/hyperledger/fabric-protos-go/orderer/etcdraft"
"github.com/hyperledger/fabric/common/channelconfig"
"github.com/hyperledger/fabric/common/crypto/tlsgen"
"github.com/hyperledger/fabric/orderer/consensus/etcdraft"
"github.com/hyperledger/fabric/orderer/consensus/etcdraft/mocks"
"github.com/stretchr/testify/require"
"go.etcd.io/etcd/raft/v3/raftpb"
)
func TestQuorumCheck(t *testing.T) {
tests := []struct {
Name string
NewConsenters map[uint64]*etcdraftproto.Consenter
ConfChange *raftpb.ConfChange
RotateNode uint64
ActiveNodes []uint64
QuorumLoss bool
}{
// Notations:
// 1 - node 1 is alive
// (1) - node 1 is dead
// 1' - node 1's cert is being rotated. Node is considered to be dead in new set
// Add
{
Name: "[1]->[1,(2)]",
NewConsenters: map[uint64]*etcdraftproto.Consenter{1: nil, 2: nil},
ConfChange: &raftpb.ConfChange{NodeID: 2, Type: raftpb.ConfChangeAddNode},
ActiveNodes: []uint64{1},
QuorumLoss: false,
},
{
Name: "[1,2]->[1,2,(3)]",
NewConsenters: map[uint64]*etcdraftproto.Consenter{1: nil, 2: nil, 3: nil},
ConfChange: &raftpb.ConfChange{NodeID: 3, Type: raftpb.ConfChangeAddNode},
ActiveNodes: []uint64{1, 2},
QuorumLoss: false,
},
{
Name: "[1,2,(3)]->[1,2,(3),(4)]",
NewConsenters: map[uint64]*etcdraftproto.Consenter{1: nil, 2: nil, 3: nil, 4: nil},
ConfChange: &raftpb.ConfChange{NodeID: 4, Type: raftpb.ConfChangeAddNode},
ActiveNodes: []uint64{1, 2},
QuorumLoss: true,
},
{
Name: "[1,2,3,(4)]->[1,2,3,(4),(5)]",
NewConsenters: map[uint64]*etcdraftproto.Consenter{1: nil, 2: nil, 3: nil, 4: nil, 5: nil},
ConfChange: &raftpb.ConfChange{NodeID: 5, Type: raftpb.ConfChangeAddNode},
ActiveNodes: []uint64{1, 2, 3},
QuorumLoss: false,
},
// Rotate
{
Name: "[1]->[1']",
NewConsenters: map[uint64]*etcdraftproto.Consenter{1: nil},
RotateNode: 1,
ActiveNodes: []uint64{1},
QuorumLoss: false,
},
{
Name: "[1,2]->[1,2']",
NewConsenters: map[uint64]*etcdraftproto.Consenter{1: nil, 2: nil},
RotateNode: 2,
ActiveNodes: []uint64{1, 2},
QuorumLoss: false,
},
{
Name: "[1,2,(3)]->[1,2',(3)]",
NewConsenters: map[uint64]*etcdraftproto.Consenter{1: nil, 2: nil, 3: nil},
RotateNode: 2,
ActiveNodes: []uint64{1, 2},
QuorumLoss: true,
},
{
Name: "[1,2,(3)]->[1,2,(3')]",
NewConsenters: map[uint64]*etcdraftproto.Consenter{1: nil, 2: nil, 3: nil},
RotateNode: 3,
ActiveNodes: []uint64{1, 2},
QuorumLoss: false,
},
// Remove
{
Name: "[1,2,(3)]->[1,2]",
NewConsenters: map[uint64]*etcdraftproto.Consenter{1: nil, 2: nil},
ConfChange: &raftpb.ConfChange{NodeID: 3, Type: raftpb.ConfChangeRemoveNode},
ActiveNodes: []uint64{1, 2},
QuorumLoss: false,
},
{
Name: "[1,2,(3)]->[1,(3)]",
NewConsenters: map[uint64]*etcdraftproto.Consenter{1: nil, 3: nil},
ConfChange: &raftpb.ConfChange{NodeID: 2, Type: raftpb.ConfChangeRemoveNode},
ActiveNodes: []uint64{1, 2},
QuorumLoss: true,
},
{
Name: "[1,2]->[1]",
NewConsenters: map[uint64]*etcdraftproto.Consenter{1: nil},
ConfChange: &raftpb.ConfChange{NodeID: 2, Type: raftpb.ConfChangeRemoveNode},
ActiveNodes: []uint64{1, 2},
QuorumLoss: false,
},
}
for _, test := range tests {
t.Run(test.Name, func(t *testing.T) {
changes := &etcdraft.MembershipChanges{
NewConsenters: test.NewConsenters,
ConfChange: test.ConfChange,
RotatedNode: test.RotateNode,
}
require.Equal(t, test.QuorumLoss, changes.UnacceptableQuorumLoss(test.ActiveNodes))
})
}
}
func TestMembershipChanges(t *testing.T) {
blockMetadata := &etcdraftproto.BlockMetadata{
ConsenterIds: []uint64{1, 2},
NextConsenterId: 3,
}
// generate certs for adding a new consenter
// certs for fake-org
tlsCA, err := tlsgen.NewCA()
require.NoError(t, err)
client1, err := tlsCA.NewClientCertKeyPair()
require.NoError(t, err)
tlsIntermediateCA, err := tlsCA.NewIntermediateCA()
require.NoError(t, err)
client2, err := tlsIntermediateCA.NewClientCertKeyPair()
require.NoError(t, err)
// certs for fake-org2
tlsCA2, err := tlsgen.NewCA()
require.NoError(t, err)
client3, err := tlsCA2.NewClientCertKeyPair()
require.NoError(t, err)
tlsIntermediateCA2, err := tlsCA2.NewIntermediateCA()
require.NoError(t, err)
client4, err := tlsIntermediateCA2.NewClientCertKeyPair()
require.NoError(t, err)
require.NoError(t, err)
c := []*etcdraftproto.Consenter{
{ClientTlsCert: client1.Cert, ServerTlsCert: client1.Cert},
{ClientTlsCert: client2.Cert, ServerTlsCert: client2.Cert},
{ClientTlsCert: client3.Cert, ServerTlsCert: client3.Cert},
{ClientTlsCert: client4.Cert, ServerTlsCert: client4.Cert},
}
mockOrdererConfig := &mocks.OrdererConfig{}
mockOrg := &mocks.OrdererOrg{}
mockMSP := &mocks.MSP{}
mockMSP.GetTLSRootCertsReturns([][]byte{
tlsCA.CertBytes(),
})
mockMSP.GetTLSIntermediateCertsReturns([][]byte{
tlsIntermediateCA.CertBytes(),
})
mockOrg.MSPReturns(mockMSP)
mockOrg2 := &mocks.OrdererOrg{}
mockMSP2 := &mocks.MSP{}
mockMSP2.GetTLSRootCertsReturns([][]byte{
tlsCA2.CertBytes(),
})
mockMSP2.GetTLSIntermediateCertsReturns([][]byte{
tlsIntermediateCA2.CertBytes(),
})
mockOrg2.MSPReturns(mockMSP2)
mockOrdererConfig.OrganizationsReturns(map[string]channelconfig.OrdererOrg{
"fake-org": mockOrg,
"fake-org2": mockOrg2,
})
tests := []struct {
Name string
OldConsenters map[uint64]*etcdraftproto.Consenter
NewConsenters []*etcdraftproto.Consenter
Changes *etcdraft.MembershipChanges
Changed, Rotated bool
ExpectedErr string
}{
{
Name: "Add a node",
OldConsenters: map[uint64]*etcdraftproto.Consenter{
1: c[0],
2: c[1],
},
NewConsenters: []*etcdraftproto.Consenter{
c[0],
c[1],
c[2],
},
Changes: &etcdraft.MembershipChanges{
NewBlockMetadata: &etcdraftproto.BlockMetadata{
ConsenterIds: []uint64{1, 2, 3},
NextConsenterId: 4,
},
NewConsenters: map[uint64]*etcdraftproto.Consenter{1: c[0], 2: c[1], 3: c[2]},
AddedNodes: []*etcdraftproto.Consenter{c[2]},
RemovedNodes: []*etcdraftproto.Consenter{},
ConfChange: &raftpb.ConfChange{
NodeID: 3,
Type: raftpb.ConfChangeAddNode,
},
},
Changed: true,
Rotated: false,
ExpectedErr: "",
},
{
Name: "Remove a node",
OldConsenters: map[uint64]*etcdraftproto.Consenter{
1: c[0],
2: c[1],
},
NewConsenters: []*etcdraftproto.Consenter{
c[1],
},
Changes: &etcdraft.MembershipChanges{
NewBlockMetadata: &etcdraftproto.BlockMetadata{
ConsenterIds: []uint64{2},
NextConsenterId: 3,
},
NewConsenters: map[uint64]*etcdraftproto.Consenter{2: c[1]},
AddedNodes: []*etcdraftproto.Consenter{},
RemovedNodes: []*etcdraftproto.Consenter{c[0]},
ConfChange: &raftpb.ConfChange{
NodeID: 1,
Type: raftpb.ConfChangeRemoveNode,
},
},
Changed: true,
Rotated: false,
ExpectedErr: "",
},
{
Name: "Rotate a certificate",
OldConsenters: map[uint64]*etcdraftproto.Consenter{
1: c[0],
2: c[1],
},
NewConsenters: []*etcdraftproto.Consenter{
c[0],
c[2],
},
Changes: &etcdraft.MembershipChanges{
NewBlockMetadata: &etcdraftproto.BlockMetadata{
ConsenterIds: []uint64{1, 2},
NextConsenterId: 3,
},
NewConsenters: map[uint64]*etcdraftproto.Consenter{1: c[0], 2: c[2]},
AddedNodes: []*etcdraftproto.Consenter{c[2]},
RemovedNodes: []*etcdraftproto.Consenter{c[1]},
RotatedNode: 2,
},
Changed: true,
Rotated: true,
ExpectedErr: "",
},
{
Name: "No change",
OldConsenters: map[uint64]*etcdraftproto.Consenter{
1: c[0],
2: c[1],
},
NewConsenters: []*etcdraftproto.Consenter{
c[0],
c[1],
},
Changes: &etcdraft.MembershipChanges{
NewBlockMetadata: &etcdraftproto.BlockMetadata{
ConsenterIds: []uint64{1, 2},
NextConsenterId: 3,
},
NewConsenters: map[uint64]*etcdraftproto.Consenter{1: c[0], 2: c[1]},
AddedNodes: []*etcdraftproto.Consenter{},
RemovedNodes: []*etcdraftproto.Consenter{},
},
Changed: false,
Rotated: false,
ExpectedErr: "",
},
{
Name: "More than one consenter added",
OldConsenters: map[uint64]*etcdraftproto.Consenter{
1: c[0],
2: c[1],
},
NewConsenters: []*etcdraftproto.Consenter{
c[0],
c[1],
c[2],
c[3],
},
Changes: nil,
ExpectedErr: "update of more than one consenter at a time is not supported, requested changes: add 2 node(s), remove 0 node(s)",
},
{
Name: "More than one consenter removed",
OldConsenters: map[uint64]*etcdraftproto.Consenter{
1: c[0],
2: c[1],
},
NewConsenters: []*etcdraftproto.Consenter{
c[2],
},
Changes: nil,
ExpectedErr: "update of more than one consenter at a time is not supported, requested changes: add 1 node(s), remove 2 node(s)",
},
}
for _, test := range tests {
t.Run(test.Name, func(t *testing.T) {
changes, err := etcdraft.ComputeMembershipChanges(blockMetadata, test.OldConsenters, test.NewConsenters)
if test.ExpectedErr != "" {
require.EqualError(t, err, test.ExpectedErr)
} else {
require.NoError(t, err)
require.Equal(t, test.Changes, changes)
require.Equal(t, test.Changed, changes.Changed())
require.Equal(t, test.Rotated, changes.Rotated())
}
})
}
}