all: remove notion of trusted checkpoints in the post-merge world (#27147)
* all: remove notion of trusted checkpoints in the post-merge world * light: remove unused function * eth/ethconfig, les: remove unused config option * les: make linter happy --------- Co-authored-by: Gary Rong <garyrong0905@gmail.com>
This commit is contained in:
co-authored by
Gary Rong
parent
d3ece3a07c
commit
1e556d220c
@@ -211,10 +211,6 @@ func New(stack *node.Node, config *ethconfig.Config) (*Ethereum, error) {
|
||||
|
||||
// Permit the downloader to use the trie cache allowance during fast sync
|
||||
cacheLimit := cacheConfig.TrieCleanLimit + cacheConfig.TrieDirtyLimit + cacheConfig.SnapshotLimit
|
||||
checkpoint := config.Checkpoint
|
||||
if checkpoint == nil {
|
||||
checkpoint = params.TrustedCheckpoints[eth.blockchain.Genesis().Hash()]
|
||||
}
|
||||
if eth.handler, err = newHandler(&handlerConfig{
|
||||
Database: chainDb,
|
||||
Chain: eth.blockchain,
|
||||
@@ -224,7 +220,6 @@ func New(stack *node.Node, config *ethconfig.Config) (*Ethereum, error) {
|
||||
Sync: config.SyncMode,
|
||||
BloomCache: uint64(cacheLimit),
|
||||
EventMux: eth.eventMux,
|
||||
Checkpoint: checkpoint,
|
||||
RequiredBlocks: config.RequiredBlocks,
|
||||
}); err != nil {
|
||||
return nil, err
|
||||
|
||||
@@ -101,10 +101,9 @@ type Downloader struct {
|
||||
mode atomic.Uint32 // Synchronisation mode defining the strategy used (per sync cycle), use d.getMode() to get the SyncMode
|
||||
mux *event.TypeMux // Event multiplexer to announce sync operation events
|
||||
|
||||
checkpoint uint64 // Checkpoint block number to enforce head against (e.g. snap sync)
|
||||
genesis uint64 // Genesis block number to limit sync to (e.g. light client CHT)
|
||||
queue *queue // Scheduler for selecting the hashes to download
|
||||
peers *peerSet // Set of active peers from which download can proceed
|
||||
genesis uint64 // Genesis block number to limit sync to (e.g. light client CHT)
|
||||
queue *queue // Scheduler for selecting the hashes to download
|
||||
peers *peerSet // Set of active peers from which download can proceed
|
||||
|
||||
stateDB ethdb.Database // Database to state sync into (and deduplicate via)
|
||||
|
||||
@@ -219,14 +218,13 @@ type BlockChain interface {
|
||||
}
|
||||
|
||||
// New creates a new downloader to fetch hashes and blocks from remote peers.
|
||||
func New(checkpoint uint64, stateDb ethdb.Database, mux *event.TypeMux, chain BlockChain, lightchain LightChain, dropPeer peerDropFn, success func()) *Downloader {
|
||||
func New(stateDb ethdb.Database, mux *event.TypeMux, chain BlockChain, lightchain LightChain, dropPeer peerDropFn, success func()) *Downloader {
|
||||
if lightchain == nil {
|
||||
lightchain = chain
|
||||
}
|
||||
dl := &Downloader{
|
||||
stateDB: stateDb,
|
||||
mux: mux,
|
||||
checkpoint: checkpoint,
|
||||
queue: newQueue(blockCacheMaxItems, blockCacheInitialItems),
|
||||
peers: newPeerSet(),
|
||||
blockchain: chain,
|
||||
@@ -593,11 +591,9 @@ func (d *Downloader) syncWithPeer(p *peerConnection, hash common.Hash, td, ttd *
|
||||
d.ancientLimit = 0
|
||||
}
|
||||
} else {
|
||||
// Legacy sync, use any hardcoded checkpoints or the best announcement
|
||||
// we have from the remote peer. TODO(karalabe): Drop this pathway.
|
||||
if d.checkpoint != 0 && d.checkpoint > fullMaxForkAncestry+1 {
|
||||
d.ancientLimit = d.checkpoint
|
||||
} else if height > fullMaxForkAncestry+1 {
|
||||
// Legacy sync, use the best announcement we have from the remote peer.
|
||||
// TODO(karalabe): Drop this pathway.
|
||||
if height > fullMaxForkAncestry+1 {
|
||||
d.ancientLimit = height - fullMaxForkAncestry - 1
|
||||
} else {
|
||||
d.ancientLimit = 0
|
||||
@@ -742,13 +738,10 @@ func (d *Downloader) fetchHead(p *peerConnection) (head *types.Header, pivot *ty
|
||||
if len(headers) == 0 || len(headers) > fetch {
|
||||
return nil, nil, fmt.Errorf("%w: returned headers %d != requested %d", errBadPeer, len(headers), fetch)
|
||||
}
|
||||
// The first header needs to be the head, validate against the checkpoint
|
||||
// and request. If only 1 header was returned, make sure there's no pivot
|
||||
// or there was not one requested.
|
||||
// The first header needs to be the head, validate against the request. If
|
||||
// only 1 header was returned, make sure there's no pivot or there was not
|
||||
// one requested.
|
||||
head = headers[0]
|
||||
if (mode == SnapSync || mode == LightSync) && head.Number.Uint64() < d.checkpoint {
|
||||
return nil, nil, fmt.Errorf("%w: remote head %d below checkpoint %d", errUnsyncedPeer, head.Number, d.checkpoint)
|
||||
}
|
||||
if len(headers) == 1 {
|
||||
if mode == SnapSync && head.Number.Uint64() > uint64(fsMinFullBlocks) {
|
||||
return nil, nil, fmt.Errorf("%w: no pivot included along head header", errBadPeer)
|
||||
|
||||
@@ -17,7 +17,6 @@
|
||||
package downloader
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"math/big"
|
||||
"os"
|
||||
@@ -82,7 +81,7 @@ func newTesterWithNotification(t *testing.T, success func()) *downloadTester {
|
||||
chain: chain,
|
||||
peers: make(map[string]*downloadTesterPeer),
|
||||
}
|
||||
tester.downloader = New(0, db, new(event.TypeMux), tester.chain, nil, tester.dropPeer, success)
|
||||
tester.downloader = New(db, new(event.TypeMux), tester.chain, nil, tester.dropPeer, success)
|
||||
return tester
|
||||
}
|
||||
|
||||
@@ -1409,44 +1408,6 @@ func TestRemoteHeaderRequestSpan(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
// Tests that peers below a pre-configured checkpoint block are prevented from
|
||||
// being fast-synced from, avoiding potential cheap eclipse attacks.
|
||||
func TestCheckpointEnforcement66Full(t *testing.T) { testCheckpointEnforcement(t, eth.ETH66, FullSync) }
|
||||
func TestCheckpointEnforcement66Snap(t *testing.T) { testCheckpointEnforcement(t, eth.ETH66, SnapSync) }
|
||||
func TestCheckpointEnforcement66Light(t *testing.T) {
|
||||
testCheckpointEnforcement(t, eth.ETH66, LightSync)
|
||||
}
|
||||
func TestCheckpointEnforcement67Full(t *testing.T) { testCheckpointEnforcement(t, eth.ETH67, FullSync) }
|
||||
func TestCheckpointEnforcement67Snap(t *testing.T) { testCheckpointEnforcement(t, eth.ETH67, SnapSync) }
|
||||
func TestCheckpointEnforcement67Light(t *testing.T) {
|
||||
testCheckpointEnforcement(t, eth.ETH67, LightSync)
|
||||
}
|
||||
|
||||
func testCheckpointEnforcement(t *testing.T, protocol uint, mode SyncMode) {
|
||||
// Create a new tester with a particular hard coded checkpoint block
|
||||
tester := newTester(t)
|
||||
defer tester.terminate()
|
||||
|
||||
tester.downloader.checkpoint = uint64(fsMinFullBlocks) + 256
|
||||
chain := testChainBase.shorten(int(tester.downloader.checkpoint) - 1)
|
||||
|
||||
// Attempt to sync with the peer and validate the result
|
||||
tester.newPeer("peer", protocol, chain.blocks[1:])
|
||||
|
||||
var expect error
|
||||
if mode == SnapSync || mode == LightSync {
|
||||
expect = errUnsyncedPeer
|
||||
}
|
||||
if err := tester.sync("peer", nil, mode); !errors.Is(err, expect) {
|
||||
t.Fatalf("block sync error mismatch: have %v, want %v", err, expect)
|
||||
}
|
||||
if mode == SnapSync || mode == LightSync {
|
||||
assertOwnChain(t, tester, 1)
|
||||
} else {
|
||||
assertOwnChain(t, tester, len(chain.blocks))
|
||||
}
|
||||
}
|
||||
|
||||
// Tests that peers below a pre-configured checkpoint block are prevented from
|
||||
// being fast-synced from, avoiding potential cheap eclipse attacks.
|
||||
func TestBeaconSync66Full(t *testing.T) { testBeaconSync(t, eth.ETH66, FullSync) }
|
||||
|
||||
+6
-13
@@ -141,13 +141,12 @@ type Config struct {
|
||||
RequiredBlocks map[uint64]common.Hash `toml:"-"`
|
||||
|
||||
// Light client options
|
||||
LightServ int `toml:",omitempty"` // Maximum percentage of time allowed for serving LES requests
|
||||
LightIngress int `toml:",omitempty"` // Incoming bandwidth limit for light servers
|
||||
LightEgress int `toml:",omitempty"` // Outgoing bandwidth limit for light servers
|
||||
LightPeers int `toml:",omitempty"` // Maximum number of LES client peers
|
||||
LightNoPrune bool `toml:",omitempty"` // Whether to disable light chain pruning
|
||||
LightNoSyncServe bool `toml:",omitempty"` // Whether to serve light clients before syncing
|
||||
SyncFromCheckpoint bool `toml:",omitempty"` // Whether to sync the header chain from the configured checkpoint
|
||||
LightServ int `toml:",omitempty"` // Maximum percentage of time allowed for serving LES requests
|
||||
LightIngress int `toml:",omitempty"` // Incoming bandwidth limit for light servers
|
||||
LightEgress int `toml:",omitempty"` // Outgoing bandwidth limit for light servers
|
||||
LightPeers int `toml:",omitempty"` // Maximum number of LES client peers
|
||||
LightNoPrune bool `toml:",omitempty"` // Whether to disable light chain pruning
|
||||
LightNoSyncServe bool `toml:",omitempty"` // Whether to serve light clients before syncing
|
||||
|
||||
// Ultra Light client options
|
||||
UltraLightServers []string `toml:",omitempty"` // List of trusted ultra light servers
|
||||
@@ -199,12 +198,6 @@ type Config struct {
|
||||
// send-transaction variants. The unit is ether.
|
||||
RPCTxFeeCap float64
|
||||
|
||||
// Checkpoint is a hardcoded checkpoint which can be nil.
|
||||
Checkpoint *params.TrustedCheckpoint `toml:",omitempty"`
|
||||
|
||||
// CheckpointOracle is the configuration for checkpoint oracle.
|
||||
CheckpointOracle *params.CheckpointOracleConfig `toml:",omitempty"`
|
||||
|
||||
// OverrideShanghai (TODO: remove after the fork)
|
||||
OverrideShanghai *uint64 `toml:",omitempty"`
|
||||
}
|
||||
|
||||
@@ -12,7 +12,6 @@ import (
|
||||
"github.com/ethereum/go-ethereum/eth/downloader"
|
||||
"github.com/ethereum/go-ethereum/eth/gasprice"
|
||||
"github.com/ethereum/go-ethereum/miner"
|
||||
"github.com/ethereum/go-ethereum/params"
|
||||
)
|
||||
|
||||
// MarshalTOML marshals as TOML.
|
||||
@@ -33,7 +32,6 @@ func (c Config) MarshalTOML() (interface{}, error) {
|
||||
LightPeers int `toml:",omitempty"`
|
||||
LightNoPrune bool `toml:",omitempty"`
|
||||
LightNoSyncServe bool `toml:",omitempty"`
|
||||
SyncFromCheckpoint bool `toml:",omitempty"`
|
||||
UltraLightServers []string `toml:",omitempty"`
|
||||
UltraLightFraction int `toml:",omitempty"`
|
||||
UltraLightOnlyAnnounce bool `toml:",omitempty"`
|
||||
@@ -58,9 +56,7 @@ func (c Config) MarshalTOML() (interface{}, error) {
|
||||
RPCGasCap uint64
|
||||
RPCEVMTimeout time.Duration
|
||||
RPCTxFeeCap float64
|
||||
Checkpoint *params.TrustedCheckpoint `toml:",omitempty"`
|
||||
CheckpointOracle *params.CheckpointOracleConfig `toml:",omitempty"`
|
||||
OverrideShanghai *uint64 `toml:",omitempty"`
|
||||
OverrideShanghai *uint64 `toml:",omitempty"`
|
||||
}
|
||||
var enc Config
|
||||
enc.Genesis = c.Genesis
|
||||
@@ -78,7 +74,6 @@ func (c Config) MarshalTOML() (interface{}, error) {
|
||||
enc.LightPeers = c.LightPeers
|
||||
enc.LightNoPrune = c.LightNoPrune
|
||||
enc.LightNoSyncServe = c.LightNoSyncServe
|
||||
enc.SyncFromCheckpoint = c.SyncFromCheckpoint
|
||||
enc.UltraLightServers = c.UltraLightServers
|
||||
enc.UltraLightFraction = c.UltraLightFraction
|
||||
enc.UltraLightOnlyAnnounce = c.UltraLightOnlyAnnounce
|
||||
@@ -103,8 +98,6 @@ func (c Config) MarshalTOML() (interface{}, error) {
|
||||
enc.RPCGasCap = c.RPCGasCap
|
||||
enc.RPCEVMTimeout = c.RPCEVMTimeout
|
||||
enc.RPCTxFeeCap = c.RPCTxFeeCap
|
||||
enc.Checkpoint = c.Checkpoint
|
||||
enc.CheckpointOracle = c.CheckpointOracle
|
||||
enc.OverrideShanghai = c.OverrideShanghai
|
||||
return &enc, nil
|
||||
}
|
||||
@@ -127,7 +120,6 @@ func (c *Config) UnmarshalTOML(unmarshal func(interface{}) error) error {
|
||||
LightPeers *int `toml:",omitempty"`
|
||||
LightNoPrune *bool `toml:",omitempty"`
|
||||
LightNoSyncServe *bool `toml:",omitempty"`
|
||||
SyncFromCheckpoint *bool `toml:",omitempty"`
|
||||
UltraLightServers []string `toml:",omitempty"`
|
||||
UltraLightFraction *int `toml:",omitempty"`
|
||||
UltraLightOnlyAnnounce *bool `toml:",omitempty"`
|
||||
@@ -152,9 +144,7 @@ func (c *Config) UnmarshalTOML(unmarshal func(interface{}) error) error {
|
||||
RPCGasCap *uint64
|
||||
RPCEVMTimeout *time.Duration
|
||||
RPCTxFeeCap *float64
|
||||
Checkpoint *params.TrustedCheckpoint `toml:",omitempty"`
|
||||
CheckpointOracle *params.CheckpointOracleConfig `toml:",omitempty"`
|
||||
OverrideShanghai *uint64 `toml:",omitempty"`
|
||||
OverrideShanghai *uint64 `toml:",omitempty"`
|
||||
}
|
||||
var dec Config
|
||||
if err := unmarshal(&dec); err != nil {
|
||||
@@ -205,9 +195,6 @@ func (c *Config) UnmarshalTOML(unmarshal func(interface{}) error) error {
|
||||
if dec.LightNoSyncServe != nil {
|
||||
c.LightNoSyncServe = *dec.LightNoSyncServe
|
||||
}
|
||||
if dec.SyncFromCheckpoint != nil {
|
||||
c.SyncFromCheckpoint = *dec.SyncFromCheckpoint
|
||||
}
|
||||
if dec.UltraLightServers != nil {
|
||||
c.UltraLightServers = dec.UltraLightServers
|
||||
}
|
||||
@@ -280,12 +267,6 @@ func (c *Config) UnmarshalTOML(unmarshal func(interface{}) error) error {
|
||||
if dec.RPCTxFeeCap != nil {
|
||||
c.RPCTxFeeCap = *dec.RPCTxFeeCap
|
||||
}
|
||||
if dec.Checkpoint != nil {
|
||||
c.Checkpoint = dec.Checkpoint
|
||||
}
|
||||
if dec.CheckpointOracle != nil {
|
||||
c.CheckpointOracle = dec.CheckpointOracle
|
||||
}
|
||||
if dec.OverrideShanghai != nil {
|
||||
c.OverrideShanghai = dec.OverrideShanghai
|
||||
}
|
||||
|
||||
+13
-92
@@ -38,7 +38,6 @@ import (
|
||||
"github.com/ethereum/go-ethereum/event"
|
||||
"github.com/ethereum/go-ethereum/log"
|
||||
"github.com/ethereum/go-ethereum/p2p"
|
||||
"github.com/ethereum/go-ethereum/params"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -77,16 +76,15 @@ type txPool interface {
|
||||
// handlerConfig is the collection of initialization parameters to create a full
|
||||
// node network handler.
|
||||
type handlerConfig struct {
|
||||
Database ethdb.Database // Database for direct sync insertions
|
||||
Chain *core.BlockChain // Blockchain to serve data from
|
||||
TxPool txPool // Transaction pool to propagate from
|
||||
Merger *consensus.Merger // The manager for eth1/2 transition
|
||||
Network uint64 // Network identifier to adfvertise
|
||||
Sync downloader.SyncMode // Whether to snap or full sync
|
||||
BloomCache uint64 // Megabytes to alloc for snap sync bloom
|
||||
EventMux *event.TypeMux // Legacy event mux, deprecate for `feed`
|
||||
Checkpoint *params.TrustedCheckpoint // Hard coded checkpoint for sync challenges
|
||||
RequiredBlocks map[uint64]common.Hash // Hard coded map of required block hashes for sync challenges
|
||||
Database ethdb.Database // Database for direct sync insertions
|
||||
Chain *core.BlockChain // Blockchain to serve data from
|
||||
TxPool txPool // Transaction pool to propagate from
|
||||
Merger *consensus.Merger // The manager for eth1/2 transition
|
||||
Network uint64 // Network identifier to adfvertise
|
||||
Sync downloader.SyncMode // Whether to snap or full sync
|
||||
BloomCache uint64 // Megabytes to alloc for snap sync bloom
|
||||
EventMux *event.TypeMux // Legacy event mux, deprecate for `feed`
|
||||
RequiredBlocks map[uint64]common.Hash // Hard coded map of required block hashes for sync challenges
|
||||
}
|
||||
|
||||
type handler struct {
|
||||
@@ -96,9 +94,6 @@ type handler struct {
|
||||
snapSync uint32 // Flag whether snap sync is enabled (gets disabled if we already have blocks)
|
||||
acceptTxs uint32 // Flag whether we're considered synchronised (enables transaction processing)
|
||||
|
||||
checkpointNumber uint64 // Block number for the sync progress validator to cross reference
|
||||
checkpointHash common.Hash // Block hash for the sync progress validator to cross reference
|
||||
|
||||
database ethdb.Database
|
||||
txpool txPool
|
||||
chain *core.BlockChain
|
||||
@@ -166,11 +161,6 @@ func newHandler(config *handlerConfig) (*handler, error) {
|
||||
h.snapSync = uint32(1)
|
||||
}
|
||||
}
|
||||
// If we have trusted checkpoints, enforce them on the chain
|
||||
if config.Checkpoint != nil {
|
||||
h.checkpointNumber = (config.Checkpoint.SectionIndex+1)*params.CHTFrequency - 1
|
||||
h.checkpointHash = config.Checkpoint.SectionHead
|
||||
}
|
||||
// If sync succeeds, pass a callback to potentially disable snap sync mode
|
||||
// and enable transaction propagation.
|
||||
success := func() {
|
||||
@@ -180,19 +170,12 @@ func newHandler(config *handlerConfig) (*handler, error) {
|
||||
log.Info("Snap sync complete, auto disabling")
|
||||
atomic.StoreUint32(&h.snapSync, 0)
|
||||
}
|
||||
// If we've successfully finished a sync cycle and passed any required
|
||||
// checkpoint, enable accepting transactions from the network
|
||||
head := h.chain.CurrentBlock()
|
||||
if head.Number.Uint64() >= 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(&h.acceptTxs, 1)
|
||||
}
|
||||
}
|
||||
// If we've successfully finished a sync cycle, accept transactions from
|
||||
// the network
|
||||
atomic.StoreUint32(&h.acceptTxs, 1)
|
||||
}
|
||||
// Construct the downloader (long sync)
|
||||
h.downloader = downloader.New(h.checkpointNumber, config.Database, h.eventMux, h.chain, nil, h.removePeer, success)
|
||||
h.downloader = downloader.New(config.Database, h.eventMux, h.chain, nil, h.removePeer, success)
|
||||
if ttd := h.chain.Config().TerminalTotalDifficulty; ttd != nil {
|
||||
if h.chain.Config().TerminalTotalDifficultyPassed {
|
||||
log.Info("Chain post-merge, sync via beacon client")
|
||||
@@ -244,16 +227,6 @@ func newHandler(config *handlerConfig) (*handler, error) {
|
||||
log.Warn("Unexpected insertion activity", ctx...)
|
||||
return 0, errors.New("unexpected behavior after transition")
|
||||
}
|
||||
// If sync hasn't reached the checkpoint yet, deny importing weird blocks.
|
||||
//
|
||||
// Ideally we would also compare the head block's timestamp and similarly reject
|
||||
// the propagated block if the head is too old. Unfortunately there is a corner
|
||||
// case when starting new networks, where the genesis might be ancient (0 unix)
|
||||
// which would prevent full nodes from accepting it.
|
||||
if h.chain.CurrentBlock().Number.Uint64() < h.checkpointNumber {
|
||||
log.Warn("Unsynced yet, discarded propagated block", "number", blocks[0].Number(), "hash", blocks[0].Hash())
|
||||
return 0, nil
|
||||
}
|
||||
// If snap sync is running, deny importing weird blocks. This is a problematic
|
||||
// clause when starting up a new network, because snap-syncing miners might not
|
||||
// accept each others' blocks until a restart. Unfortunately we haven't figured
|
||||
@@ -387,58 +360,6 @@ func (h *handler) runEthPeer(peer *eth.Peer, handler eth.Handler) error {
|
||||
dead := make(chan struct{})
|
||||
defer close(dead)
|
||||
|
||||
// If we have a trusted CHT, reject all peers below that (avoid fast sync eclipse)
|
||||
if h.checkpointHash != (common.Hash{}) {
|
||||
// Request the peer's checkpoint header for chain height/weight validation
|
||||
resCh := make(chan *eth.Response)
|
||||
|
||||
req, err := peer.RequestHeadersByNumber(h.checkpointNumber, 1, 0, false, resCh)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// Start a timer to disconnect if the peer doesn't reply in time
|
||||
go func() {
|
||||
// Ensure the request gets cancelled in case of error/drop
|
||||
defer req.Close()
|
||||
|
||||
timeout := time.NewTimer(syncChallengeTimeout)
|
||||
defer timeout.Stop()
|
||||
|
||||
select {
|
||||
case res := <-resCh:
|
||||
headers := ([]*types.Header)(*res.Res.(*eth.BlockHeadersPacket))
|
||||
if len(headers) == 0 {
|
||||
// If we're doing a snap sync, we must enforce the checkpoint
|
||||
// block to avoid eclipse attacks. Unsynced nodes are welcome
|
||||
// to connect after we're done joining the network.
|
||||
if atomic.LoadUint32(&h.snapSync) == 1 {
|
||||
peer.Log().Warn("Dropping unsynced node during sync", "addr", peer.RemoteAddr(), "type", peer.Name())
|
||||
res.Done <- errors.New("unsynced node cannot serve sync")
|
||||
return
|
||||
}
|
||||
res.Done <- nil
|
||||
return
|
||||
}
|
||||
// Validate the header and either drop the peer or continue
|
||||
if len(headers) > 1 {
|
||||
res.Done <- errors.New("too many headers in checkpoint response")
|
||||
return
|
||||
}
|
||||
if headers[0].Hash() != h.checkpointHash {
|
||||
res.Done <- errors.New("checkpoint hash mismatch")
|
||||
return
|
||||
}
|
||||
res.Done <- nil
|
||||
|
||||
case <-timeout.C:
|
||||
peer.Log().Warn("Checkpoint challenge timed out, dropping", "addr", peer.RemoteAddr(), "type", peer.Name())
|
||||
h.removePeer(peer.ID())
|
||||
|
||||
case <-dead:
|
||||
// Peer handler terminated, abort all goroutines
|
||||
}
|
||||
}()
|
||||
}
|
||||
// If we have any explicit peer required block hashes, request them
|
||||
for number, hash := range h.requiredBlocks {
|
||||
resCh := make(chan *eth.Response)
|
||||
|
||||
@@ -19,8 +19,6 @@ package eth
|
||||
import (
|
||||
"fmt"
|
||||
"math/big"
|
||||
"math/rand"
|
||||
"sync/atomic"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
@@ -38,7 +36,6 @@ import (
|
||||
"github.com/ethereum/go-ethereum/p2p"
|
||||
"github.com/ethereum/go-ethereum/p2p/enode"
|
||||
"github.com/ethereum/go-ethereum/params"
|
||||
"github.com/ethereum/go-ethereum/rlp"
|
||||
)
|
||||
|
||||
// testEthHandler is a mock event handler to listen for inbound network requests
|
||||
@@ -459,148 +456,6 @@ func testTransactionPropagation(t *testing.T, protocol uint) {
|
||||
}
|
||||
}
|
||||
|
||||
// Tests that post eth protocol handshake, clients perform a mutual checkpoint
|
||||
// challenge to validate each other's chains. Hash mismatches, or missing ones
|
||||
// during a fast sync should lead to the peer getting dropped.
|
||||
func TestCheckpointChallenge(t *testing.T) {
|
||||
tests := []struct {
|
||||
syncmode downloader.SyncMode
|
||||
checkpoint bool
|
||||
timeout bool
|
||||
empty bool
|
||||
match bool
|
||||
drop bool
|
||||
}{
|
||||
// If checkpointing is not enabled locally, don't challenge and don't drop
|
||||
{downloader.FullSync, false, false, false, false, false},
|
||||
{downloader.SnapSync, false, false, false, false, false},
|
||||
|
||||
// If checkpointing is enabled locally and remote response is empty, only drop during fast sync
|
||||
{downloader.FullSync, true, false, true, false, false},
|
||||
{downloader.SnapSync, true, false, true, false, true}, // Special case, fast sync, unsynced peer
|
||||
|
||||
// If checkpointing is enabled locally and remote response mismatches, always drop
|
||||
{downloader.FullSync, true, false, false, false, true},
|
||||
{downloader.SnapSync, true, false, false, false, true},
|
||||
|
||||
// If checkpointing is enabled locally and remote response matches, never drop
|
||||
{downloader.FullSync, true, false, false, true, false},
|
||||
{downloader.SnapSync, true, false, false, true, false},
|
||||
|
||||
// If checkpointing is enabled locally and remote times out, always drop
|
||||
{downloader.FullSync, true, true, false, true, true},
|
||||
{downloader.SnapSync, true, true, false, true, true},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(fmt.Sprintf("sync %v checkpoint %v timeout %v empty %v match %v", tt.syncmode, tt.checkpoint, tt.timeout, tt.empty, tt.match), func(t *testing.T) {
|
||||
testCheckpointChallenge(t, tt.syncmode, tt.checkpoint, tt.timeout, tt.empty, tt.match, tt.drop)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func testCheckpointChallenge(t *testing.T, syncmode downloader.SyncMode, checkpoint bool, timeout bool, empty bool, match bool, drop bool) {
|
||||
// Reduce the checkpoint handshake challenge timeout
|
||||
defer func(old time.Duration) { syncChallengeTimeout = old }(syncChallengeTimeout)
|
||||
syncChallengeTimeout = 250 * time.Millisecond
|
||||
|
||||
// Create a test handler and inject a CHT into it. The injection is a bit
|
||||
// ugly, but it beats creating everything manually just to avoid reaching
|
||||
// into the internals a bit.
|
||||
handler := newTestHandler()
|
||||
defer handler.close()
|
||||
|
||||
if syncmode == downloader.SnapSync {
|
||||
atomic.StoreUint32(&handler.handler.snapSync, 1)
|
||||
} else {
|
||||
atomic.StoreUint32(&handler.handler.snapSync, 0)
|
||||
}
|
||||
var response *types.Header
|
||||
if checkpoint {
|
||||
number := (uint64(rand.Intn(500))+1)*params.CHTFrequency - 1
|
||||
response = &types.Header{Number: big.NewInt(int64(number)), Extra: []byte("valid")}
|
||||
|
||||
handler.handler.checkpointNumber = number
|
||||
handler.handler.checkpointHash = response.Hash()
|
||||
}
|
||||
|
||||
// Create a challenger peer and a challenged one.
|
||||
p2pLocal, p2pRemote := p2p.MsgPipe()
|
||||
defer p2pLocal.Close()
|
||||
defer p2pRemote.Close()
|
||||
|
||||
local := eth.NewPeer(eth.ETH66, p2p.NewPeerPipe(enode.ID{1}, "", nil, p2pLocal), p2pLocal, handler.txpool)
|
||||
remote := eth.NewPeer(eth.ETH66, p2p.NewPeerPipe(enode.ID{2}, "", nil, p2pRemote), p2pRemote, handler.txpool)
|
||||
defer local.Close()
|
||||
defer remote.Close()
|
||||
|
||||
handlerDone := make(chan struct{})
|
||||
go func() {
|
||||
defer close(handlerDone)
|
||||
handler.handler.runEthPeer(local, func(peer *eth.Peer) error {
|
||||
return eth.Handle((*ethHandler)(handler.handler), peer)
|
||||
})
|
||||
}()
|
||||
|
||||
// Run the handshake locally to avoid spinning up a remote handler.
|
||||
var (
|
||||
genesis = handler.chain.Genesis()
|
||||
head = handler.chain.CurrentBlock()
|
||||
td = handler.chain.GetTd(head.Hash(), head.Number.Uint64())
|
||||
)
|
||||
if err := remote.Handshake(1, td, head.Hash(), genesis.Hash(), forkid.NewIDWithChain(handler.chain), forkid.NewFilter(handler.chain)); err != nil {
|
||||
t.Fatalf("failed to run protocol handshake")
|
||||
}
|
||||
// Connect a new peer and check that we receive the checkpoint challenge.
|
||||
if checkpoint {
|
||||
msg, err := p2pRemote.ReadMsg()
|
||||
if err != nil {
|
||||
t.Fatalf("failed to read checkpoint challenge: %v", err)
|
||||
}
|
||||
request := new(eth.GetBlockHeadersPacket66)
|
||||
if err := msg.Decode(request); err != nil {
|
||||
t.Fatalf("failed to decode checkpoint challenge: %v", err)
|
||||
}
|
||||
query := request.GetBlockHeadersPacket
|
||||
if query.Origin.Number != response.Number.Uint64() || query.Amount != 1 || query.Skip != 0 || query.Reverse {
|
||||
t.Fatalf("challenge mismatch: have [%d, %d, %d, %v] want [%d, %d, %d, %v]",
|
||||
query.Origin.Number, query.Amount, query.Skip, query.Reverse,
|
||||
response.Number.Uint64(), 1, 0, false)
|
||||
}
|
||||
// Create a block to reply to the challenge if no timeout is simulated.
|
||||
if !timeout {
|
||||
if empty {
|
||||
if err := remote.ReplyBlockHeadersRLP(request.RequestId, []rlp.RawValue{}); err != nil {
|
||||
t.Fatalf("failed to answer challenge: %v", err)
|
||||
}
|
||||
} else if match {
|
||||
responseRlp, _ := rlp.EncodeToBytes(response)
|
||||
if err := remote.ReplyBlockHeadersRLP(request.RequestId, []rlp.RawValue{responseRlp}); err != nil {
|
||||
t.Fatalf("failed to answer challenge: %v", err)
|
||||
}
|
||||
} else {
|
||||
responseRlp, _ := rlp.EncodeToBytes(&types.Header{Number: response.Number})
|
||||
if err := remote.ReplyBlockHeadersRLP(request.RequestId, []rlp.RawValue{responseRlp}); err != nil {
|
||||
t.Fatalf("failed to answer challenge: %v", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// Wait until the test timeout passes to ensure proper cleanup
|
||||
time.Sleep(syncChallengeTimeout + 300*time.Millisecond)
|
||||
|
||||
// Verify that the remote peer is maintained or dropped.
|
||||
if drop {
|
||||
<-handlerDone
|
||||
if peers := handler.handler.peers.len(); peers != 0 {
|
||||
t.Fatalf("peer count mismatch: have %d, want %d", peers, 0)
|
||||
}
|
||||
} else {
|
||||
if peers := handler.handler.peers.len(); peers != 1 {
|
||||
t.Fatalf("peer count mismatch: have %d, want %d", peers, 1)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Tests that blocks are broadcast to a sqrt number of peers only.
|
||||
func TestBroadcastBlock1Peer(t *testing.T) { testBroadcastBlock(t, 1, 1) }
|
||||
func TestBroadcastBlock2Peers(t *testing.T) { testBroadcastBlock(t, 2, 1) }
|
||||
|
||||
+4
-9
@@ -260,16 +260,11 @@ func (h *handler) doSync(op *chainSyncOp) error {
|
||||
log.Info("Snap sync complete, auto disabling")
|
||||
atomic.StoreUint32(&h.snapSync, 0)
|
||||
}
|
||||
// If we've successfully finished a sync cycle and passed any required checkpoint,
|
||||
// enable accepting transactions from the network.
|
||||
// If we've successfully finished a sync cycle, enable accepting transactions
|
||||
// from the network.
|
||||
atomic.StoreUint32(&h.acceptTxs, 1)
|
||||
|
||||
head := h.chain.CurrentBlock()
|
||||
if head.Number.Uint64() >= 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(&h.acceptTxs, 1)
|
||||
}
|
||||
}
|
||||
if head.Number.Uint64() > 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
|
||||
|
||||
Reference in New Issue
Block a user