github.com/Finschia/ostracon@v1.1.5/blockchain/v2/reactor.go (about)

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