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