gitlab.com/gpdionisio/tendermint@v0.34.19-dev2/blockchain/v1/reactor.go (about)

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