github.com/hechain20/hechain@v0.0.0-20220316014945-b544036ba106/orderer/common/follower/follower_chain.go (about) 1 /* 2 Copyright hechain. 2017 All Rights Reserved. 3 4 SPDX-License-Identifier: Apache-2.0 5 */ 6 7 package follower 8 9 import ( 10 "bytes" 11 "sync" 12 "time" 13 14 "github.com/hechain20/hechain/bccsp" 15 "github.com/hechain20/hechain/common/flogging" 16 "github.com/hechain20/hechain/orderer/common/cluster" 17 "github.com/hechain20/hechain/orderer/common/types" 18 "github.com/hechain20/hechain/orderer/consensus" 19 "github.com/hechain20/hechain/protoutil" 20 "github.com/hyperledger/fabric-protos-go/common" 21 "github.com/pkg/errors" 22 ) 23 24 // ErrChainStopped is returned when the chain is stopped during execution. 25 var ErrChainStopped = errors.New("chain stopped") 26 27 //go:generate counterfeiter -o mocks/ledger_resources.go -fake-name LedgerResources . LedgerResources 28 29 // LedgerResources defines some of the interfaces of ledger & config resources needed by the follower.Chain. 30 type LedgerResources interface { 31 // ChannelID The channel ID. 32 ChannelID() string 33 34 // Block returns a block with the given number, 35 // or nil if such a block doesn't exist. 36 Block(number uint64) *common.Block 37 38 // Height returns the number of blocks in the chain this channel is associated with. 39 Height() uint64 40 41 // Append appends a new block to the ledger in its raw form. 42 Append(block *common.Block) error 43 } 44 45 // TimeAfter has the signature of time.After and allows tests to provide an alternative implementation to it. 46 type TimeAfter func(d time.Duration) <-chan time.Time 47 48 //go:generate counterfeiter -o mocks/block_puller_factory.go -fake-name BlockPullerFactory . BlockPullerFactory 49 50 // BlockPullerFactory creates a ChannelPuller on demand, and exposes a method to update the a block signature verifier 51 // linked to that ChannelPuller. 52 type BlockPullerFactory interface { 53 BlockPuller(configBlock *common.Block, stopChannel chan struct{}) (ChannelPuller, error) 54 UpdateVerifierFromConfigBlock(configBlock *common.Block) error 55 } 56 57 //go:generate counterfeiter -o mocks/chain_creator.go -fake-name ChainCreator . ChainCreator 58 59 // ChainCreator defines a function that creates a new consensus.Chain for this channel, to replace the current 60 // follower.Chain. This interface is meant to be implemented by the multichannel.Registrar. 61 type ChainCreator interface { 62 SwitchFollowerToChain(chainName string) 63 } 64 65 //go:generate counterfeiter -o mocks/channel_participation_metrics_reporter.go -fake-name ChannelParticipationMetricsReporter . ChannelParticipationMetricsReporter 66 67 type ChannelParticipationMetricsReporter interface { 68 ReportConsensusRelationAndStatusMetrics(channelID string, relation types.ConsensusRelation, status types.Status) 69 } 70 71 // Chain implements a component that allows the orderer to follow a specific channel when is not a cluster member, 72 // that is, be a "follower" of the cluster. It also allows the orderer to perform "onboarding" for 73 // channels it is joining as a member, with a join-block. 74 // 75 // When an orderer is following a channel, it means that the current orderer is not a member of the consenters set 76 // of the channel, and is only pulling blocks from other orderers. In this mode, the follower is inspecting config 77 // blocks as they are pulled and if it discovers that it was introduced into the consenters set, it will trigger the 78 // creation of a regular etcdraft.Chain, that is, turn into a "member" of the cluster. 79 // 80 // A follower is also used to onboard a channel when joining as a member with a join-block that has number >0. In this 81 // mode the follower will pull blocks up until join-block.number, and then will trigger the creation of a regular 82 // etcdraft.Chain. 83 // 84 // The follower is started in one of two ways: 1) following an API Join request with a join-block that has 85 // block number >0, or 2) when the orderer was a cluster member (i.e. was running a etcdraft.Chain) and was removed 86 // from the consenters set. 87 // 88 // The follower is in status "onboarding" when it pulls blocks below the join-block number, or "active" when it 89 // pulls blocks equal or above the join-block number. 90 // 91 // The follower return clusterRelation "member" when the join-block indicates the orderer is in the consenters set, 92 // i.e. the follower is performing onboarding for an etcdraft.Chain. Otherwise, the follower return clusterRelation 93 // "follower". 94 type Chain struct { 95 mutex sync.Mutex // Protects the start/stop flags & channels, consensusRelation & status. All the rest are immutable or accessed only by the go-routine. 96 started bool // Start once. 97 stopped bool // Stop once. 98 stopChan chan struct{} // A 'closer' signals the go-routine to stop by closing this channel. 99 doneChan chan struct{} // The go-routine signals the 'closer' that it is done by closing this channel. 100 consensusRelation types.ConsensusRelation 101 status types.Status 102 103 ledgerResources LedgerResources // ledger & config resources 104 clusterConsenter consensus.ClusterConsenter // detects whether a block indicates channel membership 105 options Options 106 logger *flogging.FabricLogger 107 timeAfter TimeAfter // time.After by default, or an alternative from Options. 108 109 joinBlock *common.Block // The join-block the follower was started with. 110 lastConfig *common.Block // The last config block from the ledger. Accessed only by the go-routine. 111 firstHeight uint64 // The first ledger height 112 113 // Creates a block puller on demand, and allows the update of the block signature verifier with each incoming 114 // config block. 115 blockPullerFactory BlockPullerFactory 116 // A block puller instance, created either from the join-block or last-config-block. When pulling blocks using 117 // the last-config-block, the endpoints are updated with each incoming config block. 118 blockPuller ChannelPuller 119 120 // Creates a new consensus.Chain for this channel, to replace the current follower.Chain. 121 chainCreator ChainCreator 122 123 cryptoProvider bccsp.BCCSP // Cryptographic services 124 125 channelParticipationMetricsReporter ChannelParticipationMetricsReporter 126 } 127 128 // NewChain constructs a follower.Chain object. 129 func NewChain( 130 ledgerResources LedgerResources, 131 clusterConsenter consensus.ClusterConsenter, 132 joinBlock *common.Block, 133 options Options, 134 blockPullerFactory BlockPullerFactory, 135 chainCreator ChainCreator, 136 cryptoProvider bccsp.BCCSP, 137 channelParticipationMetricsReporter ChannelParticipationMetricsReporter, 138 ) (*Chain, error) { 139 options.applyDefaults() 140 141 chain := &Chain{ 142 stopChan: make(chan struct{}), 143 doneChan: make(chan struct{}), 144 consensusRelation: types.ConsensusRelationFollower, 145 status: types.StatusOnBoarding, 146 ledgerResources: ledgerResources, 147 clusterConsenter: clusterConsenter, 148 joinBlock: joinBlock, 149 firstHeight: ledgerResources.Height(), 150 options: options, 151 logger: options.Logger.With("channel", ledgerResources.ChannelID()), 152 timeAfter: options.TimeAfter, 153 blockPullerFactory: blockPullerFactory, 154 chainCreator: chainCreator, 155 cryptoProvider: cryptoProvider, 156 channelParticipationMetricsReporter: channelParticipationMetricsReporter, 157 } 158 159 if ledgerResources.Height() > 0 { 160 if err := chain.loadLastConfig(); err != nil { 161 return nil, err 162 } 163 if err := blockPullerFactory.UpdateVerifierFromConfigBlock(chain.lastConfig); err != nil { 164 return nil, err 165 } 166 } 167 168 if joinBlock == nil { 169 chain.status = types.StatusActive 170 if isMem, _ := chain.clusterConsenter.IsChannelMember(chain.lastConfig); isMem { 171 chain.consensusRelation = types.ConsensusRelationConsenter 172 } 173 174 chain.logger.Infof("Created with a nil join-block, ledger height: %d", chain.firstHeight) 175 } else { 176 if joinBlock.Header == nil { 177 return nil, errors.New("block header is nil") 178 } 179 if joinBlock.Data == nil { 180 return nil, errors.New("block data is nil") 181 } 182 183 // Check the block puller creation function once before we start the follower. This ensures we can extract 184 // the endpoints from the join-block. 185 puller, err := blockPullerFactory.BlockPuller(joinBlock, nil) 186 if err != nil { 187 return nil, errors.WithMessage(err, "error creating a block puller from join-block") 188 } 189 puller.Close() 190 191 if chain.joinBlock.Header.Number < chain.ledgerResources.Height() { 192 chain.status = types.StatusActive 193 } 194 if isMem, _ := chain.clusterConsenter.IsChannelMember(chain.joinBlock); isMem { 195 chain.consensusRelation = types.ConsensusRelationConsenter 196 } 197 198 chain.logger.Infof("Created with join-block number: %d, ledger height: %d", joinBlock.Header.Number, chain.firstHeight) 199 } 200 201 chain.logger.Debugf("Options are: %v", chain.options) 202 203 chain.channelParticipationMetricsReporter.ReportConsensusRelationAndStatusMetrics(ledgerResources.ChannelID(), chain.consensusRelation, chain.status) 204 205 return chain, nil 206 } 207 208 func (c *Chain) Start() { 209 c.mutex.Lock() 210 defer c.mutex.Unlock() 211 212 if c.started || c.stopped { 213 c.logger.Debugf("Not starting because: started=%v, stopped=%v", c.started, c.stopped) 214 return 215 } 216 217 c.started = true 218 219 go c.run() 220 221 c.logger.Info("Started") 222 } 223 224 // Halt signals the Chain to stop and waits for the internal go-routine to exit. 225 func (c *Chain) Halt() { 226 c.halt() 227 <-c.doneChan 228 } 229 230 func (c *Chain) halt() { 231 c.mutex.Lock() 232 defer c.mutex.Unlock() 233 234 if c.stopped { 235 c.logger.Debug("Already stopped") 236 return 237 } 238 c.stopped = true 239 close(c.stopChan) 240 c.logger.Info("Stopped") 241 } 242 243 // StatusReport returns the ConsensusRelation & Status. 244 func (c *Chain) StatusReport() (types.ConsensusRelation, types.Status) { 245 c.mutex.Lock() 246 defer c.mutex.Unlock() 247 248 return c.consensusRelation, c.status 249 } 250 251 func (c *Chain) setStatus(status types.Status) { 252 c.mutex.Lock() 253 defer c.mutex.Unlock() 254 255 c.status = status 256 257 c.channelParticipationMetricsReporter.ReportConsensusRelationAndStatusMetrics(c.ledgerResources.ChannelID(), c.consensusRelation, c.status) 258 } 259 260 func (c *Chain) setConsensusRelation(clusterRelation types.ConsensusRelation) { 261 c.mutex.Lock() 262 defer c.mutex.Unlock() 263 264 c.consensusRelation = clusterRelation 265 266 c.channelParticipationMetricsReporter.ReportConsensusRelationAndStatusMetrics(c.ledgerResources.ChannelID(), c.consensusRelation, c.status) 267 } 268 269 func (c *Chain) Height() uint64 { 270 return c.ledgerResources.Height() 271 } 272 273 func (c *Chain) IsRunning() bool { 274 c.mutex.Lock() 275 defer c.mutex.Unlock() 276 277 if c.started { 278 select { 279 case <-c.doneChan: 280 return false 281 default: 282 return true 283 } 284 } 285 286 return false 287 } 288 289 func (c *Chain) run() { 290 c.logger.Debug("The follower.Chain puller goroutine is starting") 291 292 defer func() { 293 close(c.doneChan) 294 c.logger.Debug("The follower.Chain puller goroutine is exiting") 295 }() 296 297 if err := c.pull(); err != nil { 298 c.logger.Warnf("Pull failed, error: %s", err) 299 // TODO set the status to StatusError (see FAB-18106) 300 } 301 } 302 303 func (c *Chain) increaseRetryInterval(retryInterval *time.Duration, upperLimit time.Duration) { 304 if *retryInterval == upperLimit { 305 return 306 } 307 // assuming this will never overflow int64, as upperLimit cannot be over MaxInt64/2 308 *retryInterval = time.Duration(1.5 * float64(*retryInterval)) 309 if *retryInterval > upperLimit { 310 *retryInterval = upperLimit 311 } 312 c.logger.Debugf("retry interval increased to: %v", *retryInterval) 313 } 314 315 func (c *Chain) resetRetryInterval(retryInterval *time.Duration, lowerLimit time.Duration) { 316 if *retryInterval == lowerLimit { 317 return 318 } 319 *retryInterval = lowerLimit 320 c.logger.Debugf("retry interval reset to: %v", *retryInterval) 321 } 322 323 func (c *Chain) decreaseRetryInterval(retryInterval *time.Duration, lowerLimit time.Duration) { 324 if *retryInterval == lowerLimit { 325 return 326 } 327 328 *retryInterval = *retryInterval - lowerLimit 329 if *retryInterval < lowerLimit { 330 *retryInterval = lowerLimit 331 } 332 c.logger.Debugf("retry interval decreased to: %v", *retryInterval) 333 } 334 335 // pull blocks from other orderers until a config block indicates the orderer has become a member of the cluster. 336 // When the follower.Chain's job is done, this method halts, triggers the creation of a new consensus.Chain, 337 // and returns nil. The method returns an error only when the chain is stopped or due to unrecoverable errors. 338 func (c *Chain) pull() error { 339 var err error 340 if c.joinBlock != nil { 341 err = c.pullUpToJoin() 342 if err != nil { 343 return errors.WithMessage(err, "failed to pull up to join block") 344 } 345 c.logger.Info("Onboarding finished successfully, pulled blocks up to join-block") 346 } 347 348 err = c.pullAfterJoin() 349 if err != nil { 350 return errors.WithMessage(err, "failed to pull after join block") 351 } 352 353 // Trigger creation of a new consensus.Chain. 354 c.logger.Info("Block pulling finished successfully, going to switch from follower to a consensus.Chain") 355 c.halt() 356 c.chainCreator.SwitchFollowerToChain(c.ledgerResources.ChannelID()) 357 358 return nil 359 } 360 361 // pullUpToJoin pulls blocks up to the join-block height without inspecting membership on fetched config blocks. 362 // It checks whether the chain was stopped between blocks. 363 func (c *Chain) pullUpToJoin() error { 364 targetHeight := c.joinBlock.Header.Number + 1 365 if c.ledgerResources.Height() >= targetHeight { 366 c.logger.Infof("Target height according to join block (%d) is <= to our ledger height (%d), no need to pull up to join block", 367 targetHeight, c.ledgerResources.Height()) 368 return nil 369 } 370 371 var err error 372 // Block puller created with endpoints from the join-block. 373 c.blockPuller, err = c.blockPullerFactory.BlockPuller(c.joinBlock, c.stopChan) 374 if err != nil { // This should never happen since we check the join-block before we start. 375 return errors.WithMessagef(err, "error creating block puller") 376 } 377 defer c.blockPuller.Close() 378 // Since we created the block-puller with the join-block, do not update the endpoints from the 379 // config blocks that precede it. 380 err = c.pullUntilLatestWithRetry(targetHeight, false) 381 if err != nil { 382 return err 383 } 384 385 c.logger.Infof("Pulled blocks from %d until %d", c.firstHeight, targetHeight-1) 386 return nil 387 } 388 389 // pullAfterJoin pulls blocks continuously, inspecting the fetched config 390 // blocks for membership. On every config block, it renews the BlockPuller, 391 // to take in the new configuration. It will exit with 'nil' if it detects 392 // a config block that indicates the orderer is a member of the cluster. It 393 // checks whether the chain was stopped between blocks. 394 func (c *Chain) pullAfterJoin() error { 395 c.setStatus(types.StatusActive) 396 397 err := c.loadLastConfig() 398 if err != nil { 399 return errors.WithMessage(err, "failed to load last config block") 400 } 401 402 c.blockPuller, err = c.blockPullerFactory.BlockPuller(c.lastConfig, c.stopChan) 403 if err != nil { 404 return errors.WithMessage(err, "error creating block puller") 405 } 406 defer c.blockPuller.Close() 407 408 heightPollInterval := c.options.HeightPollMinInterval 409 for { 410 // Check membership 411 isMember, errMem := c.clusterConsenter.IsChannelMember(c.lastConfig) 412 if errMem != nil { 413 return errors.WithMessage(err, "failed to determine channel membership from last config") 414 } 415 if isMember { 416 c.setConsensusRelation(types.ConsensusRelationConsenter) 417 return nil 418 } 419 420 // Poll for latest network height to advance beyond ledger height. 421 var latestNetworkHeight uint64 422 heightPollLoop: 423 for { 424 endpoint, networkHeight, errHeight := cluster.LatestHeightAndEndpoint(c.blockPuller) 425 if errHeight != nil { 426 c.logger.Errorf("Failed to get latest height and endpoint, error: %s", errHeight) 427 } else { 428 c.logger.Debugf("Orderer endpoint %s has the biggest ledger height: %d", endpoint, networkHeight) 429 } 430 431 if networkHeight > c.ledgerResources.Height() { 432 // On success, slowly decrease the polling interval 433 c.decreaseRetryInterval(&heightPollInterval, c.options.HeightPollMinInterval) 434 latestNetworkHeight = networkHeight 435 break heightPollLoop 436 } 437 438 c.logger.Debugf("My height: %d, latest network height: %d; going to wait %v for latest height to grow", 439 c.ledgerResources.Height(), networkHeight, heightPollInterval) 440 select { 441 case <-c.stopChan: 442 c.logger.Debug("Received a stop signal") 443 return ErrChainStopped 444 case <-c.timeAfter(heightPollInterval): 445 // Exponential back-off, to avoid calling LatestHeightAndEndpoint too often. 446 c.increaseRetryInterval(&heightPollInterval, c.options.HeightPollMaxInterval) 447 } 448 } 449 450 // Pull to latest height or chain stop signal 451 err = c.pullUntilLatestWithRetry(latestNetworkHeight, true) 452 if err != nil { 453 return err 454 } 455 } 456 } 457 458 // pullUntilLatestWithRetry is given a target-height and exits without an error when it reaches that target. 459 // It return with an error only if the chain is stopped. 460 // On internal pull errors it employs exponential back-off and retries. 461 // When parameter updateEndpoints is true, the block-puller's endpoints are updated with every incoming config. 462 func (c *Chain) pullUntilLatestWithRetry(latestNetworkHeight uint64, updateEndpoints bool) error { 463 retryInterval := c.options.PullRetryMinInterval 464 for { 465 numPulled, errPull := c.pullUntilTarget(latestNetworkHeight, updateEndpoints) 466 if numPulled > 0 { 467 c.resetRetryInterval(&retryInterval, c.options.PullRetryMinInterval) // On any progress, reset retry interval. 468 } 469 if errPull == nil { 470 c.logger.Debugf("Pulled %d blocks until latest network height: %d", numPulled, latestNetworkHeight) 471 break 472 } 473 474 c.logger.Debugf("Error while trying to pull to latest height: %d; going to try again in %v", 475 latestNetworkHeight, retryInterval) 476 select { 477 case <-c.stopChan: 478 c.logger.Debug("Received a stop signal") 479 return ErrChainStopped 480 case <-c.timeAfter(retryInterval): 481 // Exponential back-off on successive errors w/o progress. 482 c.increaseRetryInterval(&retryInterval, c.options.PullRetryMaxInterval) 483 } 484 } 485 486 return nil 487 } 488 489 // pullUntilTarget is given a target-height and exits without an error when it reaches that target. 490 // It may return with an error before the target, always returning the number of blocks pulled. 491 // When parameter updateEndpoints is true, the block-puller's endpoints are updated with every incoming config. 492 // The block-puller-factory which holds the block signature verifier is updated on every incoming config. 493 func (c *Chain) pullUntilTarget(targetHeight uint64, updateEndpoints bool) (uint64, error) { 494 firstBlockToPull := c.ledgerResources.Height() 495 if firstBlockToPull >= targetHeight { 496 c.logger.Debugf("Target height (%d) is <= to our ledger height (%d), skipping pulling", targetHeight, firstBlockToPull) 497 return 0, nil 498 } 499 500 var actualPrevHash []byte 501 // Initialize the actual previous hash 502 if firstBlockToPull > 0 { 503 prevBlock := c.ledgerResources.Block(firstBlockToPull - 1) 504 if prevBlock == nil { 505 return 0, errors.Errorf("cannot retrieve previous block %d", firstBlockToPull-1) 506 } 507 actualPrevHash = protoutil.BlockHeaderHash(prevBlock.Header) 508 } 509 510 // Pull until the latest height 511 for seq := firstBlockToPull; seq < targetHeight; seq++ { 512 n := seq - firstBlockToPull 513 select { 514 case <-c.stopChan: 515 c.logger.Debug("Received a stop signal") 516 return n, ErrChainStopped 517 default: 518 nextBlock := c.blockPuller.PullBlock(seq) 519 if nextBlock == nil { 520 return n, errors.WithMessagef(cluster.ErrRetryCountExhausted, "failed to pull block %d", seq) 521 } 522 reportedPrevHash := nextBlock.Header.PreviousHash 523 if (nextBlock.Header.Number > 0) && !bytes.Equal(reportedPrevHash, actualPrevHash) { 524 return n, errors.Errorf("block header mismatch on sequence %d, expected %x, got %x", 525 nextBlock.Header.Number, actualPrevHash, reportedPrevHash) 526 } 527 actualPrevHash = protoutil.BlockHeaderHash(nextBlock.Header) 528 if err := c.ledgerResources.Append(nextBlock); err != nil { 529 return n, errors.WithMessagef(err, "failed to append block %d to the ledger", nextBlock.Header.Number) 530 } 531 532 if protoutil.IsConfigBlock(nextBlock) { 533 c.logger.Debugf("Pulled blocks from %d to %d, last block is config", firstBlockToPull, nextBlock.Header.Number) 534 c.lastConfig = nextBlock 535 if err := c.blockPullerFactory.UpdateVerifierFromConfigBlock(nextBlock); err != nil { 536 return n, errors.WithMessagef(err, "failed to update verifier from last config, block number: %d", nextBlock.Header.Number) 537 } 538 if updateEndpoints { 539 endpoints, err := cluster.EndpointconfigFromConfigBlock(nextBlock, c.cryptoProvider) 540 if err != nil { 541 return n, errors.WithMessagef(err, "failed to extract endpoints from last config, block number: %d", nextBlock.Header.Number) 542 } 543 c.blockPuller.UpdateEndpoints(endpoints) 544 } 545 } 546 } 547 } 548 c.logger.Debugf("Pulled blocks from %d to %d", firstBlockToPull, targetHeight) 549 return targetHeight - firstBlockToPull, nil 550 } 551 552 func (c *Chain) loadLastConfig() error { 553 height := c.ledgerResources.Height() 554 if height == 0 { 555 return errors.New("ledger is empty") 556 } 557 lastBlock := c.ledgerResources.Block(height - 1) 558 index, err := protoutil.GetLastConfigIndexFromBlock(lastBlock) 559 if err != nil { 560 return errors.WithMessage(err, "chain does have appropriately encoded last config in its latest block") 561 } 562 lastConfig := c.ledgerResources.Block(index) 563 if lastConfig == nil { 564 return errors.Errorf("could not retrieve config block from index %d", index) 565 } 566 c.lastConfig = lastConfig 567 return nil 568 }