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:
Péter Szilágyi
2020-12-14 11:27:15 +02:00
committed by GitHub
parent 00d10e610f
commit 017831dd5b
74 changed files with 8246 additions and 3411 deletions

View File

@@ -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
}