github.com/defanghe/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 }