core, eth: split eth package, implement snap protocol (#21482)
This commit splits the eth package, separating the handling of eth and snap protocols. It also includes the capability to run snap sync (https://github.com/ethereum/devp2p/blob/master/caps/snap.md) , but does not enable it by default. Co-authored-by: Marius van der Wijden <m.vanderwijden@live.de> Co-authored-by: Martin Holst Swende <martin@swende.se>
This commit is contained in:
129
eth/sync.go
129
eth/sync.go
@@ -26,6 +26,7 @@ import (
|
||||
"github.com/ethereum/go-ethereum/core/rawdb"
|
||||
"github.com/ethereum/go-ethereum/core/types"
|
||||
"github.com/ethereum/go-ethereum/eth/downloader"
|
||||
"github.com/ethereum/go-ethereum/eth/protocols/eth"
|
||||
"github.com/ethereum/go-ethereum/log"
|
||||
"github.com/ethereum/go-ethereum/p2p/enode"
|
||||
)
|
||||
@@ -40,12 +41,12 @@ const (
|
||||
)
|
||||
|
||||
type txsync struct {
|
||||
p *peer
|
||||
p *eth.Peer
|
||||
txs []*types.Transaction
|
||||
}
|
||||
|
||||
// syncTransactions starts sending all currently pending transactions to the given peer.
|
||||
func (pm *ProtocolManager) syncTransactions(p *peer) {
|
||||
func (h *handler) syncTransactions(p *eth.Peer) {
|
||||
// Assemble the set of transaction to broadcast or announce to the remote
|
||||
// peer. Fun fact, this is quite an expensive operation as it needs to sort
|
||||
// the transactions if the sorting is not cached yet. However, with a random
|
||||
@@ -53,7 +54,7 @@ func (pm *ProtocolManager) syncTransactions(p *peer) {
|
||||
//
|
||||
// TODO(karalabe): Figure out if we could get away with random order somehow
|
||||
var txs types.Transactions
|
||||
pending, _ := pm.txpool.Pending()
|
||||
pending, _ := h.txpool.Pending()
|
||||
for _, batch := range pending {
|
||||
txs = append(txs, batch...)
|
||||
}
|
||||
@@ -63,7 +64,7 @@ func (pm *ProtocolManager) syncTransactions(p *peer) {
|
||||
// The eth/65 protocol introduces proper transaction announcements, so instead
|
||||
// of dripping transactions across multiple peers, just send the entire list as
|
||||
// an announcement and let the remote side decide what they need (likely nothing).
|
||||
if p.version >= eth65 {
|
||||
if p.Version() >= eth.ETH65 {
|
||||
hashes := make([]common.Hash, len(txs))
|
||||
for i, tx := range txs {
|
||||
hashes[i] = tx.Hash()
|
||||
@@ -73,8 +74,8 @@ func (pm *ProtocolManager) syncTransactions(p *peer) {
|
||||
}
|
||||
// Out of luck, peer is running legacy protocols, drop the txs over
|
||||
select {
|
||||
case pm.txsyncCh <- &txsync{p: p, txs: txs}:
|
||||
case <-pm.quitSync:
|
||||
case h.txsyncCh <- &txsync{p: p, txs: txs}:
|
||||
case <-h.quitSync:
|
||||
}
|
||||
}
|
||||
|
||||
@@ -82,8 +83,8 @@ func (pm *ProtocolManager) syncTransactions(p *peer) {
|
||||
// connection. When a new peer appears, we relay all currently pending
|
||||
// transactions. In order to minimise egress bandwidth usage, we send
|
||||
// the transactions in small packs to one peer at a time.
|
||||
func (pm *ProtocolManager) txsyncLoop64() {
|
||||
defer pm.wg.Done()
|
||||
func (h *handler) txsyncLoop64() {
|
||||
defer h.wg.Done()
|
||||
|
||||
var (
|
||||
pending = make(map[enode.ID]*txsync)
|
||||
@@ -94,7 +95,7 @@ func (pm *ProtocolManager) txsyncLoop64() {
|
||||
|
||||
// send starts a sending a pack of transactions from the sync.
|
||||
send := func(s *txsync) {
|
||||
if s.p.version >= eth65 {
|
||||
if s.p.Version() >= eth.ETH65 {
|
||||
panic("initial transaction syncer running on eth/65+")
|
||||
}
|
||||
// Fill pack with transactions up to the target size.
|
||||
@@ -108,14 +109,13 @@ func (pm *ProtocolManager) txsyncLoop64() {
|
||||
// Remove the transactions that will be sent.
|
||||
s.txs = s.txs[:copy(s.txs, s.txs[len(pack.txs):])]
|
||||
if len(s.txs) == 0 {
|
||||
delete(pending, s.p.ID())
|
||||
delete(pending, s.p.Peer.ID())
|
||||
}
|
||||
// Send the pack in the background.
|
||||
s.p.Log().Trace("Sending batch of transactions", "count", len(pack.txs), "bytes", size)
|
||||
sending = true
|
||||
go func() { done <- pack.p.SendTransactions64(pack.txs) }()
|
||||
go func() { done <- pack.p.SendTransactions(pack.txs) }()
|
||||
}
|
||||
|
||||
// pick chooses the next pending sync.
|
||||
pick := func() *txsync {
|
||||
if len(pending) == 0 {
|
||||
@@ -132,8 +132,8 @@ func (pm *ProtocolManager) txsyncLoop64() {
|
||||
|
||||
for {
|
||||
select {
|
||||
case s := <-pm.txsyncCh:
|
||||
pending[s.p.ID()] = s
|
||||
case s := <-h.txsyncCh:
|
||||
pending[s.p.Peer.ID()] = s
|
||||
if !sending {
|
||||
send(s)
|
||||
}
|
||||
@@ -142,13 +142,13 @@ func (pm *ProtocolManager) txsyncLoop64() {
|
||||
// Stop tracking peers that cause send failures.
|
||||
if err != nil {
|
||||
pack.p.Log().Debug("Transaction send failed", "err", err)
|
||||
delete(pending, pack.p.ID())
|
||||
delete(pending, pack.p.Peer.ID())
|
||||
}
|
||||
// Schedule the next send.
|
||||
if s := pick(); s != nil {
|
||||
send(s)
|
||||
}
|
||||
case <-pm.quitSync:
|
||||
case <-h.quitSync:
|
||||
return
|
||||
}
|
||||
}
|
||||
@@ -156,7 +156,7 @@ func (pm *ProtocolManager) txsyncLoop64() {
|
||||
|
||||
// chainSyncer coordinates blockchain sync components.
|
||||
type chainSyncer struct {
|
||||
pm *ProtocolManager
|
||||
handler *handler
|
||||
force *time.Timer
|
||||
forced bool // true when force timer fired
|
||||
peerEventCh chan struct{}
|
||||
@@ -166,15 +166,15 @@ type chainSyncer struct {
|
||||
// chainSyncOp is a scheduled sync operation.
|
||||
type chainSyncOp struct {
|
||||
mode downloader.SyncMode
|
||||
peer *peer
|
||||
peer *eth.Peer
|
||||
td *big.Int
|
||||
head common.Hash
|
||||
}
|
||||
|
||||
// newChainSyncer creates a chainSyncer.
|
||||
func newChainSyncer(pm *ProtocolManager) *chainSyncer {
|
||||
func newChainSyncer(handler *handler) *chainSyncer {
|
||||
return &chainSyncer{
|
||||
pm: pm,
|
||||
handler: handler,
|
||||
peerEventCh: make(chan struct{}),
|
||||
}
|
||||
}
|
||||
@@ -182,23 +182,24 @@ func newChainSyncer(pm *ProtocolManager) *chainSyncer {
|
||||
// handlePeerEvent notifies the syncer about a change in the peer set.
|
||||
// This is called for new peers and every time a peer announces a new
|
||||
// chain head.
|
||||
func (cs *chainSyncer) handlePeerEvent(p *peer) bool {
|
||||
func (cs *chainSyncer) handlePeerEvent(peer *eth.Peer) bool {
|
||||
select {
|
||||
case cs.peerEventCh <- struct{}{}:
|
||||
return true
|
||||
case <-cs.pm.quitSync:
|
||||
case <-cs.handler.quitSync:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
// loop runs in its own goroutine and launches the sync when necessary.
|
||||
func (cs *chainSyncer) loop() {
|
||||
defer cs.pm.wg.Done()
|
||||
defer cs.handler.wg.Done()
|
||||
|
||||
cs.pm.blockFetcher.Start()
|
||||
cs.pm.txFetcher.Start()
|
||||
defer cs.pm.blockFetcher.Stop()
|
||||
defer cs.pm.txFetcher.Stop()
|
||||
cs.handler.blockFetcher.Start()
|
||||
cs.handler.txFetcher.Start()
|
||||
defer cs.handler.blockFetcher.Stop()
|
||||
defer cs.handler.txFetcher.Stop()
|
||||
defer cs.handler.downloader.Terminate()
|
||||
|
||||
// The force timer lowers the peer count threshold down to one when it fires.
|
||||
// This ensures we'll always start sync even if there aren't enough peers.
|
||||
@@ -209,7 +210,6 @@ func (cs *chainSyncer) loop() {
|
||||
if op := cs.nextSyncOp(); op != nil {
|
||||
cs.startSync(op)
|
||||
}
|
||||
|
||||
select {
|
||||
case <-cs.peerEventCh:
|
||||
// Peer information changed, recheck.
|
||||
@@ -220,14 +220,13 @@ func (cs *chainSyncer) loop() {
|
||||
case <-cs.force.C:
|
||||
cs.forced = true
|
||||
|
||||
case <-cs.pm.quitSync:
|
||||
case <-cs.handler.quitSync:
|
||||
// Disable all insertion on the blockchain. This needs to happen before
|
||||
// terminating the downloader because the downloader waits for blockchain
|
||||
// inserts, and these can take a long time to finish.
|
||||
cs.pm.blockchain.StopInsert()
|
||||
cs.pm.downloader.Terminate()
|
||||
cs.handler.chain.StopInsert()
|
||||
cs.handler.downloader.Terminate()
|
||||
if cs.doneCh != nil {
|
||||
// Wait for the current sync to end.
|
||||
<-cs.doneCh
|
||||
}
|
||||
return
|
||||
@@ -245,19 +244,22 @@ func (cs *chainSyncer) nextSyncOp() *chainSyncOp {
|
||||
minPeers := defaultMinSyncPeers
|
||||
if cs.forced {
|
||||
minPeers = 1
|
||||
} else if minPeers > cs.pm.maxPeers {
|
||||
minPeers = cs.pm.maxPeers
|
||||
} else if minPeers > cs.handler.maxPeers {
|
||||
minPeers = cs.handler.maxPeers
|
||||
}
|
||||
if cs.pm.peers.Len() < minPeers {
|
||||
if cs.handler.peers.Len() < minPeers {
|
||||
return nil
|
||||
}
|
||||
|
||||
// We have enough peers, check TD.
|
||||
peer := cs.pm.peers.BestPeer()
|
||||
// We have enough peers, check TD
|
||||
peer := cs.handler.peers.ethPeerWithHighestTD()
|
||||
if peer == nil {
|
||||
return nil
|
||||
}
|
||||
mode, ourTD := cs.modeAndLocalHead()
|
||||
if mode == downloader.FastSync && atomic.LoadUint32(&cs.handler.snapSync) == 1 {
|
||||
// Fast sync via the snap protocol
|
||||
mode = downloader.SnapSync
|
||||
}
|
||||
op := peerToSyncOp(mode, peer)
|
||||
if op.td.Cmp(ourTD) <= 0 {
|
||||
return nil // We're in sync.
|
||||
@@ -265,42 +267,42 @@ func (cs *chainSyncer) nextSyncOp() *chainSyncOp {
|
||||
return op
|
||||
}
|
||||
|
||||
func peerToSyncOp(mode downloader.SyncMode, p *peer) *chainSyncOp {
|
||||
func peerToSyncOp(mode downloader.SyncMode, p *eth.Peer) *chainSyncOp {
|
||||
peerHead, peerTD := p.Head()
|
||||
return &chainSyncOp{mode: mode, peer: p, td: peerTD, head: peerHead}
|
||||
}
|
||||
|
||||
func (cs *chainSyncer) modeAndLocalHead() (downloader.SyncMode, *big.Int) {
|
||||
// If we're in fast sync mode, return that directly
|
||||
if atomic.LoadUint32(&cs.pm.fastSync) == 1 {
|
||||
block := cs.pm.blockchain.CurrentFastBlock()
|
||||
td := cs.pm.blockchain.GetTdByHash(block.Hash())
|
||||
if atomic.LoadUint32(&cs.handler.fastSync) == 1 {
|
||||
block := cs.handler.chain.CurrentFastBlock()
|
||||
td := cs.handler.chain.GetTdByHash(block.Hash())
|
||||
return downloader.FastSync, td
|
||||
}
|
||||
// We are probably in full sync, but we might have rewound to before the
|
||||
// fast sync pivot, check if we should reenable
|
||||
if pivot := rawdb.ReadLastPivotNumber(cs.pm.chaindb); pivot != nil {
|
||||
if head := cs.pm.blockchain.CurrentBlock(); head.NumberU64() < *pivot {
|
||||
block := cs.pm.blockchain.CurrentFastBlock()
|
||||
td := cs.pm.blockchain.GetTdByHash(block.Hash())
|
||||
if pivot := rawdb.ReadLastPivotNumber(cs.handler.database); pivot != nil {
|
||||
if head := cs.handler.chain.CurrentBlock(); head.NumberU64() < *pivot {
|
||||
block := cs.handler.chain.CurrentFastBlock()
|
||||
td := cs.handler.chain.GetTdByHash(block.Hash())
|
||||
return downloader.FastSync, td
|
||||
}
|
||||
}
|
||||
// Nope, we're really full syncing
|
||||
head := cs.pm.blockchain.CurrentHeader()
|
||||
td := cs.pm.blockchain.GetTd(head.Hash(), head.Number.Uint64())
|
||||
head := cs.handler.chain.CurrentHeader()
|
||||
td := cs.handler.chain.GetTd(head.Hash(), head.Number.Uint64())
|
||||
return downloader.FullSync, td
|
||||
}
|
||||
|
||||
// startSync launches doSync in a new goroutine.
|
||||
func (cs *chainSyncer) startSync(op *chainSyncOp) {
|
||||
cs.doneCh = make(chan error, 1)
|
||||
go func() { cs.doneCh <- cs.pm.doSync(op) }()
|
||||
go func() { cs.doneCh <- cs.handler.doSync(op) }()
|
||||
}
|
||||
|
||||
// doSync synchronizes the local blockchain with a remote peer.
|
||||
func (pm *ProtocolManager) doSync(op *chainSyncOp) error {
|
||||
if op.mode == downloader.FastSync {
|
||||
func (h *handler) doSync(op *chainSyncOp) error {
|
||||
if op.mode == downloader.FastSync || op.mode == downloader.SnapSync {
|
||||
// Before launch the fast sync, we have to ensure user uses the same
|
||||
// txlookup limit.
|
||||
// The main concern here is: during the fast sync Geth won't index the
|
||||
@@ -310,35 +312,33 @@ func (pm *ProtocolManager) doSync(op *chainSyncOp) error {
|
||||
// has been indexed. So here for the user-experience wise, it's non-optimal
|
||||
// that user can't change limit during the fast sync. If changed, Geth
|
||||
// will just blindly use the original one.
|
||||
limit := pm.blockchain.TxLookupLimit()
|
||||
if stored := rawdb.ReadFastTxLookupLimit(pm.chaindb); stored == nil {
|
||||
rawdb.WriteFastTxLookupLimit(pm.chaindb, limit)
|
||||
limit := h.chain.TxLookupLimit()
|
||||
if stored := rawdb.ReadFastTxLookupLimit(h.database); stored == nil {
|
||||
rawdb.WriteFastTxLookupLimit(h.database, limit)
|
||||
} else if *stored != limit {
|
||||
pm.blockchain.SetTxLookupLimit(*stored)
|
||||
h.chain.SetTxLookupLimit(*stored)
|
||||
log.Warn("Update txLookup limit", "provided", limit, "updated", *stored)
|
||||
}
|
||||
}
|
||||
// Run the sync cycle, and disable fast sync if we're past the pivot block
|
||||
err := pm.downloader.Synchronise(op.peer.id, op.head, op.td, op.mode)
|
||||
err := h.downloader.Synchronise(op.peer.ID(), op.head, op.td, op.mode)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if atomic.LoadUint32(&pm.fastSync) == 1 {
|
||||
if atomic.LoadUint32(&h.fastSync) == 1 {
|
||||
log.Info("Fast sync complete, auto disabling")
|
||||
atomic.StoreUint32(&pm.fastSync, 0)
|
||||
atomic.StoreUint32(&h.fastSync, 0)
|
||||
}
|
||||
|
||||
// If we've successfully finished a sync cycle and passed any required checkpoint,
|
||||
// enable accepting transactions from the network.
|
||||
head := pm.blockchain.CurrentBlock()
|
||||
if head.NumberU64() >= pm.checkpointNumber {
|
||||
head := h.chain.CurrentBlock()
|
||||
if head.NumberU64() >= h.checkpointNumber {
|
||||
// Checkpoint passed, sanity check the timestamp to have a fallback mechanism
|
||||
// for non-checkpointed (number = 0) private networks.
|
||||
if head.Time() >= uint64(time.Now().AddDate(0, -1, 0).Unix()) {
|
||||
atomic.StoreUint32(&pm.acceptTxs, 1)
|
||||
atomic.StoreUint32(&h.acceptTxs, 1)
|
||||
}
|
||||
}
|
||||
|
||||
if head.NumberU64() > 0 {
|
||||
// We've completed a sync cycle, notify all peers of new state. This path is
|
||||
// essential in star-topology networks where a gateway node needs to notify
|
||||
@@ -346,8 +346,7 @@ func (pm *ProtocolManager) doSync(op *chainSyncOp) error {
|
||||
// scenario will most often crop up in private and hackathon networks with
|
||||
// degenerate connectivity, but it should be healthy for the mainnet too to
|
||||
// more reliably update peers or the local TD state.
|
||||
pm.BroadcastBlock(head, false)
|
||||
h.BroadcastBlock(head, false)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
Reference in New Issue
Block a user