github.com/true-sqn/fabric@v2.1.1+incompatible/orderer/consensus/etcdraft/membership_test.go (about)

     1  /*
     2  Copyright IBM Corp. All Rights Reserved.
     3  
     4  SPDX-License-Identifier: Apache-2.0
     5  */
     6  
     7  package etcdraft_test
     8  
     9  import (
    10  	"fmt"
    11  	"testing"
    12  
    13  	etcdraftproto "github.com/hyperledger/fabric-protos-go/orderer/etcdraft"
    14  	"github.com/hyperledger/fabric/common/channelconfig"
    15  	"github.com/hyperledger/fabric/common/crypto/tlsgen"
    16  	"github.com/hyperledger/fabric/orderer/consensus/etcdraft"
    17  	"github.com/hyperledger/fabric/orderer/consensus/etcdraft/mocks"
    18  	"github.com/stretchr/testify/require"
    19  	"go.etcd.io/etcd/raft/raftpb"
    20  )
    21  
    22  func TestQuorumCheck(t *testing.T) {
    23  	tests := []struct {
    24  		Name          string
    25  		NewConsenters map[uint64]*etcdraftproto.Consenter
    26  		ConfChange    *raftpb.ConfChange
    27  		RotateNode    uint64
    28  		ActiveNodes   []uint64
    29  		QuorumLoss    bool
    30  	}{
    31  		// Notations:
    32  		//  1     - node 1 is alive
    33  		// (1)    - node 1 is dead
    34  		//  1'    - node 1's cert is being rotated. Node is considered to be dead in new set
    35  
    36  		// Add
    37  		{
    38  			Name:          "[1]->[1,(2)]",
    39  			NewConsenters: map[uint64]*etcdraftproto.Consenter{1: nil, 2: nil},
    40  			ConfChange:    &raftpb.ConfChange{NodeID: 2, Type: raftpb.ConfChangeAddNode},
    41  			ActiveNodes:   []uint64{1},
    42  			QuorumLoss:    false,
    43  		},
    44  		{
    45  			Name:          "[1,2]->[1,2,(3)]",
    46  			NewConsenters: map[uint64]*etcdraftproto.Consenter{1: nil, 2: nil, 3: nil},
    47  			ConfChange:    &raftpb.ConfChange{NodeID: 3, Type: raftpb.ConfChangeAddNode},
    48  			ActiveNodes:   []uint64{1, 2},
    49  			QuorumLoss:    false,
    50  		},
    51  		{
    52  			Name:          "[1,2,(3)]->[1,2,(3),(4)]",
    53  			NewConsenters: map[uint64]*etcdraftproto.Consenter{1: nil, 2: nil, 3: nil, 4: nil},
    54  			ConfChange:    &raftpb.ConfChange{NodeID: 4, Type: raftpb.ConfChangeAddNode},
    55  			ActiveNodes:   []uint64{1, 2},
    56  			QuorumLoss:    true,
    57  		},
    58  		{
    59  			Name:          "[1,2,3,(4)]->[1,2,3,(4),(5)]",
    60  			NewConsenters: map[uint64]*etcdraftproto.Consenter{1: nil, 2: nil, 3: nil, 4: nil, 5: nil},
    61  			ConfChange:    &raftpb.ConfChange{NodeID: 5, Type: raftpb.ConfChangeAddNode},
    62  			ActiveNodes:   []uint64{1, 2, 3},
    63  			QuorumLoss:    false,
    64  		},
    65  		// Rotate
    66  		{
    67  			Name:          "[1]->[1']",
    68  			NewConsenters: map[uint64]*etcdraftproto.Consenter{1: nil},
    69  			RotateNode:    1,
    70  			ActiveNodes:   []uint64{1},
    71  			QuorumLoss:    false,
    72  		},
    73  		{
    74  			Name:          "[1,2]->[1,2']",
    75  			NewConsenters: map[uint64]*etcdraftproto.Consenter{1: nil, 2: nil},
    76  			RotateNode:    2,
    77  			ActiveNodes:   []uint64{1, 2},
    78  			QuorumLoss:    false,
    79  		},
    80  		{
    81  			Name:          "[1,2,(3)]->[1,2',(3)]",
    82  			NewConsenters: map[uint64]*etcdraftproto.Consenter{1: nil, 2: nil, 3: nil},
    83  			RotateNode:    2,
    84  			ActiveNodes:   []uint64{1, 2},
    85  			QuorumLoss:    true,
    86  		},
    87  		{
    88  			Name:          "[1,2,(3)]->[1,2,(3')]",
    89  			NewConsenters: map[uint64]*etcdraftproto.Consenter{1: nil, 2: nil, 3: nil},
    90  			RotateNode:    3,
    91  			ActiveNodes:   []uint64{1, 2},
    92  			QuorumLoss:    false,
    93  		},
    94  		// Remove
    95  		{
    96  			Name:          "[1,2,(3)]->[1,2]",
    97  			NewConsenters: map[uint64]*etcdraftproto.Consenter{1: nil, 2: nil},
    98  			ConfChange:    &raftpb.ConfChange{NodeID: 3, Type: raftpb.ConfChangeRemoveNode},
    99  			ActiveNodes:   []uint64{1, 2},
   100  			QuorumLoss:    false,
   101  		},
   102  		{
   103  			Name:          "[1,2,(3)]->[1,(3)]",
   104  			NewConsenters: map[uint64]*etcdraftproto.Consenter{1: nil, 3: nil},
   105  			ConfChange:    &raftpb.ConfChange{NodeID: 2, Type: raftpb.ConfChangeRemoveNode},
   106  			ActiveNodes:   []uint64{1, 2},
   107  			QuorumLoss:    true,
   108  		},
   109  		{
   110  			Name:          "[1,2]->[1]",
   111  			NewConsenters: map[uint64]*etcdraftproto.Consenter{1: nil},
   112  			ConfChange:    &raftpb.ConfChange{NodeID: 2, Type: raftpb.ConfChangeRemoveNode},
   113  			ActiveNodes:   []uint64{1, 2},
   114  			QuorumLoss:    false,
   115  		},
   116  	}
   117  
   118  	for _, test := range tests {
   119  		t.Run(test.Name, func(t *testing.T) {
   120  			changes := &etcdraft.MembershipChanges{
   121  				NewConsenters: test.NewConsenters,
   122  				ConfChange:    test.ConfChange,
   123  				RotatedNode:   test.RotateNode,
   124  			}
   125  
   126  			require.Equal(t, test.QuorumLoss, changes.UnacceptableQuorumLoss(test.ActiveNodes))
   127  		})
   128  	}
   129  }
   130  
   131  func TestMembershipChanges(t *testing.T) {
   132  	blockMetadata := &etcdraftproto.BlockMetadata{
   133  		ConsenterIds:    []uint64{1, 2},
   134  		NextConsenterId: 3,
   135  	}
   136  
   137  	// generate certs for adding a new consenter
   138  	// certs for fake-org
   139  	tlsCA, err := tlsgen.NewCA()
   140  	require.NoError(t, err)
   141  	client1, err := tlsCA.NewClientCertKeyPair()
   142  	require.NoError(t, err)
   143  	tlsIntermediateCA, err := tlsCA.NewIntermediateCA()
   144  	require.NoError(t, err)
   145  	client2, err := tlsIntermediateCA.NewClientCertKeyPair()
   146  	require.NoError(t, err)
   147  
   148  	// certs for fake-org2
   149  	tlsCA2, err := tlsgen.NewCA()
   150  	require.NoError(t, err)
   151  	client3, err := tlsCA2.NewClientCertKeyPair()
   152  	require.NoError(t, err)
   153  	tlsIntermediateCA2, err := tlsCA2.NewIntermediateCA()
   154  	require.NoError(t, err)
   155  	client4, err := tlsIntermediateCA2.NewClientCertKeyPair()
   156  	require.NoError(t, err)
   157  
   158  	// cert for fake-org3
   159  	tlsCA3, err := tlsgen.NewCA()
   160  	require.NoError(t, err)
   161  	client5, err := tlsCA3.NewClientCertKeyPair()
   162  	require.NoError(t, err)
   163  
   164  	c := []*etcdraftproto.Consenter{
   165  		{ClientTlsCert: client1.Cert, ServerTlsCert: client1.Cert},
   166  		{ClientTlsCert: client2.Cert, ServerTlsCert: client2.Cert},
   167  		{ClientTlsCert: client3.Cert, ServerTlsCert: client3.Cert},
   168  		{ClientTlsCert: client4.Cert, ServerTlsCert: client4.Cert},
   169  	}
   170  
   171  	mockOrdererConfig := &mocks.OrdererConfig{}
   172  	mockOrg := &mocks.OrdererOrg{}
   173  	mockMSP := &mocks.MSP{}
   174  	mockMSP.GetTLSRootCertsReturns([][]byte{
   175  		tlsCA.CertBytes(),
   176  	})
   177  	mockMSP.GetTLSIntermediateCertsReturns([][]byte{
   178  		tlsIntermediateCA.CertBytes(),
   179  	})
   180  	mockOrg.MSPReturns(mockMSP)
   181  
   182  	mockOrg2 := &mocks.OrdererOrg{}
   183  	mockMSP2 := &mocks.MSP{}
   184  	mockMSP2.GetTLSRootCertsReturns([][]byte{
   185  		tlsCA2.CertBytes(),
   186  	})
   187  	mockMSP2.GetTLSIntermediateCertsReturns([][]byte{
   188  		tlsIntermediateCA2.CertBytes(),
   189  	})
   190  
   191  	mockOrg2.MSPReturns(mockMSP2)
   192  
   193  	mockOrdererConfig.OrganizationsReturns(map[string]channelconfig.OrdererOrg{
   194  		"fake-org":  mockOrg,
   195  		"fake-org2": mockOrg2,
   196  	})
   197  
   198  	tests := []struct {
   199  		Name             string
   200  		OldConsenters    map[uint64]*etcdraftproto.Consenter
   201  		NewConsenters    []*etcdraftproto.Consenter
   202  		Changes          *etcdraft.MembershipChanges
   203  		Changed, Rotated bool
   204  		ExpectedErr      string
   205  	}{
   206  		{
   207  			Name: "Add a node",
   208  			OldConsenters: map[uint64]*etcdraftproto.Consenter{
   209  				1: c[0],
   210  				2: c[1],
   211  			},
   212  			NewConsenters: []*etcdraftproto.Consenter{
   213  				c[0],
   214  				c[1],
   215  				c[2],
   216  			},
   217  			Changes: &etcdraft.MembershipChanges{
   218  				NewBlockMetadata: &etcdraftproto.BlockMetadata{
   219  					ConsenterIds:    []uint64{1, 2, 3},
   220  					NextConsenterId: 4,
   221  				},
   222  				NewConsenters: map[uint64]*etcdraftproto.Consenter{1: c[0], 2: c[1], 3: c[2]},
   223  				AddedNodes:    []*etcdraftproto.Consenter{c[2]},
   224  				RemovedNodes:  []*etcdraftproto.Consenter{},
   225  				ConfChange: &raftpb.ConfChange{
   226  					NodeID: 3,
   227  					Type:   raftpb.ConfChangeAddNode,
   228  				},
   229  			},
   230  			Changed:     true,
   231  			Rotated:     false,
   232  			ExpectedErr: "",
   233  		},
   234  		{
   235  			Name: "Add a node with an invalid client cert bytes",
   236  			OldConsenters: map[uint64]*etcdraftproto.Consenter{
   237  				1: c[0],
   238  			},
   239  			NewConsenters: []*etcdraftproto.Consenter{
   240  				c[0],
   241  				{ClientTlsCert: []byte("woops")},
   242  			},
   243  			Changes:     nil,
   244  			ExpectedErr: fmt.Sprintf("parsing tls client cert: no PEM data found in cert[% x]", []byte("woops")),
   245  		},
   246  		{
   247  			Name: "Add a node with an invalid server cert bytes",
   248  			OldConsenters: map[uint64]*etcdraftproto.Consenter{
   249  				1: c[0],
   250  			},
   251  			NewConsenters: []*etcdraftproto.Consenter{
   252  				c[0],
   253  				{ClientTlsCert: client3.Cert, ServerTlsCert: []byte("doh!")},
   254  			},
   255  			Changes:     nil,
   256  			ExpectedErr: fmt.Sprintf("parsing tls server cert: no PEM data found in cert[% x]", []byte("doh!")),
   257  		},
   258  		{
   259  			Name: "Add a node with an invalid tls client cert",
   260  			OldConsenters: map[uint64]*etcdraftproto.Consenter{
   261  				1: c[0],
   262  			},
   263  			NewConsenters: []*etcdraftproto.Consenter{
   264  				c[0],
   265  				{ClientTlsCert: client5.Cert, ServerTlsCert: client3.Cert},
   266  			},
   267  			Changes:     nil,
   268  			ExpectedErr: fmt.Sprintf("verifying tls client cert with serial number %d: x509: certificate signed by unknown authority", client5.TLSCert.SerialNumber),
   269  		},
   270  		{
   271  			Name: "Add a node with an invalid tls server cert",
   272  			OldConsenters: map[uint64]*etcdraftproto.Consenter{
   273  				1: c[0],
   274  			},
   275  			NewConsenters: []*etcdraftproto.Consenter{
   276  				c[0],
   277  				{ClientTlsCert: client3.Cert, ServerTlsCert: client5.Cert},
   278  			},
   279  			Changes:     nil,
   280  			ExpectedErr: fmt.Sprintf("verifying tls server cert with serial number %d: x509: certificate signed by unknown authority", client5.TLSCert.SerialNumber),
   281  		},
   282  		{
   283  			Name: "Remove a node",
   284  			OldConsenters: map[uint64]*etcdraftproto.Consenter{
   285  				1: c[0],
   286  				2: c[1],
   287  			},
   288  			NewConsenters: []*etcdraftproto.Consenter{
   289  				c[1],
   290  			},
   291  			Changes: &etcdraft.MembershipChanges{
   292  				NewBlockMetadata: &etcdraftproto.BlockMetadata{
   293  					ConsenterIds:    []uint64{2},
   294  					NextConsenterId: 3,
   295  				},
   296  				NewConsenters: map[uint64]*etcdraftproto.Consenter{2: c[1]},
   297  				AddedNodes:    []*etcdraftproto.Consenter{},
   298  				RemovedNodes:  []*etcdraftproto.Consenter{c[0]},
   299  				ConfChange: &raftpb.ConfChange{
   300  					NodeID: 1,
   301  					Type:   raftpb.ConfChangeRemoveNode,
   302  				},
   303  			},
   304  			Changed:     true,
   305  			Rotated:     false,
   306  			ExpectedErr: "",
   307  		},
   308  		{
   309  			Name: "Rotate a certificate",
   310  			OldConsenters: map[uint64]*etcdraftproto.Consenter{
   311  				1: c[0],
   312  				2: c[1],
   313  			},
   314  			NewConsenters: []*etcdraftproto.Consenter{
   315  				c[0],
   316  				c[2],
   317  			},
   318  			Changes: &etcdraft.MembershipChanges{
   319  				NewBlockMetadata: &etcdraftproto.BlockMetadata{
   320  					ConsenterIds:    []uint64{1, 2},
   321  					NextConsenterId: 3,
   322  				},
   323  				NewConsenters: map[uint64]*etcdraftproto.Consenter{1: c[0], 2: c[2]},
   324  				AddedNodes:    []*etcdraftproto.Consenter{c[2]},
   325  				RemovedNodes:  []*etcdraftproto.Consenter{c[1]},
   326  				RotatedNode:   2,
   327  			},
   328  			Changed:     true,
   329  			Rotated:     true,
   330  			ExpectedErr: "",
   331  		},
   332  		{
   333  			Name: "No change",
   334  			OldConsenters: map[uint64]*etcdraftproto.Consenter{
   335  				1: c[0],
   336  				2: c[1],
   337  			},
   338  			NewConsenters: []*etcdraftproto.Consenter{
   339  				c[0],
   340  				c[1],
   341  			},
   342  			Changes: &etcdraft.MembershipChanges{
   343  				NewBlockMetadata: &etcdraftproto.BlockMetadata{
   344  					ConsenterIds:    []uint64{1, 2},
   345  					NextConsenterId: 3,
   346  				},
   347  				NewConsenters: map[uint64]*etcdraftproto.Consenter{1: c[0], 2: c[1]},
   348  				AddedNodes:    []*etcdraftproto.Consenter{},
   349  				RemovedNodes:  []*etcdraftproto.Consenter{},
   350  			},
   351  			Changed:     false,
   352  			Rotated:     false,
   353  			ExpectedErr: "",
   354  		},
   355  		{
   356  			Name: "More than one consenter added",
   357  			OldConsenters: map[uint64]*etcdraftproto.Consenter{
   358  				1: c[0],
   359  				2: c[1],
   360  			},
   361  			NewConsenters: []*etcdraftproto.Consenter{
   362  				c[0],
   363  				c[1],
   364  				c[2],
   365  				c[3],
   366  			},
   367  			Changes:     nil,
   368  			ExpectedErr: "update of more than one consenter at a time is not supported, requested changes: add 2 node(s), remove 0 node(s)",
   369  		},
   370  		{
   371  			Name: "More than one consenter removed",
   372  			OldConsenters: map[uint64]*etcdraftproto.Consenter{
   373  				1: c[0],
   374  				2: c[1],
   375  			},
   376  			NewConsenters: []*etcdraftproto.Consenter{
   377  				c[2],
   378  			},
   379  			Changes:     nil,
   380  			ExpectedErr: "update of more than one consenter at a time is not supported, requested changes: add 1 node(s), remove 2 node(s)",
   381  		},
   382  	}
   383  
   384  	for _, test := range tests {
   385  		t.Run(test.Name, func(t *testing.T) {
   386  			changes, err := etcdraft.ComputeMembershipChanges(blockMetadata, test.OldConsenters, test.NewConsenters, mockOrdererConfig)
   387  
   388  			if test.ExpectedErr != "" {
   389  				require.EqualError(t, err, test.ExpectedErr)
   390  			} else {
   391  				require.NoError(t, err)
   392  				require.Equal(t, test.Changes, changes)
   393  				require.Equal(t, test.Changed, changes.Changed())
   394  				require.Equal(t, test.Rotated, changes.Rotated())
   395  			}
   396  		})
   397  	}
   398  }