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

     1  package v1
     2  
     3  import (
     4  	"fmt"
     5  	"reflect"
     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  	"github.com/line/ostracon/p2p"
    14  	ocbcproto "github.com/line/ostracon/proto/ostracon/blockchain"
    15  	sm "github.com/line/ostracon/state"
    16  	"github.com/line/ostracon/store"
    17  	"github.com/line/ostracon/types"
    18  )
    19  
    20  const (
    21  	// BlockchainChannel is a channel for blocks and status updates (`BlockStore` height)
    22  	BlockchainChannel = byte(0x40)
    23  	trySyncIntervalMS = 10
    24  	trySendIntervalMS = 10
    25  
    26  	// ask for best height every 10s
    27  	statusUpdateIntervalSeconds = 10
    28  )
    29  
    30  var (
    31  	// Maximum number of requests that can be pending per peer, i.e. for which requests have been sent but blocks
    32  	// have not been received.
    33  	maxRequestsPerPeer = 20
    34  	// Maximum number of block requests for the reactor, pending or for which blocks have been received.
    35  	maxNumRequests = 64
    36  )
    37  
    38  type consensusReactor interface {
    39  	// for when we switch from blockchain reactor and fast sync to
    40  	// the consensus machine
    41  	SwitchToConsensus(state sm.State, skipWAL bool)
    42  }
    43  
    44  // BlockchainReactor handles long-term catchup syncing.
    45  type BlockchainReactor struct {
    46  	p2p.BaseReactor
    47  
    48  	initialState sm.State // immutable
    49  	state        sm.State
    50  
    51  	blockExec *sm.BlockExecutor
    52  	store     *store.BlockStore
    53  
    54  	fastSync    bool
    55  	stateSynced bool
    56  
    57  	fsm          *BcReactorFSM
    58  	blocksSynced uint64
    59  
    60  	// Receive goroutine forwards messages to this channel to be processed in the context of the poolRoutine.
    61  	messagesForFSMCh chan bcReactorMessage
    62  
    63  	// Switch goroutine may send RemovePeer to the blockchain reactor. This is an error message that is relayed
    64  	// to this channel to be processed in the context of the poolRoutine.
    65  	errorsForFSMCh chan bcReactorMessage
    66  
    67  	// This channel is used by the FSM and indirectly the block pool to report errors to the blockchain reactor and
    68  	// the switch.
    69  	eventsFromFSMCh chan bcFsmMessage
    70  
    71  	swReporter *behaviour.SwitchReporter
    72  }
    73  
    74  // NewBlockchainReactor returns new reactor instance.
    75  func NewBlockchainReactor(state sm.State, blockExec *sm.BlockExecutor, store *store.BlockStore,
    76  	fastSync bool, async bool, recvBufSize int) *BlockchainReactor {
    77  
    78  	if state.LastBlockHeight != store.Height() {
    79  		panic(fmt.Sprintf("state (%v) and store (%v) height mismatch", state.LastBlockHeight,
    80  			store.Height()))
    81  	}
    82  
    83  	const capacity = 1000
    84  	eventsFromFSMCh := make(chan bcFsmMessage, capacity)
    85  	messagesForFSMCh := make(chan bcReactorMessage, capacity)
    86  	errorsForFSMCh := make(chan bcReactorMessage, capacity)
    87  
    88  	startHeight := store.Height() + 1
    89  	if startHeight == 1 {
    90  		startHeight = state.InitialHeight
    91  	}
    92  	bcR := &BlockchainReactor{
    93  		initialState:     state,
    94  		state:            state,
    95  		blockExec:        blockExec,
    96  		fastSync:         fastSync,
    97  		store:            store,
    98  		messagesForFSMCh: messagesForFSMCh,
    99  		eventsFromFSMCh:  eventsFromFSMCh,
   100  		errorsForFSMCh:   errorsForFSMCh,
   101  	}
   102  	fsm := NewFSM(startHeight, bcR)
   103  	bcR.fsm = fsm
   104  	bcR.BaseReactor = *p2p.NewBaseReactor("BlockchainReactor", bcR, async, recvBufSize)
   105  	// bcR.swReporter = behaviour.NewSwitchReporter(bcR.BaseReactor.Switch)
   106  
   107  	return bcR
   108  }
   109  
   110  // bcReactorMessage is used by the reactor to send messages to the FSM.
   111  type bcReactorMessage struct {
   112  	event bReactorEvent
   113  	data  bReactorEventData
   114  }
   115  
   116  type bFsmEvent uint
   117  
   118  const (
   119  	// message type events
   120  	peerErrorEv = iota + 1
   121  	syncFinishedEv
   122  )
   123  
   124  type bFsmEventData struct {
   125  	peerID p2p.ID
   126  	err    error
   127  }
   128  
   129  // bcFsmMessage is used by the FSM to send messages to the reactor
   130  type bcFsmMessage struct {
   131  	event bFsmEvent
   132  	data  bFsmEventData
   133  }
   134  
   135  // SetLogger implements service.Service by setting the logger on reactor and pool.
   136  func (bcR *BlockchainReactor) SetLogger(l log.Logger) {
   137  	bcR.BaseService.Logger = l
   138  	bcR.fsm.SetLogger(l)
   139  }
   140  
   141  // OnStart implements service.Service.
   142  func (bcR *BlockchainReactor) OnStart() error {
   143  	bcR.swReporter = behaviour.NewSwitchReporter(bcR.BaseReactor.Switch)
   144  
   145  	// call BaseReactor's OnStart()
   146  	err := bcR.BaseReactor.OnStart()
   147  	if err != nil {
   148  		return err
   149  	}
   150  
   151  	if bcR.fastSync {
   152  		go bcR.poolRoutine()
   153  	}
   154  	return nil
   155  }
   156  
   157  // OnStop implements service.Service.
   158  func (bcR *BlockchainReactor) OnStop() {
   159  	_ = bcR.Stop()
   160  }
   161  
   162  // SwitchToFastSync is called by the state sync reactor when switching to fast sync.
   163  func (bcR *BlockchainReactor) SwitchToFastSync(state sm.State) error {
   164  	bcR.fastSync = true
   165  	bcR.initialState = state
   166  	bcR.state = state
   167  	bcR.stateSynced = true
   168  
   169  	bcR.fsm = NewFSM(state.LastBlockHeight+1, bcR)
   170  	bcR.fsm.SetLogger(bcR.Logger)
   171  	go bcR.poolRoutine()
   172  	return nil
   173  }
   174  
   175  // GetChannels implements Reactor
   176  func (bcR *BlockchainReactor) GetChannels() []*p2p.ChannelDescriptor {
   177  	return []*p2p.ChannelDescriptor{
   178  		{
   179  			ID:                  BlockchainChannel,
   180  			Priority:            10,
   181  			SendQueueCapacity:   2000,
   182  			RecvBufferCapacity:  50 * 4096,
   183  			RecvMessageCapacity: bc.MaxMsgSize,
   184  		},
   185  	}
   186  }
   187  
   188  // AddPeer implements Reactor by sending our state to peer.
   189  func (bcR *BlockchainReactor) AddPeer(peer p2p.Peer) {
   190  	msgBytes, err := bc.EncodeMsg(&bcproto.StatusResponse{
   191  		Base:   bcR.store.Base(),
   192  		Height: bcR.store.Height(),
   193  	})
   194  	if err != nil {
   195  		bcR.Logger.Error("could not convert msg to protobuf", "err", err)
   196  		return
   197  	}
   198  	peer.Send(BlockchainChannel, msgBytes)
   199  	// it's OK if send fails. will try later in poolRoutine
   200  
   201  	// peer is added to the pool once we receive the first
   202  	// bcStatusResponseMessage from the peer and call pool.updatePeer()
   203  }
   204  
   205  // sendBlockToPeer loads a block and sends it to the requesting peer.
   206  // If the block doesn't exist a bcNoBlockResponseMessage is sent.
   207  // If all nodes are honest, no node should be requesting for a block that doesn't exist.
   208  func (bcR *BlockchainReactor) sendBlockToPeer(msg *bcproto.BlockRequest,
   209  	src p2p.Peer) (queued bool) {
   210  
   211  	block := bcR.store.LoadBlock(msg.Height)
   212  	if block != nil {
   213  		pbbi, err := block.ToProto()
   214  		if err != nil {
   215  			bcR.Logger.Error("Could not send block message to peer", "err", err)
   216  			return false
   217  		}
   218  		msgBytes, err := bc.EncodeMsg(&ocbcproto.BlockResponse{Block: pbbi})
   219  		if err != nil {
   220  			bcR.Logger.Error("unable to marshal msg", "err", err)
   221  			return false
   222  		}
   223  		return src.TrySend(BlockchainChannel, msgBytes)
   224  	}
   225  
   226  	bcR.Logger.Info("peer asking for a block we don't have", "src", src, "height", msg.Height)
   227  
   228  	msgBytes, err := bc.EncodeMsg(&bcproto.NoBlockResponse{Height: msg.Height})
   229  	if err != nil {
   230  		bcR.Logger.Error("unable to marshal msg", "err", err)
   231  		return false
   232  	}
   233  	return src.TrySend(BlockchainChannel, msgBytes)
   234  }
   235  
   236  func (bcR *BlockchainReactor) sendStatusResponseToPeer(msg *bcproto.StatusRequest, src p2p.Peer) (queued bool) {
   237  	msgBytes, err := bc.EncodeMsg(&bcproto.StatusResponse{
   238  		Base:   bcR.store.Base(),
   239  		Height: bcR.store.Height(),
   240  	})
   241  	if err != nil {
   242  		bcR.Logger.Error("unable to marshal msg", "err", err)
   243  		return false
   244  	}
   245  
   246  	return src.TrySend(BlockchainChannel, msgBytes)
   247  }
   248  
   249  // RemovePeer implements Reactor by removing peer from the pool.
   250  func (bcR *BlockchainReactor) RemovePeer(peer p2p.Peer, reason interface{}) {
   251  	msgData := bcReactorMessage{
   252  		event: peerRemoveEv,
   253  		data: bReactorEventData{
   254  			peerID: peer.ID(),
   255  			err:    errSwitchRemovesPeer,
   256  		},
   257  	}
   258  	bcR.errorsForFSMCh <- msgData
   259  }
   260  
   261  // Receive implements Reactor by handling 4 types of messages (look below).
   262  func (bcR *BlockchainReactor) Receive(chID byte, src p2p.Peer, msgBytes []byte) {
   263  	msg, err := bc.DecodeMsg(msgBytes)
   264  	if err != nil {
   265  		bcR.Logger.Error("error decoding message", "src", src, "chId", chID, "err", err)
   266  		_ = bcR.swReporter.Report(behaviour.BadMessage(src.ID(), err.Error()))
   267  		return
   268  	}
   269  
   270  	if err = bc.ValidateMsg(msg); err != nil {
   271  		bcR.Logger.Error("peer sent us invalid msg", "peer", src, "msg", msg, "err", err)
   272  		_ = bcR.swReporter.Report(behaviour.BadMessage(src.ID(), err.Error()))
   273  		return
   274  	}
   275  
   276  	bcR.Logger.Debug("Receive", "src", src, "chID", chID, "msg", msg)
   277  
   278  	switch msg := msg.(type) {
   279  	case *bcproto.BlockRequest:
   280  		if queued := bcR.sendBlockToPeer(msg, src); !queued {
   281  			// Unfortunately not queued since the queue is full.
   282  			bcR.Logger.Error("Could not send block message to peer", "src", src, "height", msg.Height)
   283  		}
   284  
   285  	case *bcproto.StatusRequest:
   286  		// Send peer our state.
   287  		if queued := bcR.sendStatusResponseToPeer(msg, src); !queued {
   288  			// Unfortunately not queued since the queue is full.
   289  			bcR.Logger.Error("Could not send status message to peer", "src", src)
   290  		}
   291  
   292  	case *ocbcproto.BlockResponse:
   293  		bi, err := types.BlockFromProto(msg.Block)
   294  		if err != nil {
   295  			bcR.Logger.Error("error transition block from protobuf", "err", err)
   296  			return
   297  		}
   298  		msgForFSM := bcReactorMessage{
   299  			event: blockResponseEv,
   300  			data: bReactorEventData{
   301  				peerID: src.ID(),
   302  				height: bi.Height,
   303  				block:  bi,
   304  				length: len(msgBytes),
   305  			},
   306  		}
   307  		bcR.Logger.Info("Received", "src", src, "height", bi.Height)
   308  		bcR.messagesForFSMCh <- msgForFSM
   309  	case *bcproto.NoBlockResponse:
   310  		msgForFSM := bcReactorMessage{
   311  			event: noBlockResponseEv,
   312  			data: bReactorEventData{
   313  				peerID: src.ID(),
   314  				height: msg.Height,
   315  			},
   316  		}
   317  		bcR.Logger.Debug("Peer does not have requested block", "peer", src, "height", msg.Height)
   318  		bcR.messagesForFSMCh <- msgForFSM
   319  
   320  	case *bcproto.StatusResponse:
   321  		// Got a peer status. Unverified.
   322  		msgForFSM := bcReactorMessage{
   323  			event: statusResponseEv,
   324  			data: bReactorEventData{
   325  				peerID: src.ID(),
   326  				height: msg.Height,
   327  				length: len(msgBytes),
   328  			},
   329  		}
   330  		bcR.messagesForFSMCh <- msgForFSM
   331  
   332  	default:
   333  		bcR.Logger.Error(fmt.Sprintf("unknown message type %v", reflect.TypeOf(msg)))
   334  	}
   335  }
   336  
   337  // processBlocksRoutine processes blocks until signlaed to stop over the stopProcessing channel
   338  func (bcR *BlockchainReactor) processBlocksRoutine(stopProcessing chan struct{}) {
   339  
   340  	processReceivedBlockTicker := time.NewTicker(trySyncIntervalMS * time.Millisecond)
   341  	doProcessBlockCh := make(chan struct{}, 1)
   342  
   343  	lastHundred := time.Now()
   344  	lastRate := 0.0
   345  
   346  ForLoop:
   347  	for {
   348  		select {
   349  		case <-stopProcessing:
   350  			bcR.Logger.Info("finishing block execution")
   351  			break ForLoop
   352  		case <-processReceivedBlockTicker.C: // try to execute blocks
   353  			select {
   354  			case doProcessBlockCh <- struct{}{}:
   355  			default:
   356  			}
   357  		case <-doProcessBlockCh:
   358  			for {
   359  				err := bcR.processBlock()
   360  				if err == errMissingBlock {
   361  					break
   362  				}
   363  				// Notify FSM of block processing result.
   364  				msgForFSM := bcReactorMessage{
   365  					event: processedBlockEv,
   366  					data: bReactorEventData{
   367  						err: err,
   368  					},
   369  				}
   370  				_ = bcR.fsm.Handle(&msgForFSM)
   371  
   372  				if err != nil {
   373  					break
   374  				}
   375  
   376  				bcR.blocksSynced++
   377  				if bcR.blocksSynced%100 == 0 {
   378  					lastRate = 0.9*lastRate + 0.1*(100/time.Since(lastHundred).Seconds())
   379  					height, maxPeerHeight := bcR.fsm.Status()
   380  					bcR.Logger.Info("Fast Sync Rate", "height", height,
   381  						"max_peer_height", maxPeerHeight, "blocks/s", lastRate)
   382  					lastHundred = time.Now()
   383  				}
   384  			}
   385  		}
   386  	}
   387  }
   388  
   389  // poolRoutine receives and handles messages from the Receive() routine and from the FSM.
   390  func (bcR *BlockchainReactor) poolRoutine() {
   391  
   392  	bcR.fsm.Start()
   393  
   394  	sendBlockRequestTicker := time.NewTicker(trySendIntervalMS * time.Millisecond)
   395  	statusUpdateTicker := time.NewTicker(statusUpdateIntervalSeconds * time.Second)
   396  
   397  	stopProcessing := make(chan struct{}, 1)
   398  	go bcR.processBlocksRoutine(stopProcessing)
   399  
   400  ForLoop:
   401  	for {
   402  		select {
   403  
   404  		case <-sendBlockRequestTicker.C:
   405  			if !bcR.fsm.NeedsBlocks() {
   406  				continue
   407  			}
   408  			_ = bcR.fsm.Handle(&bcReactorMessage{
   409  				event: makeRequestsEv,
   410  				data: bReactorEventData{
   411  					maxNumRequests: maxNumRequests}})
   412  
   413  		case <-statusUpdateTicker.C:
   414  			// Ask for status updates.
   415  			go bcR.sendStatusRequest()
   416  
   417  		case msg := <-bcR.messagesForFSMCh:
   418  			// Sent from the Receive() routine when status (statusResponseEv) and
   419  			// block (blockResponseEv) response events are received
   420  			_ = bcR.fsm.Handle(&msg)
   421  
   422  		case msg := <-bcR.errorsForFSMCh:
   423  			// Sent from the switch.RemovePeer() routine (RemovePeerEv) and
   424  			// FSM state timer expiry routine (stateTimeoutEv).
   425  			_ = bcR.fsm.Handle(&msg)
   426  
   427  		case msg := <-bcR.eventsFromFSMCh:
   428  			switch msg.event {
   429  			case syncFinishedEv:
   430  				stopProcessing <- struct{}{}
   431  				// Sent from the FSM when it enters finished state.
   432  				break ForLoop
   433  			case peerErrorEv:
   434  				// Sent from the FSM when it detects peer error
   435  				bcR.reportPeerErrorToSwitch(msg.data.err, msg.data.peerID)
   436  				if msg.data.err == errNoPeerResponse {
   437  					// Sent from the peer timeout handler routine
   438  					_ = bcR.fsm.Handle(&bcReactorMessage{
   439  						event: peerRemoveEv,
   440  						data: bReactorEventData{
   441  							peerID: msg.data.peerID,
   442  							err:    msg.data.err,
   443  						},
   444  					})
   445  				}
   446  				// else {
   447  				// For slow peers, or errors due to blocks received from wrong peer
   448  				// the FSM had already removed the peers
   449  				// }
   450  			default:
   451  				bcR.Logger.Error("Event from FSM not supported", "type", msg.event)
   452  			}
   453  
   454  		case <-bcR.Quit():
   455  			break ForLoop
   456  		}
   457  	}
   458  }
   459  
   460  func (bcR *BlockchainReactor) reportPeerErrorToSwitch(err error, peerID p2p.ID) {
   461  	peer := bcR.Switch.Peers().Get(peerID)
   462  	if peer != nil {
   463  		_ = bcR.swReporter.Report(behaviour.BadMessage(peerID, err.Error()))
   464  	}
   465  }
   466  
   467  func (bcR *BlockchainReactor) processBlock() error {
   468  
   469  	first, second, err := bcR.fsm.FirstTwoBlocks()
   470  	if err != nil {
   471  		// We need both to sync the first block.
   472  		return err
   473  	}
   474  
   475  	chainID := bcR.initialState.ChainID
   476  
   477  	firstParts := first.MakePartSet(types.BlockPartSizeBytes)
   478  	firstPartSetHeader := firstParts.Header()
   479  	firstID := types.BlockID{Hash: first.Hash(), PartSetHeader: firstPartSetHeader}
   480  	// Finally, verify the first block using the second's commit
   481  	// NOTE: we can probably make this more efficient, but note that calling
   482  	// first.Hash() doesn't verify the tx contents, so MakePartSet() is
   483  	// currently necessary.
   484  	err = bcR.state.Validators.VerifyCommitLight(chainID, firstID, first.Height, second.LastCommit)
   485  	if err != nil {
   486  		bcR.Logger.Error("error during commit verification", "err", err,
   487  			"first", first.Height, "second", second.Height)
   488  		return errBlockVerificationFailure
   489  	}
   490  
   491  	bcR.store.SaveBlock(first, firstParts, second.LastCommit)
   492  
   493  	bcR.state, _, err = bcR.blockExec.ApplyBlock(bcR.state, firstID, first, nil)
   494  	if err != nil {
   495  		panic(fmt.Sprintf("failed to process committed block (%d:%X): %v", first.Height, first.Hash(), err))
   496  	}
   497  
   498  	return nil
   499  }
   500  
   501  // Implements bcRNotifier
   502  // sendStatusRequest broadcasts `BlockStore` height.
   503  func (bcR *BlockchainReactor) sendStatusRequest() {
   504  	msgBytes, err := bc.EncodeMsg(&bcproto.StatusRequest{})
   505  	if err != nil {
   506  		panic(err)
   507  	}
   508  	bcR.Switch.Broadcast(BlockchainChannel, msgBytes)
   509  }
   510  
   511  // Implements bcRNotifier
   512  // BlockRequest sends `BlockRequest` height.
   513  func (bcR *BlockchainReactor) sendBlockRequest(peerID p2p.ID, height int64) error {
   514  	peer := bcR.Switch.Peers().Get(peerID)
   515  	if peer == nil {
   516  		return errNilPeerForBlockRequest
   517  	}
   518  
   519  	msgBytes, err := bc.EncodeMsg(&bcproto.BlockRequest{Height: height})
   520  	if err != nil {
   521  		return err
   522  	}
   523  	queued := peer.TrySend(BlockchainChannel, msgBytes)
   524  	if !queued {
   525  		return errSendQueueFull
   526  	}
   527  	return nil
   528  }
   529  
   530  // Implements bcRNotifier
   531  func (bcR *BlockchainReactor) switchToConsensus() {
   532  	conR, ok := bcR.Switch.Reactor("CONSENSUS").(consensusReactor)
   533  	if ok {
   534  		conR.SwitchToConsensus(bcR.state, bcR.blocksSynced > 0 || bcR.stateSynced)
   535  		bcR.eventsFromFSMCh <- bcFsmMessage{event: syncFinishedEv}
   536  	}
   537  	// else {
   538  	// Should only happen during testing.
   539  	// }
   540  }
   541  
   542  // Implements bcRNotifier
   543  // Called by FSM and pool:
   544  // - pool calls when it detects slow peer or when peer times out
   545  // - FSM calls when:
   546  //   - adding a block (addBlock) fails
   547  //   - reactor processing of a block reports failure and FSM sends back the peers of first and second blocks
   548  func (bcR *BlockchainReactor) sendPeerError(err error, peerID p2p.ID) {
   549  	bcR.Logger.Info("sendPeerError:", "peer", peerID, "error", err)
   550  	msgData := bcFsmMessage{
   551  		event: peerErrorEv,
   552  		data: bFsmEventData{
   553  			peerID: peerID,
   554  			err:    err,
   555  		},
   556  	}
   557  	bcR.eventsFromFSMCh <- msgData
   558  }
   559  
   560  // Implements bcRNotifier
   561  func (bcR *BlockchainReactor) resetStateTimer(name string, timer **time.Timer, timeout time.Duration) {
   562  	if timer == nil {
   563  		panic("nil timer pointer parameter")
   564  	}
   565  	if *timer == nil {
   566  		*timer = time.AfterFunc(timeout, func() {
   567  			msg := bcReactorMessage{
   568  				event: stateTimeoutEv,
   569  				data: bReactorEventData{
   570  					stateName: name,
   571  				},
   572  			}
   573  			bcR.errorsForFSMCh <- msg
   574  		})
   575  	} else {
   576  		(*timer).Reset(timeout)
   577  	}
   578  }