github.com/franono/tendermint@v0.32.2-0.20200527150959-749313264ce9/blockchain/v0/reactor.go (about)

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