eth: implement BlockRangeUpdate sending

This commit is contained in:
Felix Lange 2025-04-11 19:19:17 +02:00
parent 482f5cd2d8
commit a11358df21
2 changed files with 136 additions and 9 deletions

View file

@ -18,8 +18,10 @@ package eth
import ( import (
"errors" "errors"
"maps"
"math" "math"
"math/big" "math/big"
"slices"
"sync" "sync"
"sync/atomic" "sync/atomic"
"time" "time"
@ -47,6 +49,9 @@ const (
// The number is referenced from the size of tx pool. // The number is referenced from the size of tx pool.
txChanSize = 4096 txChanSize = 4096
// chainHeadChanSize is the size of channel listening to ChainHeadEvent.
chainHeadChanSize = 128
// txMaxBroadcastSize is the max size of a transaction that will be broadcasted. // txMaxBroadcastSize is the max size of a transaction that will be broadcasted.
// All transactions with a higher size will be announced and need to be fetched // All transactions with a higher size will be announced and need to be fetched
// by the peer. // by the peer.
@ -120,6 +125,7 @@ type handler struct {
eventMux *event.TypeMux eventMux *event.TypeMux
txsCh chan core.NewTxsEvent txsCh chan core.NewTxsEvent
txsSub event.Subscription txsSub event.Subscription
blockRange *blockRangeState
requiredBlocks map[uint64]common.Hash requiredBlocks map[uint64]common.Hash
@ -424,6 +430,11 @@ func (h *handler) Start(maxPeers int) {
h.txsSub = h.txpool.SubscribeTransactions(h.txsCh, false) h.txsSub = h.txpool.SubscribeTransactions(h.txsCh, false)
go h.txBroadcastLoop() go h.txBroadcastLoop()
// broadcast block range
h.wg.Add(1)
h.blockRange = newBlockRangeState(h.chain, h.eventMux)
go h.blockRangeLoop(h.blockRange)
// start sync handlers // start sync handlers
h.txFetcher.Start() h.txFetcher.Start()
@ -434,6 +445,7 @@ func (h *handler) Start(maxPeers int) {
func (h *handler) Stop() { func (h *handler) Stop() {
h.txsSub.Unsubscribe() // quits txBroadcastLoop h.txsSub.Unsubscribe() // quits txBroadcastLoop
h.blockRange.stop()
h.txFetcher.Stop() h.txFetcher.Stop()
h.downloader.Terminate() h.downloader.Terminate()
@ -555,3 +567,119 @@ func (h *handler) enableSyncedFeatures() {
h.snapSync.Store(false) h.snapSync.Store(false)
} }
} }
type blockRangeState struct {
prev eth.BlockRangeUpdatePacket
next eth.BlockRangeUpdatePacket
headCh chan core.ChainHeadEvent
headSub event.Subscription
syncSub *event.TypeMuxSubscription
}
func newBlockRangeState(chain *core.BlockChain, typeMux *event.TypeMux) *blockRangeState {
headCh := make(chan core.ChainHeadEvent, chainHeadChanSize)
headSub := chain.SubscribeChainHeadEvent(headCh)
syncSub := typeMux.Subscribe(downloader.StartEvent{}, downloader.DoneEvent{}, downloader.FailedEvent{})
st := &blockRangeState{
headCh: headCh,
headSub: headSub,
syncSub: syncSub,
}
st.update(chain, chain.CurrentBlock())
st.prev = st.next
return st
}
// blockRangeBroadcastLoop announces changes in locally-available block range to peers.
// The range to announce is the range that is available in the store, so it's not just
// about imported blocks.
func (h *handler) blockRangeLoop(st *blockRangeState) {
defer h.wg.Done()
for {
select {
case ev := <-st.syncSub.Chan():
if ev == nil {
continue
}
if _, ok := ev.Data.(downloader.StartEvent); ok && h.snapSync.Load() {
h.blockRangeWhileSnapSyncing(st)
}
case <-st.headCh:
st.update(h.chain, h.chain.CurrentBlock())
if st.shouldSend() {
h.broadcastBlockRange(st)
}
case <-st.headSub.Err():
return
}
}
}
// blockRangeWhileSnapSyncing announces block range updates during snap sync.
// Here we poll the CurrentSnapBlock on a timer and announce updates to it.
func (h *handler) blockRangeWhileSnapSyncing(st *blockRangeState) {
tick := time.NewTicker(1 * time.Minute)
defer tick.Stop()
for {
select {
case <-tick.C:
st.update(h.chain, h.chain.CurrentSnapBlock())
if st.shouldSend() {
h.broadcastBlockRange(st)
}
// back to processing head block updates when sync is done
case ev := <-st.syncSub.Chan():
if ev == nil {
continue
}
switch ev.Data.(type) {
case downloader.FailedEvent, downloader.DoneEvent:
return
}
// ignore head updates, but exit when the subscription ends
case <-st.headCh:
case <-st.headSub.Err():
return
}
}
}
// broadcastBlockRange sends a range update when one is due.
func (h *handler) broadcastBlockRange(state *blockRangeState) {
h.peers.lock.Lock()
peerlist := slices.Collect(maps.Values(h.peers.peers))
h.peers.lock.Unlock()
if len(peerlist) == 0 {
return
}
msg := state.next
log.Debug("Sending BlockRangeUpdate message", "peers", len(peerlist), "earliest", msg.EarliestBlock, "latest", msg.LatestBlock)
for _, p := range peerlist {
p.SendBlockRangeUpdate(msg)
}
state.prev = state.next
}
// update assigns the values of the next block range update from the chain.
func (st *blockRangeState) update(chain *core.BlockChain, latest *types.Header) {
earliest, _ := chain.HistoryPruningCutoff()
st.next.EarliestBlock = min(latest.Number.Uint64(), earliest)
st.next.LatestBlock = latest.Number.Uint64()
st.next.LatestBlockHash = latest.Hash()
}
// shouldSend decides whether it is time to send a block range update. We don't want to
// send these updates constantly, so they will usually only be sent every 32 blocks.
// However, there is a special case: if the range would move back, i.e. due to SetHead, we
// want to send it immediately.
func (st *blockRangeState) shouldSend() bool {
return st.next.LatestBlock < st.prev.LatestBlock ||
st.next.LatestBlock-st.prev.LatestBlock >= 32
}
func (st *blockRangeState) stop() {
st.syncSub.Unsubscribe()
st.headSub.Unsubscribe()
}

View file

@ -344,12 +344,11 @@ func (p *Peer) RequestTxs(hashes []common.Hash) error {
} }
// SendBlockRangeUpdate sends a notification about our available block range to the peer. // SendBlockRangeUpdate sends a notification about our available block range to the peer.
func (p *Peer) SendBlockRangeUpdate(earliestBlock, latestBlock uint64, latestBlockHash common.Hash) error { func (p *Peer) SendBlockRangeUpdate(msg BlockRangeUpdatePacket) error {
return p2p.Send(p.rw, BlockRangeUpdateMsg, &BlockRangeUpdatePacket{ if p.version < ETH69 {
EarliestBlock: earliestBlock, return nil
LatestBlock: latestBlock, }
LatestBlockHash: latestBlockHash, return p2p.Send(p.rw, BlockRangeUpdateMsg, &msg)
})
} }
// knownCache is a cache for known hashes. // knownCache is a cache for known hashes.