eth/protocols/eth: pass initial block range into handshake

This commit is contained in:
Felix Lange 2025-04-25 21:22:52 +02:00
parent dd575769df
commit d8de9f4dc5
5 changed files with 55 additions and 36 deletions

View file

@ -259,7 +259,7 @@ func (h *handler) runEthPeer(peer *eth.Peer, handler eth.Handler) error {
} }
// Execute the Ethereum handshake // Execute the Ethereum handshake
if err := peer.Handshake(h.networkID, h.chain); err != nil { if err := peer.Handshake(h.networkID, h.chain, h.blockRange.currentRange()); err != nil {
peer.Log().Debug("Ethereum handshake failed", "err", err) peer.Log().Debug("Ethereum handshake failed", "err", err)
return err return err
} }
@ -571,7 +571,7 @@ func (h *handler) enableSyncedFeatures() {
// blockRangeState holds the state of the block range update broadcasting mechanism. // blockRangeState holds the state of the block range update broadcasting mechanism.
type blockRangeState struct { type blockRangeState struct {
prev eth.BlockRangeUpdatePacket prev eth.BlockRangeUpdatePacket
next eth.BlockRangeUpdatePacket next atomic.Pointer[eth.BlockRangeUpdatePacket]
headCh chan core.ChainHeadEvent headCh chan core.ChainHeadEvent
headSub event.Subscription headSub event.Subscription
syncSub *event.TypeMuxSubscription syncSub *event.TypeMuxSubscription
@ -587,7 +587,7 @@ func newBlockRangeState(chain *core.BlockChain, typeMux *event.TypeMux) *blockRa
syncSub: syncSub, syncSub: syncSub,
} }
st.update(chain, chain.CurrentBlock()) st.update(chain, chain.CurrentBlock())
st.prev = st.next st.prev = *st.next.Load()
return st return st
} }
@ -655,20 +655,22 @@ func (h *handler) broadcastBlockRange(state *blockRangeState) {
if len(peerlist) == 0 { if len(peerlist) == 0 {
return return
} }
msg := state.next msg := state.currentRange()
log.Debug("Sending BlockRangeUpdate", "peers", len(peerlist), "earliest", msg.EarliestBlock, "latest", msg.LatestBlock) log.Debug("Sending BlockRangeUpdate", "peers", len(peerlist), "earliest", msg.EarliestBlock, "latest", msg.LatestBlock)
for _, p := range peerlist { for _, p := range peerlist {
p.SendBlockRangeUpdate(msg) p.SendBlockRangeUpdate(msg)
} }
state.prev = state.next state.prev = *state.next.Load()
} }
// update assigns the values of the next block range update from the chain. // update assigns the values of the next block range update from the chain.
func (st *blockRangeState) update(chain *core.BlockChain, latest *types.Header) { func (st *blockRangeState) update(chain *core.BlockChain, latest *types.Header) {
earliest, _ := chain.HistoryPruningCutoff() earliest, _ := chain.HistoryPruningCutoff()
st.next.EarliestBlock = min(latest.Number.Uint64(), earliest) st.next.Store(&eth.BlockRangeUpdatePacket{
st.next.LatestBlock = latest.Number.Uint64() EarliestBlock: min(latest.Number.Uint64(), earliest),
st.next.LatestBlockHash = latest.Hash() LatestBlock: latest.Number.Uint64(),
LatestBlockHash: latest.Hash(),
})
} }
// shouldSend decides whether it is time to send a block range update. We don't want to // shouldSend decides whether it is time to send a block range update. We don't want to
@ -676,11 +678,18 @@ func (st *blockRangeState) update(chain *core.BlockChain, latest *types.Header)
// However, there is a special case: if the range would move back, i.e. due to SetHead, we // However, there is a special case: if the range would move back, i.e. due to SetHead, we
// want to send it immediately. // want to send it immediately.
func (st *blockRangeState) shouldSend() bool { func (st *blockRangeState) shouldSend() bool {
return st.next.LatestBlock < st.prev.LatestBlock || next := st.next.Load()
st.next.LatestBlock-st.prev.LatestBlock >= 32 return next.LatestBlock < st.prev.LatestBlock ||
next.LatestBlock-st.prev.LatestBlock >= 32
} }
func (st *blockRangeState) stop() { func (st *blockRangeState) stop() {
st.syncSub.Unsubscribe() st.syncSub.Unsubscribe()
st.headSub.Unsubscribe() st.headSub.Unsubscribe()
} }
// currentRange returns the current block range.
// This is safe to call from any goroutine.
func (st *blockRangeState) currentRange() eth.BlockRangeUpdatePacket {
return *st.next.Load()
}

View file

@ -256,7 +256,7 @@ func testRecvTransactions(t *testing.T, protocol uint) {
return eth.Handle((*ethHandler)(handler.handler), peer) return eth.Handle((*ethHandler)(handler.handler), peer)
}) })
// Run the handshake locally to avoid spinning up a source handler // Run the handshake locally to avoid spinning up a source handler
if err := src.Handshake(1, handler.chain); err != nil { if err := src.Handshake(1, handler.chain, eth.BlockRangeUpdatePacket{}); err != nil {
t.Fatalf("failed to run protocol handshake") t.Fatalf("failed to run protocol handshake")
} }
// Send the transaction to the sink and verify that it's added to the tx pool // Send the transaction to the sink and verify that it's added to the tx pool
@ -311,7 +311,7 @@ func testSendTransactions(t *testing.T, protocol uint) {
return eth.Handle((*ethHandler)(handler.handler), peer) return eth.Handle((*ethHandler)(handler.handler), peer)
}) })
// Run the handshake locally to avoid spinning up a source handler // Run the handshake locally to avoid spinning up a source handler
if err := sink.Handshake(1, handler.chain); err != nil { if err := sink.Handshake(1, handler.chain, eth.BlockRangeUpdatePacket{}); err != nil {
t.Fatalf("failed to run protocol handshake") t.Fatalf("failed to run protocol handshake")
} }
// After the handshake completes, the source handler should stream the sink // After the handshake completes, the source handler should stream the sink

View file

@ -36,10 +36,10 @@ const (
// Handshake executes the eth protocol handshake, negotiating version number, // Handshake executes the eth protocol handshake, negotiating version number,
// network IDs, difficulties, head and genesis blocks. // network IDs, difficulties, head and genesis blocks.
func (p *Peer) Handshake(networkID uint64, chain *core.BlockChain) error { func (p *Peer) Handshake(networkID uint64, chain *core.BlockChain, rangeMsg BlockRangeUpdatePacket) error {
switch p.version { switch p.version {
case ETH69: case ETH69:
return p.handshake69(networkID, chain) return p.handshake69(networkID, chain, rangeMsg)
case ETH68: case ETH68:
return p.handshake68(networkID, chain) return p.handshake68(networkID, chain)
default: default:
@ -92,14 +92,12 @@ func (p *Peer) readStatus68(networkID uint64, status *StatusPacket68, genesis co
return nil return nil
} }
func (p *Peer) handshake69(networkID uint64, chain *core.BlockChain) error { func (p *Peer) handshake69(networkID uint64, chain *core.BlockChain, rangeMsg BlockRangeUpdatePacket) error {
var ( var (
genesis = chain.Genesis() genesis = chain.Genesis()
latest = chain.CurrentBlock() latest = chain.CurrentBlock()
latestSnap = chain.CurrentSnapBlock() forkID = forkid.NewID(chain.Config(), genesis, latest.Number.Uint64(), latest.Time)
forkID = forkid.NewID(chain.Config(), genesis, latest.Number.Uint64(), latest.Time) forkFilter = forkid.NewFilter(chain)
forkFilter = forkid.NewFilter(chain)
earliest, _ = chain.HistoryPruningCutoff()
) )
errc := make(chan error, 2) errc := make(chan error, 2)
@ -109,14 +107,9 @@ func (p *Peer) handshake69(networkID uint64, chain *core.BlockChain) error {
NetworkID: networkID, NetworkID: networkID,
Genesis: genesis.Hash(), Genesis: genesis.Hash(),
ForkID: forkID, ForkID: forkID,
EarliestBlock: earliest, EarliestBlock: rangeMsg.EarliestBlock,
} LatestBlock: rangeMsg.LatestBlock,
if latestSnap != nil && latestSnap.Number.Uint64() > latest.Number.Uint64() { LatestBlockHash: rangeMsg.LatestBlockHash,
pkt.LatestBlock = latestSnap.Number.Uint64()
pkt.LatestBlockHash = latestSnap.Hash()
} else {
pkt.LatestBlock = latest.Number.Uint64()
pkt.LatestBlockHash = latest.Hash()
} }
errc <- p2p.Send(p.rw, StatusMsg, pkt) errc <- p2p.Send(p.rw, StatusMsg, pkt)
}() }()
@ -145,11 +138,15 @@ func (p *Peer) readStatus69(networkID uint64, status *StatusPacket69, genesis co
return fmt.Errorf("%w: %v", errForkIDRejected, err) return fmt.Errorf("%w: %v", errForkIDRejected, err)
} }
// Handle initial block range. // Handle initial block range.
p.lastRange.Store(&BlockRangeUpdatePacket{ initRange := &BlockRangeUpdatePacket{
EarliestBlock: status.EarliestBlock, EarliestBlock: status.EarliestBlock,
LatestBlock: status.LatestBlock, LatestBlock: status.LatestBlock,
LatestBlockHash: status.LatestBlockHash, LatestBlockHash: status.LatestBlockHash,
}) }
if err := initRange.Validate(); err != nil {
return fmt.Errorf("%w: %v", errInvalidBlockRange, err)
}
p.lastRange.Store(initRange)
return nil return nil
} }
@ -210,3 +207,14 @@ func markError(p *Peer, err error) {
m.peerError.Mark(1) m.peerError.Mark(1)
} }
} }
// Validate checks basic validity of a block range announcement.
func (p *BlockRangeUpdatePacket) Validate() error {
if p.EarliestBlock > p.LatestBlock {
return errors.New("earliest > latest")
}
if p.LatestBlockHash == (common.Hash{}) {
return errors.New("zero latest hash")
}
return nil
}

View file

@ -80,7 +80,7 @@ func testHandshake(t *testing.T, protocol uint) {
// Send the junk test with one peer, check the handshake failure // Send the junk test with one peer, check the handshake failure
go p2p.Send(app, test.code, test.data) go p2p.Send(app, test.code, test.data)
err := peer.Handshake(1, backend.chain) err := peer.Handshake(1, backend.chain, BlockRangeUpdatePacket{})
if err == nil { if err == nil {
t.Errorf("test %d: protocol returned nil error, want %q", i, test.want) t.Errorf("test %d: protocol returned nil error, want %q", i, test.want)
} else if !errors.Is(err, test.want) { } else if !errors.Is(err, test.want) {

View file

@ -67,13 +67,15 @@ const (
) )
var ( var (
errNoStatusMsg = errors.New("no status message")
errMsgTooLarge = errors.New("message too long") errMsgTooLarge = errors.New("message too long")
errInvalidMsgCode = errors.New("invalid message code") errInvalidMsgCode = errors.New("invalid message code")
errProtocolVersionMismatch = errors.New("protocol version mismatch") errProtocolVersionMismatch = errors.New("protocol version mismatch")
errNetworkIDMismatch = errors.New("network ID mismatch") // handshake errors
errGenesisMismatch = errors.New("genesis mismatch") errNoStatusMsg = errors.New("no status message")
errForkIDRejected = errors.New("fork ID rejected") errNetworkIDMismatch = errors.New("network ID mismatch")
errGenesisMismatch = errors.New("genesis mismatch")
errForkIDRejected = errors.New("fork ID rejected")
errInvalidBlockRange = errors.New("invalid block range in status")
) )
// Packet represents a p2p message in the `eth` protocol. // Packet represents a p2p message in the `eth` protocol.