mirror of
https://github.com/ethereum/go-ethereum.git
synced 2026-05-24 08:49:29 +00:00
internal/ethapi, eth, eth/catalyst: address review feedback on eth_syncing CL gate
Per @MariusVanDerWijden's review feedback, tighten the change to match geth's existing style: - Drop the MarkConsensusExpected/MarkConsensusContacted/ConsensusReady doc paragraphs on Ethereum; collapse the field comments to single trailing lines matching eth/handler.go's atomic.Bool style. - Rename the unexported accessors to MarkCLExpected/MarkCLContacted (catalyst can't reach the fields directly). - Drop the multi-line comments at the catalyst call sites — the method names are self-describing. - Trim the Backend.ConsensusReady() interface comment and EthAPIBackend wrapper comment. - Replace the verbose docstring on EthereumAPI.Syncing with a single reference to #33687. - Drop the long doc comments on the syncing_test.go cases; rename test functions to short forms (TestSyncingBeforeCLContact, etc.). No behavioural change. Run: `go test ./internal/ethapi/ -count=1`.
This commit is contained in:
parent
20d6757391
commit
21b153df43
6 changed files with 25 additions and 91 deletions
|
|
@ -422,8 +422,6 @@ func (b *EthAPIBackend) SyncProgress(ctx context.Context) ethereum.SyncProgress
|
||||||
return prog
|
return prog
|
||||||
}
|
}
|
||||||
|
|
||||||
// ConsensusReady reports whether the node's "synced" claim is currently
|
|
||||||
// meaningful. See (*Ethereum).ConsensusReady for the gating rules.
|
|
||||||
func (b *EthAPIBackend) ConsensusReady() bool {
|
func (b *EthAPIBackend) ConsensusReady() bool {
|
||||||
return b.eth.ConsensusReady()
|
return b.eth.ConsensusReady()
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -125,41 +125,20 @@ type Ethereum struct {
|
||||||
|
|
||||||
shutdownTracker *shutdowncheck.ShutdownTracker // Tracks if and when the node has shutdown ungracefully
|
shutdownTracker *shutdowncheck.ShutdownTracker // Tracks if and when the node has shutdown ungracefully
|
||||||
|
|
||||||
// clExpected is set when the Engine API is registered on this node
|
clExpected atomic.Bool // Set when catalyst.Register attaches the Engine API
|
||||||
// (catalyst.Register). When unset (no Engine API attached, e.g. in
|
clContacted atomic.Bool // Set on first Engine API call (newPayload / FCU)
|
||||||
// tests, in --dev mode without catalyst, in light/legacy backends),
|
|
||||||
// the node cannot be paired with a consensus client and should not be
|
|
||||||
// gated on a CL handshake. clContacted records whether the consensus
|
|
||||||
// client has spoken to us via the Engine API at least once.
|
|
||||||
clExpected atomic.Bool
|
|
||||||
clContacted atomic.Bool
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// MarkConsensusExpected records that this node expects to be driven by a
|
// MarkCLExpected and MarkCLContacted are setters for the two clXxx flags;
|
||||||
// consensus layer via the Engine API. It should be invoked once during node
|
// catalyst calls them from its package and so cannot reach the fields directly.
|
||||||
// setup, when the Engine API is registered.
|
func (s *Ethereum) MarkCLExpected() { s.clExpected.Store(true) }
|
||||||
func (s *Ethereum) MarkConsensusExpected() {
|
func (s *Ethereum) MarkCLContacted() { s.clContacted.Store(true) }
|
||||||
s.clExpected.Store(true)
|
|
||||||
}
|
|
||||||
|
|
||||||
// MarkConsensusContacted records that the consensus layer has driven this node
|
// ConsensusReady reports whether eth_syncing should be allowed to return false.
|
||||||
// at least once via the Engine API. The flag is sticky: once set, it stays set
|
// On nodes without an Engine API, always true. On nodes that expect a CL, true
|
||||||
// for the lifetime of the process.
|
// only after the CL has driven the node at least once.
|
||||||
func (s *Ethereum) MarkConsensusContacted() {
|
|
||||||
s.clContacted.Store(true)
|
|
||||||
}
|
|
||||||
|
|
||||||
// ConsensusReady reports whether the node's "synced" claim is meaningful right
|
|
||||||
// now. If the node does not expect a consensus client (no Engine API), the
|
|
||||||
// answer is always yes. If it does, the answer is yes only after the consensus
|
|
||||||
// client has driven the node at least once. eth_syncing uses this to avoid
|
|
||||||
// claiming "synced" before any CL handshake has occurred on freshly started
|
|
||||||
// post-merge nodes.
|
|
||||||
func (s *Ethereum) ConsensusReady() bool {
|
func (s *Ethereum) ConsensusReady() bool {
|
||||||
if !s.clExpected.Load() {
|
return !s.clExpected.Load() || s.clContacted.Load()
|
||||||
return true
|
|
||||||
}
|
|
||||||
return s.clContacted.Load()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// New creates a new Ethereum object (including the initialisation of the common Ethereum object),
|
// New creates a new Ethereum object (including the initialisation of the common Ethereum object),
|
||||||
|
|
|
||||||
|
|
@ -49,11 +49,7 @@ import (
|
||||||
|
|
||||||
// Register adds the engine API and related APIs to the full node.
|
// Register adds the engine API and related APIs to the full node.
|
||||||
func Register(stack *node.Node, backend *eth.Ethereum) error {
|
func Register(stack *node.Node, backend *eth.Ethereum) error {
|
||||||
// Mark the backend as expecting a consensus client. eth_syncing uses
|
backend.MarkCLExpected()
|
||||||
// this to gate "synced" responses on a CL handshake: a node configured
|
|
||||||
// to take direction from a CL should not advertise itself as synced
|
|
||||||
// until that CL has actually driven it at least once.
|
|
||||||
backend.MarkConsensusExpected()
|
|
||||||
stack.RegisterAPIs([]rpc.API{
|
stack.RegisterAPIs([]rpc.API{
|
||||||
newTestingAPI(backend),
|
newTestingAPI(backend),
|
||||||
{
|
{
|
||||||
|
|
@ -256,9 +252,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
|
// Stash away the last update to warn the user if the beacon client goes offline
|
||||||
api.lastForkchoiceUpdate.Store(time.Now().Unix())
|
api.lastForkchoiceUpdate.Store(time.Now().Unix())
|
||||||
// Record that the consensus layer has driven us at least once. eth_syncing
|
api.eth.MarkCLContacted()
|
||||||
// uses this to avoid claiming "synced" before any CL handshake has occurred.
|
|
||||||
api.eth.MarkConsensusContacted()
|
|
||||||
|
|
||||||
// Check whether we have the block yet in our database or not. If not, we'll
|
// 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
|
// need to either trigger a sync, or to reject this forkchoice update for a
|
||||||
|
|
@ -854,9 +848,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
|
// Stash away the last update to warn the user if the beacon client goes offline
|
||||||
api.lastNewPayloadUpdate.Store(time.Now().Unix())
|
api.lastNewPayloadUpdate.Store(time.Now().Unix())
|
||||||
// Record that the consensus layer has driven us at least once. eth_syncing
|
api.eth.MarkCLContacted()
|
||||||
// uses this to avoid claiming "synced" before any CL handshake has occurred.
|
|
||||||
api.eth.MarkConsensusContacted()
|
|
||||||
|
|
||||||
// If we already have the block locally, ignore the entire execution and just
|
// If we already have the block locally, ignore the entire execution and just
|
||||||
// return a fake success.
|
// return a fake success.
|
||||||
|
|
|
||||||
|
|
@ -153,19 +153,12 @@ func (api *EthereumAPI) BlobBaseFee(ctx context.Context) *hexutil.Big {
|
||||||
// - highestBlock: block number of the highest block header this node has received from peers
|
// - highestBlock: block number of the highest block header this node has received from peers
|
||||||
// - pulledStates: number of state entries processed until now
|
// - pulledStates: number of state entries processed until now
|
||||||
// - knownStates: number of known state entries that still need to be pulled
|
// - knownStates: number of known state entries that still need to be pulled
|
||||||
//
|
|
||||||
// On post-merge nodes paired with a consensus client, Syncing also waits for
|
|
||||||
// the consensus client to drive the node at least once before reporting
|
|
||||||
// "false". This avoids advertising a freshly started node as synced before any
|
|
||||||
// CL handshake has happened — see (*Ethereum).ConsensusReady. Backends without
|
|
||||||
// an Engine API (tests, --dev mode without catalyst, light clients) report
|
|
||||||
// ready immediately, preserving legacy behavior.
|
|
||||||
func (api *EthereumAPI) Syncing(ctx context.Context) (interface{}, error) {
|
func (api *EthereumAPI) Syncing(ctx context.Context) (interface{}, error) {
|
||||||
progress := api.b.SyncProgress(ctx)
|
progress := api.b.SyncProgress(ctx)
|
||||||
|
|
||||||
// Return not syncing if the synchronisation already completed AND, for
|
// Don't claim "synced" until the CL has driven us at least once (post-merge
|
||||||
// backends that expect one, the consensus client has driven the node at
|
// nodes with Engine API attached). Backends without a CL report ready
|
||||||
// least once.
|
// immediately via ConsensusReady. Refs #33687.
|
||||||
if progress.Done() && api.b.ConsensusReady() {
|
if progress.Done() && api.b.ConsensusReady() {
|
||||||
return false, nil
|
return false, nil
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -42,12 +42,6 @@ import (
|
||||||
type Backend interface {
|
type Backend interface {
|
||||||
// General Ethereum API
|
// General Ethereum API
|
||||||
SyncProgress(ctx context.Context) ethereum.SyncProgress
|
SyncProgress(ctx context.Context) ethereum.SyncProgress
|
||||||
// ConsensusReady reports whether the node's "synced" claim is currently
|
|
||||||
// meaningful. For backends that do not expect a consensus client (no
|
|
||||||
// Engine API attached) the answer is always true. For backends that do,
|
|
||||||
// the answer flips to true only after the consensus client has driven
|
|
||||||
// the node at least once. eth_syncing uses this to avoid reporting
|
|
||||||
// "synced" before any CL handshake has happened.
|
|
||||||
ConsensusReady() bool
|
ConsensusReady() bool
|
||||||
|
|
||||||
SuggestGasTipCap(ctx context.Context) (*big.Int, error)
|
SuggestGasTipCap(ctx context.Context) (*big.Int, error)
|
||||||
|
|
|
||||||
|
|
@ -23,9 +23,6 @@ import (
|
||||||
"github.com/ethereum/go-ethereum"
|
"github.com/ethereum/go-ethereum"
|
||||||
)
|
)
|
||||||
|
|
||||||
// syncingBackend is a minimal Backend embedding that only implements the two
|
|
||||||
// methods Syncing calls. Embedding the interface avoids pulling in the full
|
|
||||||
// testBackend setup just to flip a single bool.
|
|
||||||
type syncingBackend struct {
|
type syncingBackend struct {
|
||||||
Backend
|
Backend
|
||||||
progress ethereum.SyncProgress
|
progress ethereum.SyncProgress
|
||||||
|
|
@ -35,18 +32,9 @@ type syncingBackend struct {
|
||||||
func (b *syncingBackend) SyncProgress(_ context.Context) ethereum.SyncProgress { return b.progress }
|
func (b *syncingBackend) SyncProgress(_ context.Context) ethereum.SyncProgress { return b.progress }
|
||||||
func (b *syncingBackend) ConsensusReady() bool { return b.ready }
|
func (b *syncingBackend) ConsensusReady() bool { return b.ready }
|
||||||
|
|
||||||
// TestSyncingReportsBeforeConsensusContact verifies that on a CL-paired node
|
// Issue #33687: a Done downloader but no CL handshake yet must report syncing.
|
||||||
// (ConsensusReady false), eth_syncing returns a truthy progress object even
|
func TestSyncingBeforeCLContact(t *testing.T) {
|
||||||
// when the local downloader believes itself to be done. This is the bug fix
|
api := NewEthereumAPI(&syncingBackend{progress: ethereum.SyncProgress{}, ready: false})
|
||||||
// for issue #33687: a freshly started node must not advertise itself as
|
|
||||||
// "synced" before the consensus client has actually driven it.
|
|
||||||
func TestSyncingReportsBeforeConsensusContact(t *testing.T) {
|
|
||||||
api := NewEthereumAPI(&syncingBackend{
|
|
||||||
// progress.Done() returns true on a zero-valued struct because all
|
|
||||||
// remaining counters are zero and CurrentBlock >= HighestBlock.
|
|
||||||
progress: ethereum.SyncProgress{},
|
|
||||||
ready: false,
|
|
||||||
})
|
|
||||||
res, err := api.Syncing(context.Background())
|
res, err := api.Syncing(context.Background())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("Syncing returned error: %v", err)
|
t.Fatalf("Syncing returned error: %v", err)
|
||||||
|
|
@ -56,34 +44,24 @@ func TestSyncingReportsBeforeConsensusContact(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// TestSyncingReportsFalseAfterConsensusContact verifies that once the
|
func TestSyncingAfterCLContact(t *testing.T) {
|
||||||
// consensus layer has handshaken at least once (or the backend does not
|
api := NewEthereumAPI(&syncingBackend{progress: ethereum.SyncProgress{}, ready: true})
|
||||||
// expect one) and progress.Done() is true, eth_syncing reports false.
|
|
||||||
func TestSyncingReportsFalseAfterConsensusContact(t *testing.T) {
|
|
||||||
api := NewEthereumAPI(&syncingBackend{
|
|
||||||
progress: ethereum.SyncProgress{},
|
|
||||||
ready: true,
|
|
||||||
})
|
|
||||||
res, err := api.Syncing(context.Background())
|
res, err := api.Syncing(context.Background())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("Syncing returned error: %v", err)
|
t.Fatalf("Syncing returned error: %v", err)
|
||||||
}
|
}
|
||||||
v, ok := res.(bool)
|
if v, ok := res.(bool); !ok || v {
|
||||||
if !ok || v {
|
|
||||||
t.Fatalf("expected false after CL handshake when sync is done, got %v", res)
|
t.Fatalf("expected false after CL handshake when sync is done, got %v", res)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// TestSyncingReportsActiveSyncEvenWithoutConsensusContact verifies that when
|
// Active sync stays truthy regardless of the CL gate.
|
||||||
// the downloader is actively syncing, eth_syncing returns the progress map
|
func TestSyncingActiveSyncIgnoresCLGate(t *testing.T) {
|
||||||
// regardless of the CL gate. This preserves the legacy semantics for the case
|
|
||||||
// the issue thread did not affect.
|
|
||||||
func TestSyncingReportsActiveSyncEvenWithoutConsensusContact(t *testing.T) {
|
|
||||||
api := NewEthereumAPI(&syncingBackend{
|
api := NewEthereumAPI(&syncingBackend{
|
||||||
progress: ethereum.SyncProgress{
|
progress: ethereum.SyncProgress{
|
||||||
StartingBlock: 100,
|
StartingBlock: 100,
|
||||||
CurrentBlock: 150,
|
CurrentBlock: 150,
|
||||||
HighestBlock: 200, // CurrentBlock < HighestBlock => Done()=false
|
HighestBlock: 200,
|
||||||
},
|
},
|
||||||
ready: false,
|
ready: false,
|
||||||
})
|
})
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue