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

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