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

     1  package v0
     2  
     3  import (
     4  	"fmt"
     5  	"reflect"
     6  	"time"
     7  
     8  	"github.com/gogo/protobuf/proto"
     9  
    10  	bcproto "github.com/tendermint/tendermint/proto/tendermint/blockchain"
    11  
    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  
    25  	trySyncIntervalMS = 10
    26  
    27  	// stop syncing when last block's time is
    28  	// within this much of the system time.
    29  	// stopSyncingDurationMinutes = 10
    30  
    31  	// ask for best height every 10s
    32  	statusUpdateIntervalSeconds = 10
    33  	// check if we should switch to consensus reactor
    34  	switchToConsensusIntervalSeconds = 1
    35  )
    36  
    37  type consensusReactor interface {
    38  	// for when we switch from blockchain reactor and fast sync to
    39  	// the consensus machine
    40  	SwitchToConsensus(state sm.State, skipWAL bool)
    41  }
    42  
    43  type peerError struct {
    44  	err    error
    45  	peerID p2p.ID
    46  }
    47  
    48  func (e peerError) Error() string {
    49  	return fmt.Sprintf("error with peer %v: %s", e.peerID, e.err.Error())
    50  }
    51  
    52  // BlockchainReactor handles long-term catchup syncing.
    53  type BlockchainReactor struct {
    54  	p2p.BaseReactor
    55  
    56  	// immutable
    57  	initialState sm.State
    58  
    59  	blockExec *sm.BlockExecutor
    60  	store     *store.BlockStore
    61  	pool      *BlockPool
    62  	fastSync  bool
    63  
    64  	requestsCh <-chan BlockRequest
    65  	errorsCh   <-chan peerError
    66  }
    67  
    68  // NewBlockchainReactor returns new reactor instance.
    69  func NewBlockchainReactor(state sm.State, blockExec *sm.BlockExecutor, store *store.BlockStore,
    70  	fastSync bool, async bool, recvBufSize int) *BlockchainReactor {
    71  
    72  	if state.LastBlockHeight != store.Height() {
    73  		panic(fmt.Sprintf("state (%v) and store (%v) height mismatch", state.LastBlockHeight,
    74  			store.Height()))
    75  	}
    76  
    77  	requestsCh := make(chan BlockRequest, maxTotalRequesters)
    78  
    79  	const capacity = 1000                      // must be bigger than peers count
    80  	errorsCh := make(chan peerError, capacity) // so we don't block in #Receive#pool.AddBlock
    81  
    82  	startHeight := store.Height() + 1
    83  	if startHeight == 1 {
    84  		startHeight = state.InitialHeight
    85  	}
    86  	pool := NewBlockPool(startHeight, requestsCh, errorsCh)
    87  
    88  	bcR := &BlockchainReactor{
    89  		initialState: state,
    90  		blockExec:    blockExec,
    91  		store:        store,
    92  		pool:         pool,
    93  		fastSync:     fastSync,
    94  		requestsCh:   requestsCh,
    95  		errorsCh:     errorsCh,
    96  	}
    97  	bcR.BaseReactor = *p2p.NewBaseReactor("BlockchainReactor", bcR, async, recvBufSize)
    98  	return bcR
    99  }
   100  
   101  // SetLogger implements service.Service by setting the logger on reactor and pool.
   102  func (bcR *BlockchainReactor) SetLogger(l log.Logger) {
   103  	bcR.BaseService.Logger = l
   104  	bcR.pool.Logger = l
   105  }
   106  
   107  // OnStart implements service.Service.
   108  func (bcR *BlockchainReactor) OnStart() error {
   109  	// call BaseReactor's OnStart()
   110  	err := bcR.BaseReactor.OnStart()
   111  	if err != nil {
   112  		return err
   113  	}
   114  
   115  	if bcR.fastSync {
   116  		err = bcR.pool.Start()
   117  		if err != nil {
   118  			return err
   119  		}
   120  		go bcR.poolRoutine(false)
   121  	}
   122  	return nil
   123  }
   124  
   125  // SwitchToFastSync is called by the state sync reactor when switching to fast sync.
   126  func (bcR *BlockchainReactor) SwitchToFastSync(state sm.State) error {
   127  	bcR.fastSync = true
   128  	bcR.initialState = state
   129  
   130  	bcR.pool.height = state.LastBlockHeight + 1
   131  	err := bcR.pool.Start()
   132  	if err != nil {
   133  		return err
   134  	}
   135  	go bcR.poolRoutine(true)
   136  	return nil
   137  }
   138  
   139  // OnStop implements service.Service.
   140  func (bcR *BlockchainReactor) OnStop() {
   141  	if bcR.fastSync {
   142  		if err := bcR.pool.Stop(); err != nil {
   143  			bcR.Logger.Error("Error stopping pool", "err", err)
   144  		}
   145  	}
   146  }
   147  
   148  // GetChannels implements Reactor
   149  func (bcR *BlockchainReactor) GetChannels() []*p2p.ChannelDescriptor {
   150  	return []*p2p.ChannelDescriptor{
   151  		{
   152  			ID:                  BlockchainChannel,
   153  			Priority:            5,
   154  			SendQueueCapacity:   1000,
   155  			RecvBufferCapacity:  50 * 4096,
   156  			RecvMessageCapacity: bc.MaxMsgSize,
   157  			MessageType:         &ocbcproto.Message{},
   158  		},
   159  	}
   160  }
   161  
   162  // AddPeer implements Reactor by sending our state to peer.
   163  func (bcR *BlockchainReactor) AddPeer(peer p2p.Peer) {
   164  	p2p.SendEnvelopeShim(peer, p2p.Envelope{ //nolint: staticcheck
   165  		ChannelID: BlockchainChannel,
   166  		Message: &bcproto.StatusResponse{
   167  			Base:   bcR.store.Base(),
   168  			Height: bcR.store.Height(),
   169  		},
   170  	}, bcR.Logger)
   171  	// it's OK if send fails. will try later in poolRoutine
   172  
   173  	// peer is added to the pool once we receive the first
   174  	// bcStatusResponseMessage from the peer and call pool.SetPeerRange
   175  }
   176  
   177  // RemovePeer implements Reactor by removing peer from the pool.
   178  func (bcR *BlockchainReactor) RemovePeer(peer p2p.Peer, reason interface{}) {
   179  	bcR.pool.RemovePeer(peer.ID())
   180  }
   181  
   182  // respondToPeer loads a block and sends it to the requesting peer,
   183  // if we have it. Otherwise, we'll respond saying we don't have it.
   184  func (bcR *BlockchainReactor) respondToPeer(msg *bcproto.BlockRequest,
   185  	src p2p.Peer) (queued bool) {
   186  
   187  	block := bcR.store.LoadBlock(msg.Height)
   188  	if block != nil {
   189  		bl, err := block.ToProto()
   190  		if err != nil {
   191  			bcR.Logger.Error("could not convert msg to protobuf", "err", err)
   192  			return false
   193  		}
   194  		return p2p.TrySendEnvelopeShim(src, p2p.Envelope{ //nolint: staticcheck
   195  			ChannelID: BlockchainChannel,
   196  			Message:   &ocbcproto.BlockResponse{Block: bl},
   197  		}, bcR.Logger)
   198  	}
   199  
   200  	return p2p.TrySendEnvelopeShim(src, p2p.Envelope{ //nolint: staticcheck
   201  		ChannelID: BlockchainChannel,
   202  		Message:   &bcproto.NoBlockResponse{Height: msg.Height},
   203  	}, bcR.Logger)
   204  }
   205  
   206  func (bcR *BlockchainReactor) ReceiveEnvelope(e p2p.Envelope) {
   207  	if err := bc.ValidateMsg(e.Message); err != nil {
   208  		bcR.Logger.Error("Peer sent us invalid msg", "peer", e.Src, "msg", e.Message, "err", err)
   209  		bcR.Switch.StopPeerForError(e.Src, err)
   210  		return
   211  	}
   212  
   213  	bcR.Logger.Debug("Receive", "e.Src", e.Src, "chID", e.ChannelID, "msg", e.Message)
   214  
   215  	switch msg := e.Message.(type) {
   216  	case *bcproto.BlockRequest:
   217  		bcR.respondToPeer(msg, e.Src)
   218  	case *ocbcproto.BlockResponse:
   219  		bi, err := types.BlockFromProto(msg.Block)
   220  		if err != nil {
   221  			bcR.Logger.Error("Block content is invalid", "err", err)
   222  			return
   223  		}
   224  		bcR.pool.AddBlock(e.Src.ID(), bi, msg.Block.Size())
   225  	case *bcproto.StatusRequest:
   226  		// Send peer our state.
   227  		p2p.TrySendEnvelopeShim(e.Src, p2p.Envelope{ //nolint: staticcheck
   228  			ChannelID: BlockchainChannel,
   229  			Message: &bcproto.StatusResponse{
   230  				Height: bcR.store.Height(),
   231  				Base:   bcR.store.Base(),
   232  			},
   233  		}, bcR.Logger)
   234  	case *bcproto.StatusResponse:
   235  		// Got a peer status. Unverified.
   236  		bcR.pool.SetPeerRange(e.Src.ID(), msg.Base, msg.Height)
   237  	case *bcproto.NoBlockResponse:
   238  		bcR.Logger.Debug("Peer does not have requested block", "peer", e.Src, "height", msg.Height)
   239  	default:
   240  		bcR.Logger.Error(fmt.Sprintf("Unknown message type %v", reflect.TypeOf(msg)))
   241  	}
   242  }
   243  
   244  func (bcR *BlockchainReactor) Receive(chID byte, peer p2p.Peer, msgBytes []byte) {
   245  	msg := &ocbcproto.Message{}
   246  	err := proto.Unmarshal(msgBytes, msg)
   247  	if err != nil {
   248  		panic(err)
   249  	}
   250  	uw, err := msg.Unwrap()
   251  	if err != nil {
   252  		panic(err)
   253  	}
   254  	bcR.ReceiveEnvelope(p2p.Envelope{
   255  		ChannelID: chID,
   256  		Src:       peer,
   257  		Message:   uw,
   258  	})
   259  }
   260  
   261  // Handle messages from the poolReactor telling the reactor what to do.
   262  // NOTE: Don't sleep in the FOR_LOOP or otherwise slow it down!
   263  func (bcR *BlockchainReactor) poolRoutine(stateSynced bool) {
   264  
   265  	trySyncTicker := time.NewTicker(trySyncIntervalMS * time.Millisecond)
   266  	defer trySyncTicker.Stop()
   267  
   268  	statusUpdateTicker := time.NewTicker(statusUpdateIntervalSeconds * time.Second)
   269  	defer statusUpdateTicker.Stop()
   270  
   271  	switchToConsensusTicker := time.NewTicker(switchToConsensusIntervalSeconds * time.Second)
   272  	defer switchToConsensusTicker.Stop()
   273  
   274  	blocksSynced := uint64(0)
   275  
   276  	chainID := bcR.initialState.ChainID
   277  	state := bcR.initialState
   278  
   279  	lastHundred := time.Now()
   280  	lastRate := 0.0
   281  
   282  	didProcessCh := make(chan struct{}, 1)
   283  
   284  	go func() {
   285  		for {
   286  			select {
   287  			case <-bcR.Quit():
   288  				return
   289  			case <-bcR.pool.Quit():
   290  				return
   291  			case request := <-bcR.requestsCh:
   292  				peer := bcR.Switch.Peers().Get(request.PeerID)
   293  				if peer == nil {
   294  					continue
   295  				}
   296  				queued := p2p.TrySendEnvelopeShim(peer, p2p.Envelope{ //nolint: staticcheck
   297  					ChannelID: BlockchainChannel,
   298  					Message:   &bcproto.BlockRequest{Height: request.Height},
   299  				}, bcR.Logger)
   300  				if !queued {
   301  					bcR.Logger.Debug("Send queue is full, drop block request", "peer", peer.ID(), "height", request.Height)
   302  				}
   303  			case err := <-bcR.errorsCh:
   304  				peer := bcR.Switch.Peers().Get(err.peerID)
   305  				if peer != nil {
   306  					bcR.Switch.StopPeerForError(peer, err)
   307  				}
   308  
   309  			case <-statusUpdateTicker.C:
   310  				// ask for status updates
   311  				go bcR.BroadcastStatusRequest() // nolint: errcheck
   312  
   313  			}
   314  		}
   315  	}()
   316  
   317  FOR_LOOP:
   318  	for {
   319  		select {
   320  		case <-switchToConsensusTicker.C:
   321  			height, numPending, lenRequesters := bcR.pool.GetStatus()
   322  			outbound, inbound, _ := bcR.Switch.NumPeers()
   323  			bcR.Logger.Debug("Consensus ticker", "numPending", numPending, "total", lenRequesters,
   324  				"outbound", outbound, "inbound", inbound)
   325  			if bcR.pool.IsCaughtUp() {
   326  				bcR.Logger.Info("Time to switch to consensus reactor!", "height", height)
   327  				if err := bcR.pool.Stop(); err != nil {
   328  					bcR.Logger.Error("Error stopping pool", "err", err)
   329  				}
   330  				conR, ok := bcR.Switch.Reactor("CONSENSUS").(consensusReactor)
   331  				if ok {
   332  					conR.SwitchToConsensus(state, blocksSynced > 0 || stateSynced)
   333  				}
   334  				// else {
   335  				// should only happen during testing
   336  				// }
   337  
   338  				break FOR_LOOP
   339  			}
   340  
   341  		case <-trySyncTicker.C: // chan time
   342  			select {
   343  			case didProcessCh <- struct{}{}:
   344  			default:
   345  			}
   346  
   347  		case <-didProcessCh:
   348  			// NOTE: It is a subtle mistake to process more than a single block
   349  			// at a time (e.g. 10) here, because we only TrySend 1 request per
   350  			// loop.  The ratio mismatch can result in starving of blocks, a
   351  			// sudden burst of requests and responses, and repeat.
   352  			// Consequently, it is better to split these routines rather than
   353  			// coupling them as it's written here.  TODO uncouple from request
   354  			// routine.
   355  
   356  			// See if there are any blocks to sync.
   357  			first, second := bcR.pool.PeekTwoBlocks()
   358  			// bcR.Logger.Info("TrySync peeked", "first", first, "second", second)
   359  			if first == nil || second == nil {
   360  				// We need both to sync the first block.
   361  				continue FOR_LOOP
   362  			} else {
   363  				// Try again quickly next loop.
   364  				didProcessCh <- struct{}{}
   365  			}
   366  
   367  			firstParts := first.MakePartSet(types.BlockPartSizeBytes)
   368  			firstPartSetHeader := firstParts.Header()
   369  			firstID := types.BlockID{Hash: first.Hash(), PartSetHeader: firstPartSetHeader}
   370  			// Finally, verify the first block using the second's commit
   371  			// NOTE: we can probably make this more efficient, but note that calling
   372  			// first.Hash() doesn't verify the tx contents, so MakePartSet() is
   373  			// currently necessary.
   374  			err := state.Validators.VerifyCommitLight(chainID, firstID, first.Height, second.LastCommit)
   375  			if err == nil {
   376  				// validate the block before we persist it
   377  				err = bcR.blockExec.ValidateBlock(state, first.Round, first)
   378  			}
   379  
   380  			// If either of the checks failed we log the error and request for a new block
   381  			// at that height
   382  			if err != nil {
   383  				bcR.Logger.Error("Error in validation", "err", err)
   384  				peerID := bcR.pool.RedoRequest(first.Height)
   385  				peer := bcR.Switch.Peers().Get(peerID)
   386  				if peer != nil {
   387  					// NOTE: we've already removed the peer's request, but we
   388  					// still need to clean up the rest.
   389  					bcR.Switch.StopPeerForError(peer, fmt.Errorf("blockchainReactor validation error: %v", err))
   390  				}
   391  				peerID2 := bcR.pool.RedoRequest(second.Height)
   392  				peer2 := bcR.Switch.Peers().Get(peerID2)
   393  				if peer2 != nil && peer2 != peer {
   394  					// NOTE: we've already removed the peer's request, but we
   395  					// still need to clean up the rest.
   396  					bcR.Switch.StopPeerForError(peer2, fmt.Errorf("blockchainReactor validation error: %v", err))
   397  				}
   398  				continue FOR_LOOP
   399  			}
   400  
   401  			bcR.pool.PopRequest()
   402  
   403  			// TODO: batch saves so we dont persist to disk every block
   404  			bcR.store.SaveBlock(first, firstParts, second.LastCommit)
   405  
   406  			// TODO: same thing for app - but we would need a way to
   407  			// get the hash without persisting the state
   408  			state, _, err = bcR.blockExec.ApplyBlock(state, firstID, first, nil)
   409  			if err != nil {
   410  				// TODO This is bad, are we zombie?
   411  				panic(fmt.Sprintf("Failed to process committed block (%d:%X): %v", first.Height, first.Hash(), err))
   412  			}
   413  			blocksSynced++
   414  
   415  			if blocksSynced%100 == 0 {
   416  				lastRate = 0.9*lastRate + 0.1*(100/time.Since(lastHundred).Seconds())
   417  				bcR.Logger.Info("Fast Sync Rate", "height", bcR.pool.height,
   418  					"max_peer_height", bcR.pool.MaxPeerHeight(), "blocks/s", lastRate)
   419  				lastHundred = time.Now()
   420  			}
   421  
   422  			continue FOR_LOOP
   423  
   424  		case <-bcR.Quit():
   425  			break FOR_LOOP
   426  		}
   427  	}
   428  }
   429  
   430  // BroadcastStatusRequest broadcasts `BlockStore` base and height.
   431  func (bcR *BlockchainReactor) BroadcastStatusRequest() error {
   432  	bcR.Switch.BroadcastEnvelope(p2p.Envelope{
   433  		ChannelID: BlockchainChannel,
   434  		Message:   &bcproto.StatusRequest{},
   435  	})
   436  	return nil
   437  }