github.com/vipernet-xyz/tm@v0.34.24/statesync/reactor.go (about)

     1  package statesync
     2  
     3  import (
     4  	"errors"
     5  	"sort"
     6  	"time"
     7  
     8  	"github.com/gogo/protobuf/proto"
     9  
    10  	abci "github.com/vipernet-xyz/tm/abci/types"
    11  	"github.com/vipernet-xyz/tm/config"
    12  	tmsync "github.com/vipernet-xyz/tm/libs/sync"
    13  	"github.com/vipernet-xyz/tm/p2p"
    14  	ssproto "github.com/vipernet-xyz/tm/proto/tendermint/statesync"
    15  	"github.com/vipernet-xyz/tm/proxy"
    16  	sm "github.com/vipernet-xyz/tm/state"
    17  	"github.com/vipernet-xyz/tm/types"
    18  )
    19  
    20  const (
    21  	// SnapshotChannel exchanges snapshot metadata
    22  	SnapshotChannel = byte(0x60)
    23  	// ChunkChannel exchanges chunk contents
    24  	ChunkChannel = byte(0x61)
    25  	// recentSnapshots is the number of recent snapshots to send and receive per peer.
    26  	recentSnapshots = 10
    27  )
    28  
    29  // Reactor handles state sync, both restoring snapshots for the local node and serving snapshots
    30  // for other nodes.
    31  type Reactor struct {
    32  	p2p.BaseReactor
    33  
    34  	cfg       config.StateSyncConfig
    35  	conn      proxy.AppConnSnapshot
    36  	connQuery proxy.AppConnQuery
    37  	tempDir   string
    38  
    39  	// This will only be set when a state sync is in progress. It is used to feed received
    40  	// snapshots and chunks into the sync.
    41  	mtx    tmsync.RWMutex
    42  	syncer *syncer
    43  }
    44  
    45  // NewReactor creates a new state sync reactor.
    46  func NewReactor(
    47  	cfg config.StateSyncConfig,
    48  	conn proxy.AppConnSnapshot,
    49  	connQuery proxy.AppConnQuery,
    50  	tempDir string,
    51  ) *Reactor {
    52  
    53  	r := &Reactor{
    54  		cfg:       cfg,
    55  		conn:      conn,
    56  		connQuery: connQuery,
    57  	}
    58  	r.BaseReactor = *p2p.NewBaseReactor("StateSync", r)
    59  
    60  	return r
    61  }
    62  
    63  // GetChannels implements p2p.Reactor.
    64  func (r *Reactor) GetChannels() []*p2p.ChannelDescriptor {
    65  	return []*p2p.ChannelDescriptor{
    66  		{
    67  			ID:                  SnapshotChannel,
    68  			Priority:            5,
    69  			SendQueueCapacity:   10,
    70  			RecvMessageCapacity: snapshotMsgSize,
    71  			MessageType:         &ssproto.Message{},
    72  		},
    73  		{
    74  			ID:                  ChunkChannel,
    75  			Priority:            3,
    76  			SendQueueCapacity:   10,
    77  			RecvMessageCapacity: chunkMsgSize,
    78  			MessageType:         &ssproto.Message{},
    79  		},
    80  	}
    81  }
    82  
    83  // OnStart implements p2p.Reactor.
    84  func (r *Reactor) OnStart() error {
    85  	return nil
    86  }
    87  
    88  // AddPeer implements p2p.Reactor.
    89  func (r *Reactor) AddPeer(peer p2p.Peer) {
    90  	r.mtx.RLock()
    91  	defer r.mtx.RUnlock()
    92  	if r.syncer != nil {
    93  		r.syncer.AddPeer(peer)
    94  	}
    95  }
    96  
    97  // RemovePeer implements p2p.Reactor.
    98  func (r *Reactor) RemovePeer(peer p2p.Peer, reason interface{}) {
    99  	r.mtx.RLock()
   100  	defer r.mtx.RUnlock()
   101  	if r.syncer != nil {
   102  		r.syncer.RemovePeer(peer)
   103  	}
   104  }
   105  
   106  // Receive implements p2p.Reactor.
   107  func (r *Reactor) ReceiveEnvelope(e p2p.Envelope) {
   108  	if !r.IsRunning() {
   109  		return
   110  	}
   111  
   112  	err := validateMsg(e.Message)
   113  	if err != nil {
   114  		r.Logger.Error("Invalid message", "peer", e.Src, "msg", e.Message, "err", err)
   115  		r.Switch.StopPeerForError(e.Src, err)
   116  		return
   117  	}
   118  
   119  	switch e.ChannelID {
   120  	case SnapshotChannel:
   121  		switch msg := e.Message.(type) {
   122  		case *ssproto.SnapshotsRequest:
   123  			snapshots, err := r.recentSnapshots(recentSnapshots)
   124  			if err != nil {
   125  				r.Logger.Error("Failed to fetch snapshots", "err", err)
   126  				return
   127  			}
   128  			for _, snapshot := range snapshots {
   129  				r.Logger.Debug("Advertising snapshot", "height", snapshot.Height,
   130  					"format", snapshot.Format, "peer", e.Src.ID())
   131  				p2p.SendEnvelopeShim(e.Src, p2p.Envelope{ //nolint: staticcheck
   132  					ChannelID: e.ChannelID,
   133  					Message: &ssproto.SnapshotsResponse{
   134  						Height:   snapshot.Height,
   135  						Format:   snapshot.Format,
   136  						Chunks:   snapshot.Chunks,
   137  						Hash:     snapshot.Hash,
   138  						Metadata: snapshot.Metadata,
   139  					},
   140  				}, r.Logger)
   141  			}
   142  
   143  		case *ssproto.SnapshotsResponse:
   144  			r.mtx.RLock()
   145  			defer r.mtx.RUnlock()
   146  			if r.syncer == nil {
   147  				r.Logger.Debug("Received unexpected snapshot, no state sync in progress")
   148  				return
   149  			}
   150  			r.Logger.Debug("Received snapshot", "height", msg.Height, "format", msg.Format, "peer", e.Src.ID())
   151  			_, err := r.syncer.AddSnapshot(e.Src, &snapshot{
   152  				Height:   msg.Height,
   153  				Format:   msg.Format,
   154  				Chunks:   msg.Chunks,
   155  				Hash:     msg.Hash,
   156  				Metadata: msg.Metadata,
   157  			})
   158  			// TODO: We may want to consider punishing the peer for certain errors
   159  			if err != nil {
   160  				r.Logger.Error("Failed to add snapshot", "height", msg.Height, "format", msg.Format,
   161  					"peer", e.Src.ID(), "err", err)
   162  				return
   163  			}
   164  
   165  		default:
   166  			r.Logger.Error("Received unknown message %T", msg)
   167  		}
   168  
   169  	case ChunkChannel:
   170  		switch msg := e.Message.(type) {
   171  		case *ssproto.ChunkRequest:
   172  			r.Logger.Debug("Received chunk request", "height", msg.Height, "format", msg.Format,
   173  				"chunk", msg.Index, "peer", e.Src.ID())
   174  			resp, err := r.conn.LoadSnapshotChunkSync(abci.RequestLoadSnapshotChunk{
   175  				Height: msg.Height,
   176  				Format: msg.Format,
   177  				Chunk:  msg.Index,
   178  			})
   179  			if err != nil {
   180  				r.Logger.Error("Failed to load chunk", "height", msg.Height, "format", msg.Format,
   181  					"chunk", msg.Index, "err", err)
   182  				return
   183  			}
   184  			r.Logger.Debug("Sending chunk", "height", msg.Height, "format", msg.Format,
   185  				"chunk", msg.Index, "peer", e.Src.ID())
   186  			p2p.SendEnvelopeShim(e.Src, p2p.Envelope{ //nolint: staticcheck
   187  				ChannelID: ChunkChannel,
   188  				Message: &ssproto.ChunkResponse{
   189  					Height:  msg.Height,
   190  					Format:  msg.Format,
   191  					Index:   msg.Index,
   192  					Chunk:   resp.Chunk,
   193  					Missing: resp.Chunk == nil,
   194  				},
   195  			}, r.Logger)
   196  
   197  		case *ssproto.ChunkResponse:
   198  			r.mtx.RLock()
   199  			defer r.mtx.RUnlock()
   200  			if r.syncer == nil {
   201  				r.Logger.Debug("Received unexpected chunk, no state sync in progress", "peer", e.Src.ID())
   202  				return
   203  			}
   204  			r.Logger.Debug("Received chunk, adding to sync", "height", msg.Height, "format", msg.Format,
   205  				"chunk", msg.Index, "peer", e.Src.ID())
   206  			_, err := r.syncer.AddChunk(&chunk{
   207  				Height: msg.Height,
   208  				Format: msg.Format,
   209  				Index:  msg.Index,
   210  				Chunk:  msg.Chunk,
   211  				Sender: e.Src.ID(),
   212  			})
   213  			if err != nil {
   214  				r.Logger.Error("Failed to add chunk", "height", msg.Height, "format", msg.Format,
   215  					"chunk", msg.Index, "err", err)
   216  				return
   217  			}
   218  
   219  		default:
   220  			r.Logger.Error("Received unknown message %T", msg)
   221  		}
   222  
   223  	default:
   224  		r.Logger.Error("Received message on invalid channel %x", e.ChannelID)
   225  	}
   226  }
   227  
   228  func (r *Reactor) Receive(chID byte, peer p2p.Peer, msgBytes []byte) {
   229  	msg := &ssproto.Message{}
   230  	err := proto.Unmarshal(msgBytes, msg)
   231  	if err != nil {
   232  		panic(err)
   233  	}
   234  	um, err := msg.Unwrap()
   235  	if err != nil {
   236  		panic(err)
   237  	}
   238  
   239  	r.ReceiveEnvelope(p2p.Envelope{
   240  		ChannelID: chID,
   241  		Src:       peer,
   242  		Message:   um,
   243  	})
   244  }
   245  
   246  // recentSnapshots fetches the n most recent snapshots from the app
   247  func (r *Reactor) recentSnapshots(n uint32) ([]*snapshot, error) {
   248  	resp, err := r.conn.ListSnapshotsSync(abci.RequestListSnapshots{})
   249  	if err != nil {
   250  		return nil, err
   251  	}
   252  	sort.Slice(resp.Snapshots, func(i, j int) bool {
   253  		a := resp.Snapshots[i]
   254  		b := resp.Snapshots[j]
   255  		switch {
   256  		case a.Height > b.Height:
   257  			return true
   258  		case a.Height == b.Height && a.Format > b.Format:
   259  			return true
   260  		default:
   261  			return false
   262  		}
   263  	})
   264  	snapshots := make([]*snapshot, 0, n)
   265  	for i, s := range resp.Snapshots {
   266  		if i >= recentSnapshots {
   267  			break
   268  		}
   269  		snapshots = append(snapshots, &snapshot{
   270  			Height:   s.Height,
   271  			Format:   s.Format,
   272  			Chunks:   s.Chunks,
   273  			Hash:     s.Hash,
   274  			Metadata: s.Metadata,
   275  		})
   276  	}
   277  	return snapshots, nil
   278  }
   279  
   280  // Sync runs a state sync, returning the new state and last commit at the snapshot height.
   281  // The caller must store the state and commit in the state database and block store.
   282  func (r *Reactor) Sync(stateProvider StateProvider, discoveryTime time.Duration) (sm.State, *types.Commit, error) {
   283  	r.mtx.Lock()
   284  	if r.syncer != nil {
   285  		r.mtx.Unlock()
   286  		return sm.State{}, nil, errors.New("a state sync is already in progress")
   287  	}
   288  	r.syncer = newSyncer(r.cfg, r.Logger, r.conn, r.connQuery, stateProvider, r.tempDir)
   289  	r.mtx.Unlock()
   290  
   291  	hook := func() {
   292  		r.Logger.Debug("Requesting snapshots from known peers")
   293  		// Request snapshots from all currently connected peers
   294  
   295  		r.Switch.BroadcastEnvelope(p2p.Envelope{
   296  			ChannelID: SnapshotChannel,
   297  			Message:   &ssproto.SnapshotsRequest{},
   298  		})
   299  	}
   300  
   301  	hook()
   302  
   303  	state, commit, err := r.syncer.SyncAny(discoveryTime, hook)
   304  
   305  	r.mtx.Lock()
   306  	r.syncer = nil
   307  	r.mtx.Unlock()
   308  	return state, commit, err
   309  }