github.com/osdi23p228/fabric@v0.0.0-20221218062954-77808885f5db/orderer/consensus/etcdraft/eviction.go (about)

     1  /*
     2  Copyright IBM Corp. All Rights Reserved.
     3  
     4  SPDX-License-Identifier: Apache-2.0
     5  */
     6  
     7  package etcdraft
     8  
     9  import (
    10  	"fmt"
    11  	"sync"
    12  	"sync/atomic"
    13  	"time"
    14  
    15  	"github.com/hyperledger/fabric-protos-go/common"
    16  	"github.com/osdi23p228/fabric/common/flogging"
    17  	"github.com/osdi23p228/fabric/orderer/common/cluster"
    18  	"github.com/osdi23p228/fabric/protoutil"
    19  	"go.etcd.io/etcd/raft/raftpb"
    20  )
    21  
    22  // PeriodicCheck checks periodically a condition, and reports
    23  // the cumulative consecutive period the condition was fulfilled.
    24  type PeriodicCheck struct {
    25  	Logger              *flogging.FabricLogger
    26  	CheckInterval       time.Duration
    27  	Condition           func() bool
    28  	Report              func(cumulativePeriod time.Duration)
    29  	ReportCleared       func()
    30  	conditionHoldsSince time.Time
    31  	once                sync.Once // Used to prevent double initialization
    32  	stopped             uint32
    33  }
    34  
    35  // Run runs the PeriodicCheck
    36  func (pc *PeriodicCheck) Run() {
    37  	pc.once.Do(pc.check)
    38  }
    39  
    40  // Stop stops the periodic checks
    41  func (pc *PeriodicCheck) Stop() {
    42  	pc.Logger.Info("Periodic check is stopping.")
    43  	atomic.AddUint32(&pc.stopped, 1)
    44  }
    45  
    46  func (pc *PeriodicCheck) shouldRun() bool {
    47  	return atomic.LoadUint32(&pc.stopped) == 0
    48  }
    49  
    50  func (pc *PeriodicCheck) check() {
    51  	if pc.Condition() {
    52  		pc.conditionFulfilled()
    53  	} else {
    54  		pc.conditionNotFulfilled()
    55  	}
    56  
    57  	if !pc.shouldRun() {
    58  		return
    59  	}
    60  	time.AfterFunc(pc.CheckInterval, pc.check)
    61  }
    62  
    63  func (pc *PeriodicCheck) conditionNotFulfilled() {
    64  	if pc.ReportCleared != nil && !pc.conditionHoldsSince.IsZero() {
    65  		pc.ReportCleared()
    66  	}
    67  	pc.conditionHoldsSince = time.Time{}
    68  }
    69  
    70  func (pc *PeriodicCheck) conditionFulfilled() {
    71  	if pc.conditionHoldsSince.IsZero() {
    72  		pc.conditionHoldsSince = time.Now()
    73  	}
    74  
    75  	pc.Report(time.Since(pc.conditionHoldsSince))
    76  }
    77  
    78  type evictionSuspector struct {
    79  	evictionSuspicionThreshold time.Duration
    80  	logger                     *flogging.FabricLogger
    81  	createPuller               CreateBlockPuller
    82  	height                     func() uint64
    83  	amIInChannel               cluster.SelfMembershipPredicate
    84  	halt                       func()
    85  	writeBlock                 func(block *common.Block) error
    86  	triggerCatchUp             func(sn *raftpb.Snapshot)
    87  	halted                     bool
    88  	timesTriggered             int
    89  }
    90  
    91  func (es *evictionSuspector) clearSuspicion() {
    92  	es.timesTriggered = 0
    93  }
    94  
    95  func (es *evictionSuspector) confirmSuspicion(cumulativeSuspicion time.Duration) {
    96  	// The goal here is to only execute the body of the function once every es.evictionSuspicionThreshold
    97  	if es.evictionSuspicionThreshold*time.Duration(es.timesTriggered+1) > cumulativeSuspicion || es.halted {
    98  		return
    99  	}
   100  	es.timesTriggered++
   101  
   102  	es.logger.Infof("Suspecting our own eviction from the channel for %v", cumulativeSuspicion)
   103  	puller, err := es.createPuller()
   104  	if err != nil {
   105  		es.logger.Panicf("Failed creating a block puller: %v", err)
   106  	}
   107  
   108  	lastConfigBlock, err := cluster.PullLastConfigBlock(puller)
   109  	if err != nil {
   110  		es.logger.Errorf("Failed pulling the last config block: %v", err)
   111  		return
   112  	}
   113  
   114  	es.logger.Infof("Last config block was found to be block [%d]", lastConfigBlock.Header.Number)
   115  
   116  	height := es.height()
   117  
   118  	if lastConfigBlock.Header.Number+1 <= height {
   119  		es.logger.Infof("Our height is higher or equal than the height of the orderer we pulled the last block from, aborting.")
   120  		return
   121  	}
   122  
   123  	err = es.amIInChannel(lastConfigBlock)
   124  	if err != cluster.ErrNotInChannel && err != cluster.ErrForbidden {
   125  		details := fmt.Sprintf(", our certificate was found in config block with sequence %d", lastConfigBlock.Header.Number)
   126  		if err != nil {
   127  			details = fmt.Sprintf(": %s", err.Error())
   128  		}
   129  		es.logger.Infof("Cannot confirm our own eviction from the channel%s", details)
   130  
   131  		es.triggerCatchUp(&raftpb.Snapshot{Data: protoutil.MarshalOrPanic(lastConfigBlock)})
   132  		return
   133  	}
   134  
   135  	es.logger.Warningf("Detected our own eviction from the channel in block [%d]", lastConfigBlock.Header.Number)
   136  
   137  	es.logger.Infof("Waiting for chain to halt")
   138  	es.halt()
   139  	es.halted = true
   140  	es.logger.Infof("Chain has been halted, pulling remaining blocks up to (and including) eviction block.")
   141  
   142  	nextBlock := height
   143  	es.logger.Infof("Will now pull blocks %d to %d", nextBlock, lastConfigBlock.Header.Number)
   144  	for seq := nextBlock; seq <= lastConfigBlock.Header.Number; seq++ {
   145  		es.logger.Infof("Pulling block [%d]", seq)
   146  		block := puller.PullBlock(seq)
   147  		err := es.writeBlock(block)
   148  		if err != nil {
   149  			es.logger.Panicf("Failed writing block [%d] to the ledger: %v", block.Header.Number, err)
   150  		}
   151  	}
   152  
   153  	es.logger.Infof("Pulled all blocks up to eviction block.")
   154  }