github.com/line/ostracon@v1.0.10-0.20230328032236-7f20145f065d/blockchain/v2/reactor.go (about)

     1  package v2
     2  
     3  import (
     4  	"errors"
     5  	"fmt"
     6  	"time"
     7  
     8  	bcproto "github.com/tendermint/tendermint/proto/tendermint/blockchain"
     9  
    10  	"github.com/line/ostracon/behaviour"
    11  	bc "github.com/line/ostracon/blockchain"
    12  	"github.com/line/ostracon/libs/log"
    13  	tmsync "github.com/line/ostracon/libs/sync"
    14  	"github.com/line/ostracon/p2p"
    15  	ocbcproto "github.com/line/ostracon/proto/ostracon/blockchain"
    16  	"github.com/line/ostracon/state"
    17  	"github.com/line/ostracon/types"
    18  )
    19  
    20  const (
    21  	// chBufferSize is the buffer size of all event channels.
    22  	chBufferSize int = 1000
    23  )
    24  
    25  type blockStore interface {
    26  	LoadBlock(height int64) *types.Block
    27  	SaveBlock(*types.Block, *types.PartSet, *types.Commit)
    28  	Base() int64
    29  	Height() int64
    30  }
    31  
    32  // BlockchainReactor handles fast sync protocol.
    33  type BlockchainReactor struct {
    34  	p2p.BaseReactor
    35  
    36  	fastSync    bool // if true, enable fast sync on start
    37  	stateSynced bool // set to true when SwitchToFastSync is called by state sync
    38  	scheduler   *Routine
    39  	processor   *Routine
    40  	logger      log.Logger
    41  
    42  	mtx           tmsync.RWMutex
    43  	maxPeerHeight int64
    44  	syncHeight    int64
    45  	events        chan Event // non-nil during a fast sync
    46  
    47  	reporter behaviour.Reporter
    48  	io       iIO
    49  	store    blockStore
    50  }
    51  
    52  //nolint:unused,deadcode
    53  type blockVerifier interface {
    54  	VerifyCommit(chainID string, blockID types.BlockID, height int64, commit *types.Commit) error
    55  }
    56  
    57  type blockApplier interface {
    58  	ApplyBlock(state state.State, blockID types.BlockID, block *types.Block, times *state.CommitStepTimes) (state.State,
    59  		int64, error)
    60  }
    61  
    62  // XXX: unify naming in this package around tmState
    63  func newReactor(state state.State, store blockStore, reporter behaviour.Reporter,
    64  	blockApplier blockApplier, fastSync bool) *BlockchainReactor {
    65  	initHeight := state.LastBlockHeight + 1
    66  	if initHeight == 1 {
    67  		initHeight = state.InitialHeight
    68  	}
    69  	scheduler := newScheduler(initHeight, time.Now())
    70  	pContext := newProcessorContext(store, blockApplier, state)
    71  	// TODO: Fix naming to just newProcesssor
    72  	// newPcState requires a processorContext
    73  	processor := newPcState(pContext)
    74  
    75  	return &BlockchainReactor{
    76  		scheduler: newRoutine("scheduler", scheduler.handle, chBufferSize),
    77  		processor: newRoutine("processor", processor.handle, chBufferSize),
    78  		store:     store,
    79  		reporter:  reporter,
    80  		logger:    log.NewNopLogger(),
    81  		fastSync:  fastSync,
    82  	}
    83  }
    84  
    85  // NewBlockchainReactor creates a new reactor instance.
    86  func NewBlockchainReactor(
    87  	state state.State,
    88  	blockApplier blockApplier,
    89  	store blockStore,
    90  	fastSync bool) *BlockchainReactor {
    91  	reporter := behaviour.NewMockReporter()
    92  	return newReactor(state, store, reporter, blockApplier, fastSync)
    93  }
    94  
    95  // SetSwitch implements Reactor interface.
    96  func (r *BlockchainReactor) SetSwitch(sw *p2p.Switch) {
    97  	r.Switch = sw
    98  	if sw != nil {
    99  		r.io = newSwitchIo(sw)
   100  	} else {
   101  		r.io = nil
   102  	}
   103  }
   104  
   105  func (r *BlockchainReactor) setMaxPeerHeight(height int64) {
   106  	r.mtx.Lock()
   107  	defer r.mtx.Unlock()
   108  	if height > r.maxPeerHeight {
   109  		r.maxPeerHeight = height
   110  	}
   111  }
   112  
   113  func (r *BlockchainReactor) setSyncHeight(height int64) {
   114  	r.mtx.Lock()
   115  	defer r.mtx.Unlock()
   116  	r.syncHeight = height
   117  }
   118  
   119  // SyncHeight returns the height to which the BlockchainReactor has synced.
   120  func (r *BlockchainReactor) SyncHeight() int64 {
   121  	r.mtx.RLock()
   122  	defer r.mtx.RUnlock()
   123  	return r.syncHeight
   124  }
   125  
   126  // SetLogger sets the logger of the reactor.
   127  func (r *BlockchainReactor) SetLogger(logger log.Logger) {
   128  	r.logger = logger
   129  	r.scheduler.setLogger(logger)
   130  	r.processor.setLogger(logger)
   131  }
   132  
   133  // Start implements cmn.Service interface
   134  func (r *BlockchainReactor) Start() error {
   135  	r.reporter = behaviour.NewSwitchReporter(r.BaseReactor.Switch)
   136  	if r.fastSync {
   137  		err := r.startSync(nil)
   138  		if err != nil {
   139  			return fmt.Errorf("failed to start fast sync: %w", err)
   140  		}
   141  	}
   142  	return nil
   143  }
   144  
   145  // startSync begins a fast sync, signalled by r.events being non-nil. If state is non-nil,
   146  // the scheduler and processor is updated with this state on startup.
   147  func (r *BlockchainReactor) startSync(state *state.State) error {
   148  	r.mtx.Lock()
   149  	defer r.mtx.Unlock()
   150  	if r.events != nil {
   151  		return errors.New("fast sync already in progress")
   152  	}
   153  	r.events = make(chan Event, chBufferSize)
   154  	go r.scheduler.start()
   155  	go r.processor.start()
   156  	if state != nil {
   157  		<-r.scheduler.ready()
   158  		<-r.processor.ready()
   159  		r.scheduler.send(bcResetState{state: *state})
   160  		r.processor.send(bcResetState{state: *state})
   161  	}
   162  	go r.demux(r.events)
   163  	return nil
   164  }
   165  
   166  // endSync ends a fast sync
   167  func (r *BlockchainReactor) endSync() {
   168  	r.mtx.Lock()
   169  	defer r.mtx.Unlock()
   170  	if r.events != nil {
   171  		close(r.events)
   172  	}
   173  	r.events = nil
   174  	r.scheduler.stop()
   175  	r.processor.stop()
   176  }
   177  
   178  // SwitchToFastSync is called by the state sync reactor when switching to fast sync.
   179  func (r *BlockchainReactor) SwitchToFastSync(state state.State) error {
   180  	r.stateSynced = true
   181  	state = state.Copy()
   182  	return r.startSync(&state)
   183  }
   184  
   185  // reactor generated ticker events:
   186  // ticker for cleaning peers
   187  type rTryPrunePeer struct {
   188  	priorityHigh
   189  	time time.Time
   190  }
   191  
   192  func (e rTryPrunePeer) String() string {
   193  	return fmt.Sprintf("rTryPrunePeer{%v}", e.time)
   194  }
   195  
   196  // ticker event for scheduling block requests
   197  type rTrySchedule struct {
   198  	priorityHigh
   199  	time time.Time
   200  }
   201  
   202  func (e rTrySchedule) String() string {
   203  	return fmt.Sprintf("rTrySchedule{%v}", e.time)
   204  }
   205  
   206  // ticker for block processing
   207  type rProcessBlock struct {
   208  	priorityNormal
   209  }
   210  
   211  func (e rProcessBlock) String() string {
   212  	return "rProcessBlock"
   213  }
   214  
   215  // reactor generated events based on blockchain related messages from peers:
   216  // blockResponse message received from a peer
   217  type bcBlockResponse struct {
   218  	priorityNormal
   219  	time   time.Time
   220  	peerID p2p.ID
   221  	size   int64
   222  	block  *types.Block
   223  }
   224  
   225  func (resp bcBlockResponse) String() string {
   226  	return fmt.Sprintf("bcBlockResponse{%d#%X (size: %d bytes) from %v at %v}",
   227  		resp.block.Height, resp.block.Hash(), resp.size, resp.peerID, resp.time)
   228  }
   229  
   230  // blockNoResponse message received from a peer
   231  type bcNoBlockResponse struct {
   232  	priorityNormal
   233  	time   time.Time
   234  	peerID p2p.ID
   235  	height int64
   236  }
   237  
   238  func (resp bcNoBlockResponse) String() string {
   239  	return fmt.Sprintf("bcNoBlockResponse{%v has no block at height %d at %v}",
   240  		resp.peerID, resp.height, resp.time)
   241  }
   242  
   243  // statusResponse message received from a peer
   244  type bcStatusResponse struct {
   245  	priorityNormal
   246  	time   time.Time
   247  	peerID p2p.ID
   248  	base   int64
   249  	height int64
   250  }
   251  
   252  func (resp bcStatusResponse) String() string {
   253  	return fmt.Sprintf("bcStatusResponse{%v is at height %d (base: %d) at %v}",
   254  		resp.peerID, resp.height, resp.base, resp.time)
   255  }
   256  
   257  // new peer is connected
   258  type bcAddNewPeer struct {
   259  	priorityNormal
   260  	peerID p2p.ID
   261  }
   262  
   263  func (resp bcAddNewPeer) String() string {
   264  	return fmt.Sprintf("bcAddNewPeer{%v}", resp.peerID)
   265  }
   266  
   267  // existing peer is removed
   268  type bcRemovePeer struct {
   269  	priorityHigh
   270  	peerID p2p.ID
   271  	reason interface{}
   272  }
   273  
   274  func (resp bcRemovePeer) String() string {
   275  	return fmt.Sprintf("bcRemovePeer{%v due to %v}", resp.peerID, resp.reason)
   276  }
   277  
   278  // resets the scheduler and processor state, e.g. following a switch from state syncing
   279  type bcResetState struct {
   280  	priorityHigh
   281  	state state.State
   282  }
   283  
   284  func (e bcResetState) String() string {
   285  	return fmt.Sprintf("bcResetState{%v}", e.state)
   286  }
   287  
   288  // Takes the channel as a parameter to avoid race conditions on r.events.
   289  func (r *BlockchainReactor) demux(events <-chan Event) {
   290  	var lastRate = 0.0
   291  	var lastHundred = time.Now()
   292  
   293  	var (
   294  		processBlockFreq = 20 * time.Millisecond
   295  		doProcessBlockCh = make(chan struct{}, 1)
   296  		doProcessBlockTk = time.NewTicker(processBlockFreq)
   297  	)
   298  	defer doProcessBlockTk.Stop()
   299  
   300  	var (
   301  		prunePeerFreq = 1 * time.Second
   302  		doPrunePeerCh = make(chan struct{}, 1)
   303  		doPrunePeerTk = time.NewTicker(prunePeerFreq)
   304  	)
   305  	defer doPrunePeerTk.Stop()
   306  
   307  	var (
   308  		scheduleFreq = 20 * time.Millisecond
   309  		doScheduleCh = make(chan struct{}, 1)
   310  		doScheduleTk = time.NewTicker(scheduleFreq)
   311  	)
   312  	defer doScheduleTk.Stop()
   313  
   314  	var (
   315  		statusFreq = 10 * time.Second
   316  		doStatusCh = make(chan struct{}, 1)
   317  		doStatusTk = time.NewTicker(statusFreq)
   318  	)
   319  	defer doStatusTk.Stop()
   320  	doStatusCh <- struct{}{} // immediately broadcast to get status of existing peers
   321  
   322  	// XXX: Extract timers to make testing atemporal
   323  	for {
   324  		select {
   325  		// Pacers: send at most per frequency but don't saturate
   326  		case <-doProcessBlockTk.C:
   327  			select {
   328  			case doProcessBlockCh <- struct{}{}:
   329  			default:
   330  			}
   331  		case <-doPrunePeerTk.C:
   332  			select {
   333  			case doPrunePeerCh <- struct{}{}:
   334  			default:
   335  			}
   336  		case <-doScheduleTk.C:
   337  			select {
   338  			case doScheduleCh <- struct{}{}:
   339  			default:
   340  			}
   341  		case <-doStatusTk.C:
   342  			select {
   343  			case doStatusCh <- struct{}{}:
   344  			default:
   345  			}
   346  
   347  		// Tickers: perform tasks periodically
   348  		case <-doScheduleCh:
   349  			r.scheduler.send(rTrySchedule{time: time.Now()})
   350  		case <-doPrunePeerCh:
   351  			r.scheduler.send(rTryPrunePeer{time: time.Now()})
   352  		case <-doProcessBlockCh:
   353  			r.processor.send(rProcessBlock{})
   354  		case <-doStatusCh:
   355  			if err := r.io.broadcastStatusRequest(); err != nil {
   356  				r.logger.Error("Error broadcasting status request", "err", err)
   357  			}
   358  
   359  		// Events from peers. Closing the channel signals event loop termination.
   360  		case event, ok := <-events:
   361  			if !ok {
   362  				r.logger.Info("Stopping event processing")
   363  				return
   364  			}
   365  			switch event := event.(type) {
   366  			case bcStatusResponse:
   367  				r.setMaxPeerHeight(event.height)
   368  				r.scheduler.send(event)
   369  			case bcAddNewPeer, bcRemovePeer, bcBlockResponse, bcNoBlockResponse:
   370  				r.scheduler.send(event)
   371  			default:
   372  				r.logger.Error("Received unexpected event", "event", fmt.Sprintf("%T", event))
   373  			}
   374  
   375  		// Incremental events from scheduler
   376  		case event := <-r.scheduler.next():
   377  			switch event := event.(type) {
   378  			case scBlockReceived:
   379  				r.processor.send(event)
   380  			case scPeerError:
   381  				r.processor.send(event)
   382  				if err := r.reporter.Report(behaviour.BadMessage(event.peerID, "scPeerError")); err != nil {
   383  					r.logger.Error("Error reporting peer", "err", err)
   384  				}
   385  			case scBlockRequest:
   386  				if err := r.io.sendBlockRequest(event.peerID, event.height); err != nil {
   387  					r.logger.Error("Error sending block request", "err", err)
   388  				}
   389  			case scFinishedEv:
   390  				r.processor.send(event)
   391  				r.scheduler.stop()
   392  			case scSchedulerFail:
   393  				r.logger.Error("Scheduler failure", "err", event.reason.Error())
   394  			case scPeersPruned:
   395  				// Remove peers from the processor.
   396  				for _, peerID := range event.peers {
   397  					r.processor.send(scPeerError{peerID: peerID, reason: errors.New("peer was pruned")})
   398  				}
   399  				r.logger.Debug("Pruned peers", "count", len(event.peers))
   400  			case noOpEvent:
   401  			default:
   402  				r.logger.Error("Received unexpected scheduler event", "event", fmt.Sprintf("%T", event))
   403  			}
   404  
   405  		// Incremental events from processor
   406  		case event := <-r.processor.next():
   407  			switch event := event.(type) {
   408  			case pcBlockProcessed:
   409  				r.setSyncHeight(event.height)
   410  				if r.syncHeight%100 == 0 {
   411  					lastRate = 0.9*lastRate + 0.1*(100/time.Since(lastHundred).Seconds())
   412  					r.logger.Info("Fast Sync Rate", "height", r.syncHeight,
   413  						"max_peer_height", r.maxPeerHeight, "blocks/s", lastRate)
   414  					lastHundred = time.Now()
   415  				}
   416  				r.scheduler.send(event)
   417  			case pcBlockVerificationFailure:
   418  				r.scheduler.send(event)
   419  			case pcFinished:
   420  				r.logger.Info("Fast sync complete, switching to consensus")
   421  				if !r.io.trySwitchToConsensus(event.tmState, event.blocksSynced > 0 || r.stateSynced) {
   422  					r.logger.Error("Failed to switch to consensus reactor")
   423  				}
   424  				r.endSync()
   425  				return
   426  			case noOpEvent:
   427  			default:
   428  				r.logger.Error("Received unexpected processor event", "event", fmt.Sprintf("%T", event))
   429  			}
   430  
   431  		// Terminal event from scheduler
   432  		case err := <-r.scheduler.final():
   433  			switch err {
   434  			case nil:
   435  				r.logger.Info("Scheduler stopped")
   436  			default:
   437  				r.logger.Error("Scheduler aborted with error", "err", err)
   438  			}
   439  
   440  		// Terminal event from processor
   441  		case err := <-r.processor.final():
   442  			switch err {
   443  			case nil:
   444  				r.logger.Info("Processor stopped")
   445  			default:
   446  				r.logger.Error("Processor aborted with error", "err", err)
   447  			}
   448  		}
   449  	}
   450  }
   451  
   452  // Stop implements cmn.Service interface.
   453  func (r *BlockchainReactor) Stop() error {
   454  	r.logger.Info("reactor stopping")
   455  	r.endSync()
   456  	r.logger.Info("reactor stopped")
   457  	return nil
   458  }
   459  
   460  // Receive implements Reactor by handling different message types.
   461  func (r *BlockchainReactor) Receive(chID byte, src p2p.Peer, msgBytes []byte) {
   462  	msg, err := bc.DecodeMsg(msgBytes)
   463  	if err != nil {
   464  		r.logger.Error("error decoding message",
   465  			"src", src.ID(), "chId", chID, "msg", msg, "err", err)
   466  		_ = r.reporter.Report(behaviour.BadMessage(src.ID(), err.Error()))
   467  		return
   468  	}
   469  
   470  	if err = bc.ValidateMsg(msg); err != nil {
   471  		r.logger.Error("peer sent us invalid msg", "peer", src, "msg", msg, "err", err)
   472  		_ = r.reporter.Report(behaviour.BadMessage(src.ID(), err.Error()))
   473  		return
   474  	}
   475  
   476  	r.logger.Debug("Receive", "src", src.ID(), "chID", chID, "msg", msg)
   477  
   478  	switch msg := msg.(type) {
   479  	case *bcproto.StatusRequest:
   480  		if err := r.io.sendStatusResponse(r.store.Base(), r.store.Height(), src.ID()); err != nil {
   481  			r.logger.Error("Could not send status message to peer", "src", src)
   482  		}
   483  
   484  	case *bcproto.BlockRequest:
   485  		block := r.store.LoadBlock(msg.Height)
   486  		if block != nil {
   487  			if err = r.io.sendBlockToPeer(block, src.ID()); err != nil {
   488  				r.logger.Error("Could not send block message to peer: ", err)
   489  			}
   490  		} else {
   491  			r.logger.Info("peer asking for a block we don't have", "src", src, "height", msg.Height)
   492  			peerID := src.ID()
   493  			if err = r.io.sendBlockNotFound(msg.Height, peerID); err != nil {
   494  				r.logger.Error("Couldn't send block not found: ", err)
   495  			}
   496  		}
   497  
   498  	case *bcproto.StatusResponse:
   499  		r.mtx.RLock()
   500  		if r.events != nil {
   501  			r.events <- bcStatusResponse{peerID: src.ID(), base: msg.Base, height: msg.Height}
   502  		}
   503  		r.mtx.RUnlock()
   504  
   505  	case *ocbcproto.BlockResponse:
   506  		bi, err := types.BlockFromProto(msg.Block)
   507  		if err != nil {
   508  			r.logger.Error("error transitioning block from protobuf", "err", err)
   509  			return
   510  		}
   511  		r.mtx.RLock()
   512  		if r.events != nil {
   513  			r.events <- bcBlockResponse{
   514  				peerID: src.ID(),
   515  				block:  bi,
   516  				size:   int64(len(msgBytes)),
   517  				time:   time.Now(),
   518  			}
   519  		}
   520  		r.mtx.RUnlock()
   521  
   522  	case *bcproto.NoBlockResponse:
   523  		r.mtx.RLock()
   524  		if r.events != nil {
   525  			r.events <- bcNoBlockResponse{peerID: src.ID(), height: msg.Height, time: time.Now()}
   526  		}
   527  		r.mtx.RUnlock()
   528  	}
   529  }
   530  
   531  // AddPeer implements Reactor interface
   532  func (r *BlockchainReactor) AddPeer(peer p2p.Peer) {
   533  	err := r.io.sendStatusResponse(r.store.Base(), r.store.Height(), peer.ID())
   534  	if err != nil {
   535  		r.logger.Error("Could not send status message to peer new", "src", peer.ID, "height", r.SyncHeight())
   536  	}
   537  	r.mtx.RLock()
   538  	defer r.mtx.RUnlock()
   539  	if r.events != nil {
   540  		r.events <- bcAddNewPeer{peerID: peer.ID()}
   541  	}
   542  }
   543  
   544  // RemovePeer implements Reactor interface.
   545  func (r *BlockchainReactor) RemovePeer(peer p2p.Peer, reason interface{}) {
   546  	r.mtx.RLock()
   547  	defer r.mtx.RUnlock()
   548  	if r.events != nil {
   549  		r.events <- bcRemovePeer{
   550  			peerID: peer.ID(),
   551  			reason: reason,
   552  		}
   553  	}
   554  }
   555  
   556  // GetChannels implements Reactor
   557  func (r *BlockchainReactor) GetChannels() []*p2p.ChannelDescriptor {
   558  	return []*p2p.ChannelDescriptor{
   559  		{
   560  			ID:                  BlockchainChannel,
   561  			Priority:            5,
   562  			SendQueueCapacity:   2000,
   563  			RecvBufferCapacity:  50 * 4096,
   564  			RecvMessageCapacity: bc.MaxMsgSize,
   565  		},
   566  	}
   567  }