diff --git a/eth/api_backend.go b/eth/api_backend.go index c4e2508a3d..bb83b078a6 100644 --- a/eth/api_backend.go +++ b/eth/api_backend.go @@ -456,6 +456,10 @@ func (b *EthAPIBackend) SyncProgress(ctx context.Context) ethereum.SyncProgress return prog } +func (b *EthAPIBackend) ConsensusReady() bool { + return b.eth.ConsensusReady() +} + func (b *EthAPIBackend) SuggestGasTipCap(ctx context.Context) (*big.Int, error) { return b.gpo.SuggestTipCap(ctx) } diff --git a/eth/backend.go b/eth/backend.go index ba9b4d4459..595a2ba54f 100644 --- a/eth/backend.go +++ b/eth/backend.go @@ -25,6 +25,7 @@ import ( "math/big" "runtime" "sync" + "sync/atomic" "time" "github.com/ethereum/go-ethereum/accounts" @@ -133,6 +134,19 @@ type Ethereum struct { lock sync.RWMutex // Protects the variadic fields (e.g. gas price and etherbase) shutdownTracker *shutdowncheck.ShutdownTracker // Tracks if and when the node has shutdown ungracefully + + clContacted atomic.Bool // Set on first Engine API call (newPayload / FCU) +} + +// MarkCLContacted records that the consensus client has driven this node at +// least once; catalyst calls it from its package and so cannot reach the field +// directly. +func (s *Ethereum) MarkCLContacted() { s.clContacted.Store(true) } + +// ConsensusReady reports whether eth_syncing should be allowed to return false. +// True once the consensus client has driven the node at least once. +func (s *Ethereum) ConsensusReady() bool { + return s.clContacted.Load() } // New creates a new Ethereum object (including the initialisation of the common Ethereum object), diff --git a/eth/catalyst/api.go b/eth/catalyst/api.go index 46f8cc98fc..f5eec288e8 100644 --- a/eth/catalyst/api.go +++ b/eth/catalyst/api.go @@ -257,6 +257,7 @@ func (api *ConsensusAPI) forkchoiceUpdated(ctx context.Context, update engine.Fo } // Stash away the last update to warn the user if the beacon client goes offline api.lastForkchoiceUpdate.Store(time.Now().Unix()) + api.eth.MarkCLContacted() // Check whether we have the block yet in our database or not. If not, we'll // need to either trigger a sync, or to reject this forkchoice update for a @@ -945,6 +946,7 @@ func (api *ConsensusAPI) newPayload(ctx context.Context, params engine.Executabl } // Stash away the last update to warn the user if the beacon client goes offline api.lastNewPayloadUpdate.Store(time.Now().Unix()) + api.eth.MarkCLContacted() // If we already have the block locally, ignore the entire execution and just // return a fake success. diff --git a/internal/ethapi/api.go b/internal/ethapi/api.go index 2eb4dee3c0..47ba1084fb 100644 --- a/internal/ethapi/api.go +++ b/internal/ethapi/api.go @@ -161,8 +161,10 @@ func (api *EthereumAPI) BaseFee(ctx context.Context) *hexutil.Big { func (api *EthereumAPI) Syncing(ctx context.Context) (interface{}, error) { progress := api.b.SyncProgress(ctx) - // Return not syncing if the synchronisation already completed - if progress.Done() { + // Don't claim "synced" until the CL has driven us at least once (post-merge + // nodes with Engine API attached). Backends without a CL report ready + // immediately via ConsensusReady. Refs #33687. + if progress.Done() && api.b.ConsensusReady() { return false, nil } // Otherwise gather the block sync stats diff --git a/internal/ethapi/api_test.go b/internal/ethapi/api_test.go index 80a9036ecc..a6d4ad7294 100644 --- a/internal/ethapi/api_test.go +++ b/internal/ethapi/api_test.go @@ -493,6 +493,7 @@ func newTestBackend(t *testing.T, n int, gspec *core.Genesis, engine consensus.E func (b testBackend) SyncProgress(ctx context.Context) ethereum.SyncProgress { return ethereum.SyncProgress{} } +func (b testBackend) ConsensusReady() bool { return true } func (b testBackend) SuggestGasTipCap(ctx context.Context) (*big.Int, error) { return big.NewInt(0), nil } diff --git a/internal/ethapi/backend.go b/internal/ethapi/backend.go index 25f21c65da..8e77661a9e 100644 --- a/internal/ethapi/backend.go +++ b/internal/ethapi/backend.go @@ -42,6 +42,7 @@ import ( type Backend interface { // General Ethereum API SyncProgress(ctx context.Context) ethereum.SyncProgress + ConsensusReady() bool SuggestGasTipCap(ctx context.Context) (*big.Int, error) FeeHistory(ctx context.Context, blockCount uint64, lastBlock rpc.BlockNumber, rewardPercentiles []float64) (*big.Int, [][]*big.Int, []*big.Int, []float64, []*big.Int, []float64, error) diff --git a/internal/ethapi/transaction_args_test.go b/internal/ethapi/transaction_args_test.go index 1d587032bb..377f2d693d 100644 --- a/internal/ethapi/transaction_args_test.go +++ b/internal/ethapi/transaction_args_test.go @@ -335,6 +335,7 @@ func (b *backendMock) ChainConfig() *params.ChainConfig { return b.config } func (b *backendMock) SyncProgress(ctx context.Context) ethereum.SyncProgress { return ethereum.SyncProgress{} } +func (b *backendMock) ConsensusReady() bool { return true } func (b *backendMock) FeeHistory(ctx context.Context, blockCount uint64, lastBlock rpc.BlockNumber, rewardPercentiles []float64) (*big.Int, [][]*big.Int, []*big.Int, []float64, []*big.Int, []float64, error) { return nil, nil, nil, nil, nil, nil, nil }