github.com/hechain20/hechain@v0.0.0-20220316014945-b544036ba106/orderer/common/follower/follower_chain_test.go (about) 1 /* 2 Copyright hechain. 2017 All Rights Reserved. 3 4 SPDX-License-Identifier: Apache-2.0 5 */ 6 7 package follower_test 8 9 import ( 10 "sync" 11 "sync/atomic" 12 "testing" 13 "time" 14 15 "github.com/golang/protobuf/proto" 16 "github.com/hechain20/hechain/bccsp" 17 "github.com/hechain20/hechain/bccsp/sw" 18 "github.com/hechain20/hechain/common/flogging" 19 "github.com/hechain20/hechain/orderer/common/follower" 20 "github.com/hechain20/hechain/orderer/common/follower/mocks" 21 "github.com/hechain20/hechain/orderer/common/types" 22 "github.com/hechain20/hechain/orderer/consensus" 23 "github.com/hechain20/hechain/protoutil" 24 "github.com/hyperledger/fabric-protos-go/common" 25 "github.com/pkg/errors" 26 "github.com/stretchr/testify/require" 27 ) 28 29 //go:generate counterfeiter -o mocks/cluster_consenter.go -fake-name ClusterConsenter . clusterConsenter 30 type clusterConsenter interface { 31 consensus.ClusterConsenter 32 } 33 34 var _ clusterConsenter // suppress "unused type" warning 35 36 //go:generate counterfeiter -o mocks/time_after.go -fake-name TimeAfter . timeAfter 37 type timeAfter interface { 38 After(d time.Duration) <-chan time.Time 39 } 40 41 var _ timeAfter // suppress "unused type" warning 42 43 var testLogger = flogging.MustGetLogger("follower.test") 44 45 // Global test variables 46 var ( 47 cryptoProvider bccsp.BCCSP 48 localBlockchain *memoryBlockChain 49 remoteBlockchain *memoryBlockChain 50 ledgerResources *mocks.LedgerResources 51 mockClusterConsenter *mocks.ClusterConsenter 52 mockChannelParticipationMetricsReporter *mocks.ChannelParticipationMetricsReporter 53 pullerFactory *mocks.BlockPullerFactory 54 puller *mocks.ChannelPuller 55 mockChainCreator *mocks.ChainCreator 56 options follower.Options 57 timeAfterCount *mocks.TimeAfter 58 maxDelay int64 59 ) 60 61 // Before each test in all test cases 62 func globalSetup(t *testing.T) { 63 var err error 64 cryptoProvider, err = sw.NewDefaultSecurityLevelWithKeystore(sw.NewDummyKeyStore()) 65 require.NoError(t, err) 66 67 localBlockchain = &memoryBlockChain{} 68 remoteBlockchain = &memoryBlockChain{} 69 70 ledgerResources = &mocks.LedgerResources{} 71 ledgerResources.ChannelIDReturns("my-channel") 72 ledgerResources.HeightCalls(localBlockchain.Height) 73 ledgerResources.BlockCalls(localBlockchain.Block) 74 75 mockClusterConsenter = &mocks.ClusterConsenter{} 76 77 pullerFactory = &mocks.BlockPullerFactory{} 78 puller = &mocks.ChannelPuller{} 79 pullerFactory.BlockPullerReturns(puller, nil) 80 81 mockChainCreator = &mocks.ChainCreator{} 82 83 mockChannelParticipationMetricsReporter = &mocks.ChannelParticipationMetricsReporter{} 84 85 options = follower.Options{ 86 Logger: testLogger, 87 PullRetryMinInterval: 1 * time.Microsecond, 88 PullRetryMaxInterval: 5 * time.Microsecond, 89 HeightPollMinInterval: 1 * time.Microsecond, 90 HeightPollMaxInterval: 10 * time.Microsecond, 91 Cert: []byte{1, 2, 3, 4}, 92 } 93 94 atomic.StoreInt64(&maxDelay, 0) 95 timeAfterCount = &mocks.TimeAfter{} 96 timeAfterCount.AfterCalls( 97 func(d time.Duration) <-chan time.Time { 98 if d.Nanoseconds() > atomic.LoadInt64(&maxDelay) { 99 atomic.StoreInt64(&maxDelay, d.Nanoseconds()) 100 } 101 c := make(chan time.Time, 1) 102 c <- time.Now() 103 return c 104 }, 105 ) 106 } 107 108 func TestFollowerNewChain(t *testing.T) { 109 joinBlockAppRaft := makeConfigBlock(10, []byte{}, 0) 110 require.NotNil(t, joinBlockAppRaft) 111 112 t.Run("with join block, not in channel, empty ledger", func(t *testing.T) { 113 globalSetup(t) 114 ledgerResources.HeightReturns(0) 115 mockClusterConsenter.IsChannelMemberReturns(false, nil) 116 chain, err := follower.NewChain(ledgerResources, mockClusterConsenter, joinBlockAppRaft, options, pullerFactory, mockChainCreator, nil, mockChannelParticipationMetricsReporter) 117 require.NoError(t, err) 118 119 consensusRelation, status := chain.StatusReport() 120 require.Equal(t, types.ConsensusRelationFollower, consensusRelation) 121 require.Equal(t, types.StatusOnBoarding, status) 122 }) 123 124 t.Run("with join block, in channel, empty ledger", func(t *testing.T) { 125 globalSetup(t) 126 ledgerResources.HeightReturns(0) 127 mockClusterConsenter.IsChannelMemberReturns(true, nil) 128 chain, err := follower.NewChain(ledgerResources, mockClusterConsenter, joinBlockAppRaft, options, pullerFactory, mockChainCreator, nil, mockChannelParticipationMetricsReporter) 129 require.NoError(t, err) 130 131 consensusRelation, status := chain.StatusReport() 132 require.Equal(t, types.ConsensusRelationConsenter, consensusRelation) 133 require.Equal(t, types.StatusOnBoarding, status) 134 135 require.NotPanics(t, chain.Start) 136 require.NotPanics(t, chain.Start) 137 require.NotPanics(t, chain.Halt) 138 require.NotPanics(t, chain.Halt) 139 require.NotPanics(t, chain.Start) 140 }) 141 142 t.Run("bad join block", func(t *testing.T) { 143 globalSetup(t) 144 chain, err := follower.NewChain(ledgerResources, mockClusterConsenter, &common.Block{}, options, pullerFactory, mockChainCreator, nil, mockChannelParticipationMetricsReporter) 145 require.EqualError(t, err, "block header is nil") 146 require.Nil(t, chain) 147 chain, err = follower.NewChain(ledgerResources, mockClusterConsenter, &common.Block{Header: &common.BlockHeader{}}, options, pullerFactory, mockChainCreator, nil, mockChannelParticipationMetricsReporter) 148 require.EqualError(t, err, "block data is nil") 149 require.Nil(t, chain) 150 }) 151 152 t.Run("without join block", func(t *testing.T) { 153 globalSetup(t) 154 localBlockchain.fill(5) 155 mockClusterConsenter.IsChannelMemberCalls(amIReallyInChannel) 156 chain, err := follower.NewChain(ledgerResources, mockClusterConsenter, nil, options, pullerFactory, mockChainCreator, nil, mockChannelParticipationMetricsReporter) 157 require.NoError(t, err) 158 159 consensusRelation, status := chain.StatusReport() 160 require.Equal(t, types.ConsensusRelationFollower, consensusRelation) 161 require.True(t, status == types.StatusActive) 162 }) 163 164 t.Run("can not find config block in chain", func(t *testing.T) { 165 globalSetup(t) 166 localBlockchain.fill(5) 167 168 // Set last config index to non-existing value 222 169 lastBlock := localBlockchain.Block(localBlockchain.Height() - 1) 170 err := setLastConfigIndexInBlock(lastBlock, 222) 171 require.NoError(t, err) 172 173 chain, err := follower.NewChain(ledgerResources, mockClusterConsenter, nil, options, pullerFactory, mockChainCreator, nil, mockChannelParticipationMetricsReporter) 174 require.EqualError(t, err, "could not retrieve config block from index 222") 175 require.Nil(t, chain) 176 }) 177 } 178 179 func TestFollowerPullUpToJoin(t *testing.T) { 180 joinNum := uint64(10) 181 joinBlockAppRaft := makeConfigBlock(joinNum, []byte{}, 1) 182 require.NotNil(t, joinBlockAppRaft) 183 184 var wgChain sync.WaitGroup 185 186 setup := func() { 187 globalSetup(t) 188 remoteBlockchain.fill(joinNum) 189 remoteBlockchain.appendConfig(1) 190 191 ledgerResources.AppendCalls(localBlockchain.Append) 192 193 puller.PullBlockCalls(func(i uint64) *common.Block { return remoteBlockchain.Block(i) }) 194 pullerFactory.BlockPullerReturns(puller, nil) 195 196 wgChain = sync.WaitGroup{} 197 wgChain.Add(1) 198 mockChainCreator.SwitchFollowerToChainCalls(func(_ string) { wgChain.Done() }) 199 200 wgChain = sync.WaitGroup{} 201 wgChain.Add(1) 202 } 203 204 t.Run("zero until join block, member", func(t *testing.T) { 205 setup() 206 mockClusterConsenter.IsChannelMemberCalls(amIReallyInChannel) 207 208 chain, err := follower.NewChain(ledgerResources, mockClusterConsenter, joinBlockAppRaft, options, pullerFactory, mockChainCreator, cryptoProvider, mockChannelParticipationMetricsReporter) 209 require.NoError(t, err) 210 211 consensusRelation, status := chain.StatusReport() 212 require.Equal(t, types.ConsensusRelationConsenter, consensusRelation) 213 require.Equal(t, types.StatusOnBoarding, status) 214 215 require.NotPanics(t, chain.Start) 216 wgChain.Wait() 217 require.NotPanics(t, chain.Halt) 218 require.False(t, chain.IsRunning()) 219 220 consensusRelation, status = chain.StatusReport() 221 require.Equal(t, types.ConsensusRelationConsenter, consensusRelation) 222 require.Equal(t, types.StatusActive, status) 223 224 require.Equal(t, 3, pullerFactory.BlockPullerCallCount()) 225 require.Equal(t, 11, ledgerResources.AppendCallCount()) 226 for i := uint64(0); i <= joinNum; i++ { 227 require.Equal(t, remoteBlockchain.Block(i).Header, localBlockchain.Block(i).Header, "failed block i=%d", i) 228 } 229 require.Equal(t, 1, mockChainCreator.SwitchFollowerToChainCallCount()) 230 }) 231 t.Run("existing half chain until join block, member", func(t *testing.T) { 232 setup() 233 mockClusterConsenter.IsChannelMemberCalls(amIReallyInChannel) 234 localBlockchain.fill(joinNum / 2) // A gap between the ledger and the join block 235 require.True(t, joinBlockAppRaft.Header.Number > ledgerResources.Height()) 236 require.True(t, ledgerResources.Height() > 0) 237 238 chain, err := follower.NewChain(ledgerResources, mockClusterConsenter, joinBlockAppRaft, options, pullerFactory, mockChainCreator, cryptoProvider, mockChannelParticipationMetricsReporter) 239 require.NoError(t, err) 240 241 consensusRelation, status := chain.StatusReport() 242 require.Equal(t, types.ConsensusRelationConsenter, consensusRelation) 243 require.Equal(t, types.StatusOnBoarding, status) 244 245 require.Equal(t, 1, mockChannelParticipationMetricsReporter.ReportConsensusRelationAndStatusMetricsCallCount()) 246 channel, relation, status := mockChannelParticipationMetricsReporter.ReportConsensusRelationAndStatusMetricsArgsForCall(0) 247 require.Equal(t, "my-channel", channel) 248 require.Equal(t, types.ConsensusRelationConsenter, relation) 249 require.Equal(t, types.StatusOnBoarding, status) 250 251 require.NotPanics(t, chain.Start) 252 wgChain.Wait() 253 require.NotPanics(t, chain.Halt) 254 require.False(t, chain.IsRunning()) 255 256 consensusRelation, status = chain.StatusReport() 257 require.Equal(t, types.ConsensusRelationConsenter, consensusRelation) 258 require.Equal(t, types.StatusActive, status) 259 260 require.Equal(t, 3, pullerFactory.BlockPullerCallCount()) 261 require.Equal(t, 6, ledgerResources.AppendCallCount()) 262 for i := uint64(0); i <= joinNum; i++ { 263 require.Equal(t, remoteBlockchain.Block(i).Header, localBlockchain.Block(i).Header, "failed block i=%d", i) 264 } 265 require.Equal(t, 1, mockChainCreator.SwitchFollowerToChainCallCount()) 266 267 require.Equal(t, 3, mockChannelParticipationMetricsReporter.ReportConsensusRelationAndStatusMetricsCallCount()) 268 channel, relation, status = mockChannelParticipationMetricsReporter.ReportConsensusRelationAndStatusMetricsArgsForCall(2) 269 require.Equal(t, "my-channel", channel) 270 require.Equal(t, types.ConsensusRelationConsenter, relation) 271 require.Equal(t, types.StatusActive, status) 272 }) 273 t.Run("no need to pull, member", func(t *testing.T) { 274 setup() 275 mockClusterConsenter.IsChannelMemberCalls(amIReallyInChannel) 276 localBlockchain.fill(joinNum) 277 localBlockchain.appendConfig(1) // No gap between the ledger and the join block 278 require.True(t, joinBlockAppRaft.Header.Number < ledgerResources.Height()) 279 280 chain, err := follower.NewChain(ledgerResources, mockClusterConsenter, joinBlockAppRaft, options, pullerFactory, mockChainCreator, cryptoProvider, mockChannelParticipationMetricsReporter) 281 require.NoError(t, err) 282 283 consensusRelation, status := chain.StatusReport() 284 require.Equal(t, types.ConsensusRelationConsenter, consensusRelation) 285 require.Equal(t, types.StatusActive, status) 286 287 require.NotPanics(t, chain.Start) 288 wgChain.Wait() 289 require.NotPanics(t, chain.Halt) 290 require.False(t, chain.IsRunning()) 291 292 consensusRelation, status = chain.StatusReport() 293 require.Equal(t, types.ConsensusRelationConsenter, consensusRelation) 294 require.Equal(t, types.StatusActive, status) 295 296 require.Equal(t, 2, pullerFactory.BlockPullerCallCount()) 297 require.Equal(t, 0, ledgerResources.AppendCallCount()) 298 299 require.Equal(t, 1, mockChainCreator.SwitchFollowerToChainCallCount()) 300 }) 301 t.Run("overcome pull failures, member", func(t *testing.T) { 302 setup() 303 mockClusterConsenter.IsChannelMemberCalls(amIReallyInChannel) 304 305 failPull := 10 306 pullerFactory = &mocks.BlockPullerFactory{} 307 puller = &mocks.ChannelPuller{} 308 puller.PullBlockCalls(func(i uint64) *common.Block { 309 if i%2 == 1 && failPull > 0 { 310 failPull = failPull - 1 311 return nil 312 } 313 failPull = 10 314 return remoteBlockchain.Block(i) 315 }) 316 pullerFactory.BlockPullerReturns(puller, nil) 317 options.TimeAfter = timeAfterCount.After 318 319 chain, err := follower.NewChain(ledgerResources, mockClusterConsenter, joinBlockAppRaft, options, pullerFactory, mockChainCreator, cryptoProvider, mockChannelParticipationMetricsReporter) 320 require.NoError(t, err) 321 322 consensusRelation, status := chain.StatusReport() 323 require.Equal(t, types.ConsensusRelationConsenter, consensusRelation) 324 require.Equal(t, types.StatusOnBoarding, status) 325 326 require.NotPanics(t, chain.Start) 327 wgChain.Wait() 328 require.NotPanics(t, chain.Halt) 329 require.False(t, chain.IsRunning()) 330 331 consensusRelation, status = chain.StatusReport() 332 require.Equal(t, types.ConsensusRelationConsenter, consensusRelation) 333 require.Equal(t, types.StatusActive, status) 334 335 require.Equal(t, 3, pullerFactory.BlockPullerCallCount()) 336 require.Equal(t, 11, ledgerResources.AppendCallCount()) 337 for i := uint64(0); i <= joinNum; i++ { 338 require.Equal(t, remoteBlockchain.Block(i).Header, localBlockchain.Block(i).Header, "failed block i=%d", i) 339 } 340 require.Equal(t, 1, mockChainCreator.SwitchFollowerToChainCallCount()) 341 require.Equal(t, 50, timeAfterCount.AfterCallCount()) 342 require.Equal(t, int64(5000), atomic.LoadInt64(&maxDelay)) 343 }) 344 } 345 346 func TestFollowerPullAfterJoin(t *testing.T) { 347 joinNum := uint64(10) 348 var wgChain sync.WaitGroup 349 350 setup := func() { 351 globalSetup(t) 352 remoteBlockchain.fill(joinNum + 11) 353 localBlockchain.fill(joinNum + 1) 354 355 mockClusterConsenter.IsChannelMemberCalls(amIReallyInChannel) 356 357 puller.PullBlockCalls(func(i uint64) *common.Block { return remoteBlockchain.Block(i) }) 358 puller.HeightsByEndpointsCalls( 359 func() (map[string]uint64, error) { 360 m := make(map[string]uint64) 361 m["good-node"] = remoteBlockchain.Height() 362 m["lazy-node"] = remoteBlockchain.Height() - 2 363 return m, nil 364 }, 365 ) 366 pullerFactory.BlockPullerReturns(puller, nil) 367 368 wgChain = sync.WaitGroup{} 369 wgChain.Add(1) 370 } 371 372 t.Run("No config in the middle", func(t *testing.T) { 373 setup() 374 ledgerResources.AppendCalls(func(block *common.Block) error { 375 _ = localBlockchain.Append(block) 376 // Stop when we catch-up with latest 377 if remoteBlockchain.Height() == localBlockchain.Height() { 378 wgChain.Done() 379 } 380 return nil 381 }) 382 require.Equal(t, joinNum+1, localBlockchain.Height()) 383 384 chain, err := follower.NewChain(ledgerResources, mockClusterConsenter, nil, options, pullerFactory, mockChainCreator, cryptoProvider, mockChannelParticipationMetricsReporter) 385 require.NoError(t, err) 386 387 consensusRelation, status := chain.StatusReport() 388 require.Equal(t, types.ConsensusRelationFollower, consensusRelation) 389 require.Equal(t, types.StatusActive, status) 390 391 require.NotPanics(t, chain.Start) 392 wgChain.Wait() 393 require.NotPanics(t, chain.Halt) 394 require.False(t, chain.IsRunning()) 395 396 consensusRelation, status = chain.StatusReport() 397 require.Equal(t, types.ConsensusRelationFollower, consensusRelation) 398 require.Equal(t, types.StatusActive, status) 399 400 require.Equal(t, 1, pullerFactory.BlockPullerCallCount()) 401 require.Equal(t, 10, ledgerResources.AppendCallCount()) 402 require.Equal(t, uint64(21), localBlockchain.Height()) 403 for i := uint64(0); i < localBlockchain.Height(); i++ { 404 require.Equal(t, remoteBlockchain.Block(i).Header, localBlockchain.Block(i).Header, "failed block i=%d", i) 405 } 406 require.Equal(t, 0, mockChainCreator.SwitchFollowerToChainCallCount()) 407 }) 408 t.Run("No config in the middle, latest height increasing", func(t *testing.T) { 409 setup() 410 ledgerResources.AppendCalls(func(block *common.Block) error { 411 _ = localBlockchain.Append(block) 412 if remoteBlockchain.Height() == localBlockchain.Height() { 413 if remoteBlockchain.Height() < 50 { 414 remoteBlockchain.fill(10) 415 } else { 416 // Stop when we catch-up with latest 417 wgChain.Done() 418 } 419 } 420 return nil 421 }) 422 require.Equal(t, joinNum+1, localBlockchain.Height()) 423 424 chain, err := follower.NewChain(ledgerResources, mockClusterConsenter, nil, options, pullerFactory, mockChainCreator, cryptoProvider, mockChannelParticipationMetricsReporter) 425 426 require.NoError(t, err) 427 428 consensusRelation, status := chain.StatusReport() 429 require.Equal(t, types.ConsensusRelationFollower, consensusRelation) 430 require.Equal(t, types.StatusActive, status) 431 432 require.NotPanics(t, chain.Start) 433 wgChain.Wait() 434 require.NotPanics(t, chain.Halt) 435 require.False(t, chain.IsRunning()) 436 437 consensusRelation, status = chain.StatusReport() 438 require.Equal(t, types.ConsensusRelationFollower, consensusRelation) 439 require.Equal(t, types.StatusActive, status) 440 441 require.Equal(t, 1, pullerFactory.BlockPullerCallCount()) 442 require.Equal(t, 40, ledgerResources.AppendCallCount()) 443 require.Equal(t, uint64(51), localBlockchain.Height()) 444 for i := uint64(0); i < localBlockchain.Height(); i++ { 445 require.Equal(t, remoteBlockchain.Block(i).Header, localBlockchain.Block(i).Header, "failed block i=%d", i) 446 } 447 require.Equal(t, 0, mockChainCreator.SwitchFollowerToChainCallCount()) 448 }) 449 t.Run("No config in the middle, latest height increasing slowly", func(t *testing.T) { 450 setup() 451 452 var hCount int 453 puller.HeightsByEndpointsCalls( 454 func() (map[string]uint64, error) { 455 m := make(map[string]uint64) 456 if hCount%10 == 0 { 457 m["good-node"] = remoteBlockchain.Height() 458 m["lazy-node"] = remoteBlockchain.Height() - 2 459 } else { 460 m["equal-to-local-node"] = localBlockchain.Height() 461 } 462 hCount++ 463 return m, nil 464 }, 465 ) 466 ledgerResources.AppendCalls(func(block *common.Block) error { 467 _ = localBlockchain.Append(block) 468 if remoteBlockchain.Height() == localBlockchain.Height() { 469 if remoteBlockchain.Height() < 50 { 470 remoteBlockchain.fill(10) 471 } else { 472 // Stop when we catch-up with latest 473 wgChain.Done() 474 } 475 } 476 return nil 477 }) 478 require.Equal(t, joinNum+1, localBlockchain.Height()) 479 options.TimeAfter = timeAfterCount.After 480 chain, err := follower.NewChain(ledgerResources, mockClusterConsenter, nil, options, pullerFactory, mockChainCreator, cryptoProvider, mockChannelParticipationMetricsReporter) 481 482 require.NoError(t, err) 483 484 require.Equal(t, 0, timeAfterCount.AfterCallCount()) 485 486 consensusRelation, status := chain.StatusReport() 487 require.Equal(t, types.ConsensusRelationFollower, consensusRelation) 488 require.Equal(t, types.StatusActive, status) 489 490 require.NotPanics(t, chain.Start) 491 wgChain.Wait() 492 require.NotPanics(t, chain.Halt) 493 require.False(t, chain.IsRunning()) 494 495 consensusRelation, status = chain.StatusReport() 496 require.Equal(t, types.ConsensusRelationFollower, consensusRelation) 497 require.Equal(t, types.StatusActive, status) 498 499 require.Equal(t, 1, pullerFactory.BlockPullerCallCount()) 500 require.Equal(t, 40, ledgerResources.AppendCallCount()) 501 require.Equal(t, uint64(51), localBlockchain.Height()) 502 for i := uint64(0); i < localBlockchain.Height(); i++ { 503 require.Equal(t, remoteBlockchain.Block(i).Header, localBlockchain.Block(i).Header, "failed block i=%d", i) 504 } 505 require.Equal(t, 0, mockChainCreator.SwitchFollowerToChainCallCount()) 506 require.True(t, puller.HeightsByEndpointsCallCount() >= 30) 507 require.Equal(t, int64(10000), atomic.LoadInt64(&maxDelay)) 508 }) 509 t.Run("Configs in the middle, latest height increasing", func(t *testing.T) { 510 setup() 511 ledgerResources.AppendCalls(func(block *common.Block) error { 512 _ = localBlockchain.Append(block) 513 514 if remoteBlockchain.Height() == localBlockchain.Height() { 515 if remoteBlockchain.Height() < 50 { 516 remoteBlockchain.fill(9) 517 // Each config appended will trigger the creation of a new puller in the next round 518 remoteBlockchain.appendConfig(0) 519 } else { 520 remoteBlockchain.fill(9) 521 // This will trigger the creation of a new chain 522 remoteBlockchain.appendConfig(1) 523 } 524 } 525 return nil 526 }) 527 mockChainCreator.SwitchFollowerToChainCalls(func(_ string) { wgChain.Done() }) // Stop when a new chain is created 528 require.Equal(t, joinNum+1, localBlockchain.Height()) 529 530 chain, err := follower.NewChain(ledgerResources, mockClusterConsenter, nil, options, pullerFactory, mockChainCreator, cryptoProvider, mockChannelParticipationMetricsReporter) 531 require.NoError(t, err) 532 533 consensusRelation, status := chain.StatusReport() 534 require.Equal(t, types.ConsensusRelationFollower, consensusRelation) 535 require.Equal(t, types.StatusActive, status) 536 537 require.NotPanics(t, chain.Start) 538 wgChain.Wait() 539 require.NotPanics(t, chain.Halt) 540 require.False(t, chain.IsRunning()) 541 542 consensusRelation, status = chain.StatusReport() 543 require.Equal(t, types.ConsensusRelationConsenter, consensusRelation) 544 require.Equal(t, types.StatusActive, status) 545 546 require.Equal(t, 1, pullerFactory.BlockPullerCallCount(), "after finding a config, block puller is created") 547 require.Equal(t, 50, ledgerResources.AppendCallCount()) 548 require.Equal(t, uint64(61), localBlockchain.Height()) 549 for i := uint64(0); i < localBlockchain.Height(); i++ { 550 require.Equal(t, remoteBlockchain.Block(i).Header, localBlockchain.Block(i).Header, "failed block i=%d", i) 551 } 552 require.Equal(t, 1, mockChainCreator.SwitchFollowerToChainCallCount()) 553 }) 554 t.Run("Overcome puller errors, configs in the middle, latest height increasing", func(t *testing.T) { 555 setup() 556 ledgerResources.AppendCalls(func(block *common.Block) error { 557 _ = localBlockchain.Append(block) 558 559 if remoteBlockchain.Height() == localBlockchain.Height() { 560 if remoteBlockchain.Height() < 50 { 561 remoteBlockchain.fill(9) 562 // Each config appended will trigger the creation of a new puller in the next round 563 remoteBlockchain.appendConfig(0) 564 } else { 565 remoteBlockchain.fill(9) 566 // This will trigger the creation of a new chain 567 remoteBlockchain.appendConfig(1) 568 } 569 } 570 return nil 571 }) 572 mockChainCreator.SwitchFollowerToChainCalls(func(_ string) { wgChain.Done() }) // Stop when a new chain is created 573 require.Equal(t, joinNum+1, localBlockchain.Height()) 574 575 failPull := 10 576 puller.PullBlockCalls(func(i uint64) *common.Block { 577 if i%2 == 1 && failPull > 0 { 578 failPull = failPull - 1 579 return nil 580 } 581 failPull = 10 582 return remoteBlockchain.Block(i) 583 }) 584 585 failHeight := 1 586 puller.HeightsByEndpointsCalls( 587 func() (map[string]uint64, error) { 588 if failHeight > 0 { 589 failHeight = failHeight - 1 590 return nil, errors.New("failed to get heights") 591 } 592 failHeight = 1 593 m := make(map[string]uint64) 594 m["good-node"] = remoteBlockchain.Height() 595 m["lazy-node"] = remoteBlockchain.Height() - 2 596 return m, nil 597 }, 598 ) 599 pullerFactory.BlockPullerReturns(puller, nil) 600 601 options.TimeAfter = timeAfterCount.After 602 603 chain, err := follower.NewChain(ledgerResources, mockClusterConsenter, nil, options, pullerFactory, mockChainCreator, cryptoProvider, mockChannelParticipationMetricsReporter) 604 605 require.NoError(t, err) 606 607 consensusRelation, status := chain.StatusReport() 608 require.Equal(t, types.ConsensusRelationFollower, consensusRelation) 609 require.Equal(t, types.StatusActive, status) 610 611 require.NotPanics(t, chain.Start) 612 wgChain.Wait() 613 require.NotPanics(t, chain.Halt) 614 require.False(t, chain.IsRunning()) 615 616 consensusRelation, status = chain.StatusReport() 617 require.Equal(t, types.ConsensusRelationConsenter, consensusRelation) 618 require.Equal(t, types.StatusActive, status) 619 620 require.Equal(t, 1, pullerFactory.BlockPullerCallCount(), "after finding a config, or error, block puller is created") 621 require.Equal(t, 50, ledgerResources.AppendCallCount()) 622 require.Equal(t, uint64(61), localBlockchain.Height()) 623 for i := uint64(0); i < localBlockchain.Height(); i++ { 624 require.Equal(t, remoteBlockchain.Block(i).Header, localBlockchain.Block(i).Header, "failed block i=%d", i) 625 } 626 627 require.Equal(t, 1, mockChainCreator.SwitchFollowerToChainCallCount()) 628 require.Equal(t, 259, timeAfterCount.AfterCallCount()) 629 require.Equal(t, int64(5000), atomic.LoadInt64(&maxDelay)) 630 }) 631 } 632 633 func TestFollowerPullPastJoin(t *testing.T) { 634 joinNum := uint64(10) 635 var joinBlockAppRaft *common.Block 636 var wgChain sync.WaitGroup 637 638 setup := func() { 639 globalSetup(t) 640 remoteBlockchain.fill(joinNum) 641 remoteBlockchain.appendConfig(0) 642 joinBlockAppRaft = remoteBlockchain.Block(joinNum) 643 require.NotNil(t, joinBlockAppRaft) 644 remoteBlockchain.fill(10) 645 646 mockClusterConsenter.IsChannelMemberCalls(amIReallyInChannel) 647 648 puller.PullBlockCalls(func(i uint64) *common.Block { return remoteBlockchain.Block(i) }) 649 puller.HeightsByEndpointsCalls( 650 func() (map[string]uint64, error) { 651 m := make(map[string]uint64) 652 m["good-node"] = remoteBlockchain.Height() 653 m["lazy-node"] = remoteBlockchain.Height() - 2 654 return m, nil 655 }, 656 ) 657 pullerFactory.BlockPullerReturns(puller, nil) 658 659 wgChain = sync.WaitGroup{} 660 wgChain.Add(1) 661 } 662 663 t.Run("No config in the middle", func(t *testing.T) { 664 setup() 665 ledgerResources.AppendCalls(func(block *common.Block) error { 666 _ = localBlockchain.Append(block) 667 // Stop when we catch-up with latest 668 if remoteBlockchain.Height() == localBlockchain.Height() { 669 wgChain.Done() 670 } 671 return nil 672 }) 673 require.Equal(t, uint64(0), localBlockchain.Height()) 674 675 chain, err := follower.NewChain(ledgerResources, mockClusterConsenter, joinBlockAppRaft, options, pullerFactory, mockChainCreator, cryptoProvider, mockChannelParticipationMetricsReporter) 676 require.NoError(t, err) 677 678 consensusRelation, status := chain.StatusReport() 679 require.Equal(t, types.ConsensusRelationFollower, consensusRelation) 680 require.Equal(t, types.StatusOnBoarding, status) 681 682 require.NotPanics(t, chain.Start) 683 wgChain.Wait() 684 require.NotPanics(t, chain.Halt) 685 require.False(t, chain.IsRunning()) 686 687 consensusRelation, status = chain.StatusReport() 688 require.Equal(t, types.ConsensusRelationFollower, consensusRelation) 689 require.Equal(t, types.StatusActive, status) 690 691 require.Equal(t, 3, pullerFactory.BlockPullerCallCount()) 692 require.Equal(t, 21, ledgerResources.AppendCallCount()) 693 require.Equal(t, uint64(21), localBlockchain.Height()) 694 for i := uint64(0); i < localBlockchain.Height(); i++ { 695 require.Equal(t, remoteBlockchain.Block(i).Header, localBlockchain.Block(i).Header, "failed block i=%d", i) 696 } 697 require.Equal(t, 0, mockChainCreator.SwitchFollowerToChainCallCount()) 698 }) 699 t.Run("No config in the middle, latest height increasing", func(t *testing.T) { 700 setup() 701 ledgerResources.AppendCalls(func(block *common.Block) error { 702 _ = localBlockchain.Append(block) 703 if remoteBlockchain.Height() == localBlockchain.Height() { 704 if remoteBlockchain.Height() < 50 { 705 remoteBlockchain.fill(10) 706 } else { 707 // Stop when we catch-up with latest 708 wgChain.Done() 709 } 710 } 711 return nil 712 }) 713 require.Equal(t, uint64(0), localBlockchain.Height()) 714 715 chain, err := follower.NewChain(ledgerResources, mockClusterConsenter, joinBlockAppRaft, options, pullerFactory, mockChainCreator, cryptoProvider, mockChannelParticipationMetricsReporter) 716 717 require.NoError(t, err) 718 719 consensusRelation, status := chain.StatusReport() 720 require.Equal(t, types.ConsensusRelationFollower, consensusRelation) 721 require.Equal(t, types.StatusOnBoarding, status) 722 723 require.NotPanics(t, chain.Start) 724 wgChain.Wait() 725 require.NotPanics(t, chain.Halt) 726 require.False(t, chain.IsRunning()) 727 728 consensusRelation, status = chain.StatusReport() 729 require.Equal(t, types.ConsensusRelationFollower, consensusRelation) 730 require.Equal(t, types.StatusActive, status) 731 732 require.Equal(t, 3, pullerFactory.BlockPullerCallCount()) 733 require.Equal(t, 51, ledgerResources.AppendCallCount()) 734 require.Equal(t, uint64(51), localBlockchain.Height()) 735 for i := uint64(0); i < localBlockchain.Height(); i++ { 736 require.Equal(t, remoteBlockchain.Block(i).Header, localBlockchain.Block(i).Header, "failed block i=%d", i) 737 } 738 require.Equal(t, 0, mockChainCreator.SwitchFollowerToChainCallCount()) 739 }) 740 t.Run("Configs in the middle, latest height increasing", func(t *testing.T) { 741 setup() 742 localBlockchain.fill(5) 743 localBlockchain.appendConfig(0) 744 ledgerResources.AppendCalls(func(block *common.Block) error { 745 _ = localBlockchain.Append(block) 746 747 if remoteBlockchain.Height() == localBlockchain.Height() { 748 if remoteBlockchain.Height() < 50 { 749 remoteBlockchain.fill(9) 750 // Each config appended will trigger the creation of a new puller in the next round 751 remoteBlockchain.appendConfig(0) 752 } else { 753 remoteBlockchain.fill(9) 754 // This will trigger the creation of a new chain 755 remoteBlockchain.appendConfig(1) 756 } 757 } 758 return nil 759 }) 760 mockChainCreator.SwitchFollowerToChainCalls(func(_ string) { wgChain.Done() }) // Stop when a new chain is created 761 require.Equal(t, uint64(6), localBlockchain.Height()) 762 763 chain, err := follower.NewChain(ledgerResources, mockClusterConsenter, joinBlockAppRaft, options, pullerFactory, mockChainCreator, cryptoProvider, mockChannelParticipationMetricsReporter) 764 require.NoError(t, err) 765 766 consensusRelation, status := chain.StatusReport() 767 require.Equal(t, types.ConsensusRelationFollower, consensusRelation) 768 require.Equal(t, types.StatusOnBoarding, status) 769 770 require.NotPanics(t, chain.Start) 771 wgChain.Wait() 772 require.NotPanics(t, chain.Halt) 773 require.False(t, chain.IsRunning()) 774 775 consensusRelation, status = chain.StatusReport() 776 require.Equal(t, types.ConsensusRelationConsenter, consensusRelation) 777 require.Equal(t, types.StatusActive, status) 778 779 require.Equal(t, 3, pullerFactory.BlockPullerCallCount(), "after finding a config, block puller is created") 780 require.Equal(t, 55, ledgerResources.AppendCallCount()) 781 require.Equal(t, uint64(61), localBlockchain.Height()) 782 for i := uint64(0); i < localBlockchain.Height(); i++ { 783 require.Equal(t, remoteBlockchain.Block(i).Header, localBlockchain.Block(i).Header, "failed block i=%d", i) 784 } 785 require.Equal(t, 1, mockChainCreator.SwitchFollowerToChainCallCount()) 786 }) 787 t.Run("Overcome puller errors, configs in the middle, latest height increasing", func(t *testing.T) { 788 setup() 789 ledgerResources.AppendCalls(func(block *common.Block) error { 790 _ = localBlockchain.Append(block) 791 792 if remoteBlockchain.Height() == localBlockchain.Height() { 793 if remoteBlockchain.Height() < 50 { 794 remoteBlockchain.fill(9) 795 // Each config appended will trigger the creation of a new puller in the next round 796 remoteBlockchain.appendConfig(0) 797 } else { 798 remoteBlockchain.fill(9) 799 // This will trigger the creation of a new chain 800 remoteBlockchain.appendConfig(1) 801 } 802 } 803 return nil 804 }) 805 mockChainCreator.SwitchFollowerToChainCalls(func(_ string) { wgChain.Done() }) // Stop when a new chain is created 806 require.Equal(t, uint64(0), localBlockchain.Height()) 807 808 failPull := 10 809 puller.PullBlockCalls(func(i uint64) *common.Block { 810 if i%2 == 1 && failPull > 0 { 811 failPull = failPull - 1 812 return nil 813 } 814 failPull = 10 815 return remoteBlockchain.Block(i) 816 }) 817 818 failHeight := 1 819 puller.HeightsByEndpointsCalls( 820 func() (map[string]uint64, error) { 821 if failHeight > 0 { 822 failHeight = failHeight - 1 823 return nil, errors.New("failed to get heights") 824 } 825 failHeight = 1 826 m := make(map[string]uint64) 827 m["good-node"] = remoteBlockchain.Height() 828 m["lazy-node"] = remoteBlockchain.Height() - 2 829 return m, nil 830 }, 831 ) 832 pullerFactory.BlockPullerReturns(puller, nil) 833 834 options.TimeAfter = timeAfterCount.After 835 836 chain, err := follower.NewChain(ledgerResources, mockClusterConsenter, joinBlockAppRaft, options, pullerFactory, mockChainCreator, cryptoProvider, mockChannelParticipationMetricsReporter) 837 838 require.NoError(t, err) 839 840 consensusRelation, status := chain.StatusReport() 841 require.Equal(t, types.ConsensusRelationFollower, consensusRelation) 842 require.Equal(t, types.StatusOnBoarding, status) 843 844 require.NotPanics(t, chain.Start) 845 wgChain.Wait() 846 require.NotPanics(t, chain.Halt) 847 require.False(t, chain.IsRunning()) 848 849 consensusRelation, status = chain.StatusReport() 850 require.Equal(t, types.ConsensusRelationConsenter, consensusRelation) 851 require.Equal(t, types.StatusActive, status) 852 853 require.Equal(t, 3, pullerFactory.BlockPullerCallCount(), "after finding a config, or error, block puller is created") 854 require.Equal(t, 61, ledgerResources.AppendCallCount()) 855 require.Equal(t, uint64(61), localBlockchain.Height()) 856 for i := uint64(0); i < localBlockchain.Height(); i++ { 857 require.Equal(t, remoteBlockchain.Block(i).Header, localBlockchain.Block(i).Header, "failed block i=%d", i) 858 } 859 860 require.Equal(t, 1, mockChainCreator.SwitchFollowerToChainCallCount()) 861 require.Equal(t, 309, timeAfterCount.AfterCallCount()) 862 require.Equal(t, int64(5000), atomic.LoadInt64(&maxDelay)) 863 }) 864 } 865 866 type memoryBlockChain struct { 867 lock sync.Mutex 868 chain []*common.Block 869 } 870 871 func (mbc *memoryBlockChain) Append(block *common.Block) error { 872 mbc.lock.Lock() 873 defer mbc.lock.Unlock() 874 875 mbc.chain = append(mbc.chain, block) 876 return nil 877 } 878 879 func (mbc *memoryBlockChain) Height() uint64 { 880 mbc.lock.Lock() 881 defer mbc.lock.Unlock() 882 883 return uint64(len(mbc.chain)) 884 } 885 886 func (mbc *memoryBlockChain) Block(i uint64) *common.Block { 887 mbc.lock.Lock() 888 defer mbc.lock.Unlock() 889 890 if i < uint64(len(mbc.chain)) { 891 return mbc.chain[i] 892 } 893 return nil 894 } 895 896 func (mbc *memoryBlockChain) fill(numBlocks uint64) { 897 mbc.lock.Lock() 898 defer mbc.lock.Unlock() 899 900 height := uint64(len(mbc.chain)) 901 prevHash := []byte{} 902 903 for i := height; i < height+numBlocks; i++ { 904 if i > 0 { 905 prevHash = protoutil.BlockHeaderHash(mbc.chain[i-1].Header) 906 } 907 908 var block *common.Block 909 if i == 0 { 910 block = makeConfigBlock(i, prevHash, 0) 911 } else { 912 block = protoutil.NewBlock(i, prevHash) 913 protoutil.CopyBlockMetadata(mbc.chain[i-1], block) 914 } 915 916 mbc.chain = append(mbc.chain, block) 917 } 918 } 919 920 func (mbc *memoryBlockChain) appendConfig(isMember uint8) { 921 mbc.lock.Lock() 922 defer mbc.lock.Unlock() 923 924 h := uint64(len(mbc.chain)) 925 configBlock := makeConfigBlock(h, protoutil.BlockHeaderHash(mbc.chain[h-1].Header), isMember) 926 mbc.chain = append(mbc.chain, configBlock) 927 } 928 929 func amIReallyInChannel(configBlock *common.Block) (bool, error) { 930 if !protoutil.IsConfigBlock(configBlock) { 931 return false, errors.New("not a config") 932 } 933 env, err := protoutil.ExtractEnvelope(configBlock, 0) 934 if err != nil { 935 return false, err 936 } 937 payload := protoutil.UnmarshalPayloadOrPanic(env.Payload) 938 if len(payload.Data) == 0 { 939 return false, errors.New("empty data") 940 } 941 if payload.Data[0] > 0 { 942 return true, nil 943 } 944 return false, nil 945 } 946 947 func makeConfigBlock(num uint64, prevHash []byte, isMember uint8) *common.Block { 948 block := protoutil.NewBlock(num, prevHash) 949 env := &common.Envelope{ 950 Payload: protoutil.MarshalOrPanic(&common.Payload{ 951 Header: protoutil.MakePayloadHeader( 952 protoutil.MakeChannelHeader(common.HeaderType_CONFIG, 0, "my-chennel", 0), 953 protoutil.MakeSignatureHeader([]byte{}, []byte{}), 954 ), 955 Data: []byte{isMember}, 956 }, 957 ), 958 } 959 block.Data.Data = append(block.Data.Data, protoutil.MarshalOrPanic(env)) 960 protoutil.InitBlockMetadata(block) 961 obm := &common.OrdererBlockMetadata{LastConfig: &common.LastConfig{Index: num}} 962 block.Metadata.Metadata[common.BlockMetadataIndex_SIGNATURES] = protoutil.MarshalOrPanic( 963 &common.Metadata{ 964 Value: protoutil.MarshalOrPanic(obm), 965 }, 966 ) 967 protoutil.InitBlockMetadata(block) 968 969 return block 970 } 971 972 func TestChain_makeConfigBlock(t *testing.T) { 973 joinBlockAppRaft := makeConfigBlock(10, []byte{1, 2, 3, 4}, 0) 974 require.NotNil(t, joinBlockAppRaft) 975 require.True(t, protoutil.IsConfigBlock(joinBlockAppRaft)) 976 require.NotPanics(t, func() { protoutil.GetLastConfigIndexFromBlockOrPanic(joinBlockAppRaft) }) 977 require.Equal(t, uint64(10), protoutil.GetLastConfigIndexFromBlockOrPanic(joinBlockAppRaft)) 978 require.NotPanics(t, func() { amIReallyInChannel(joinBlockAppRaft) }) 979 isMem, err := amIReallyInChannel(joinBlockAppRaft) 980 require.NoError(t, err) 981 require.False(t, isMem) 982 joinBlockAppRaft = makeConfigBlock(11, []byte{1, 2, 3, 4}, 1) 983 isMem, err = amIReallyInChannel(joinBlockAppRaft) 984 require.NoError(t, err) 985 require.True(t, isMem) 986 isMem, err = amIReallyInChannel(protoutil.NewBlock(10, []byte{1, 2, 3, 4})) 987 require.EqualError(t, err, "not a config") 988 require.False(t, isMem) 989 } 990 991 func setLastConfigIndexInBlock(block *common.Block, lastConfigIndex uint64) error { 992 ordererBlockMetadata := &common.OrdererBlockMetadata{ 993 LastConfig: &common.LastConfig{ 994 Index: lastConfigIndex, 995 }, 996 } 997 obmBytes, err := proto.Marshal(ordererBlockMetadata) 998 if err != nil { 999 return err 1000 } 1001 metadata := &common.Metadata{ 1002 Value: obmBytes, 1003 } 1004 metadataBytes, err := proto.Marshal(metadata) 1005 if err != nil { 1006 return err 1007 } 1008 block.Metadata.Metadata[common.BlockMetadataIndex_SIGNATURES] = metadataBytes 1009 return nil 1010 }