Skip to content

Conversation

@MarcoPolo
Copy link
Contributor

@MarcoPolo MarcoPolo commented Dec 12, 2025

This is used by cell-level dissemination (aka partial messages) to give the CL all blobs the EL knows about and let CL communicate efficiently about any other missing blobs. In other words, partial responses from the EL is useful now.

See the related (closed) PR: ethereum/execution-apis#674 and the new PR: ethereum/execution-apis#719

@raulk
Copy link
Member

raulk commented Dec 13, 2025

Previous version of this PR: #32170

@raulk
Copy link
Member

raulk commented Dec 13, 2025

This one includes metrics accounting, so it seems like an improvement over the old one.

@MariusVanDerWijden
Copy link
Member

How do you feel about:

diff --git a/eth/catalyst/api.go b/eth/catalyst/api.go
index 109581e240..2be050f084 100644
--- a/eth/catalyst/api.go
+++ b/eth/catalyst/api.go
@@ -571,51 +571,19 @@ func (api *ConsensusAPI) GetBlobsV2(hashes []common.Hash) ([]*engine.BlobAndProo
 	if api.config().LatestFork(head.Time) < forks.Osaka {
 		return nil, unsupportedForkErr("engine_getBlobsV2 is not available before Osaka fork")
 	}
-	if len(hashes) > 128 {
-		return nil, engine.TooLargeRequest.With(fmt.Errorf("requested blob count too large: %v", len(hashes)))
-	}
-	available := api.eth.BlobTxPool().AvailableBlobs(hashes)
-	getBlobsRequestedCounter.Inc(int64(len(hashes)))
-	getBlobsAvailableCounter.Inc(int64(available))
-
-	// Optimization: check first if all blobs are available, if not, return empty response
-	if available != len(hashes) {
-		getBlobsV2RequestMiss.Inc(1)
-		return nil, nil
-	}
-
-	blobs, _, proofs, err := api.eth.BlobTxPool().GetBlobs(hashes, types.BlobSidecarVersion1, false)
-	if err != nil {
-		return nil, engine.InvalidParams.With(err)
-	}
-
-	// To comply with API spec, check again that we really got all data needed
-	for _, blob := range blobs {
-		if blob == nil {
-			getBlobsV2RequestMiss.Inc(1)
-			return nil, nil
-		}
-	}
-	getBlobsV2RequestHit.Inc(1)
-
-	res := make([]*engine.BlobAndProofV2, len(hashes))
-	for i := 0; i < len(blobs); i++ {
-		var cellProofs []hexutil.Bytes
-		for _, proof := range proofs[i] {
-			cellProofs = append(cellProofs, proof[:])
-		}
-		res[i] = &engine.BlobAndProofV2{
-			Blob:       blobs[i][:],
-			CellProofs: cellProofs,
-		}
-	}
-	return res, nil
+	return api.getBlobs(hashes, false)
 }
 
 // GetBlobsV3 returns a set of blobs from the transaction pool. Same as
 // GetBlobsV2, except will return partial responses in case there is a missing
 // blob.
 func (api *ConsensusAPI) GetBlobsV3(hashes []common.Hash) ([]*engine.BlobAndProofV2, error) {
+	return api.getBlobs(hashes, true)
+}
+
+// getBlobs returns all available blobs.
+// if allowPartials is not set, either all or no blobs are returned.
+func (api *ConsensusAPI) getBlobs(hashes []common.Hash, allowPartials bool) ([]*engine.BlobAndProofV2, error) {
 	if len(hashes) > 128 {
 		return nil, engine.TooLargeRequest.With(fmt.Errorf("requested blob count too large: %v", len(hashes)))
 	}
@@ -631,10 +599,14 @@ func (api *ConsensusAPI) GetBlobsV3(hashes []common.Hash) ([]*engine.BlobAndProo
 	res := make([]*engine.BlobAndProofV2, len(hashes))
 	for i := range blobs {
 		if blobs[i] == nil {
-			getBlobsV3RequestMiss.Inc(1)
-			continue
+			if allowPartials {
+				getBlobsV3RequestMiss.Inc(1)
+				continue
+			} else {
+				getBlobsV2RequestMiss.Inc(1)
+				return nil, nil
+			}
 		}
-		getBlobsV3RequestHit.Inc(1)
 		var cellProofs []hexutil.Bytes
 		for _, proof := range proofs[i] {
 			cellProofs = append(cellProofs, proof[:])
@@ -644,6 +616,11 @@ func (api *ConsensusAPI) GetBlobsV3(hashes []common.Hash) ([]*engine.BlobAndProo
 			CellProofs: cellProofs,
 		}
 	}
+	if allowPartials {
+		getBlobsV3RequestHit.Inc(int64(len(blobs)))
+	} else {
+		getBlobsV2RequestHit.Inc(1)
+	}
 	return res, nil
 }
 

@MarcoPolo
Copy link
Contributor Author

@MariusVanDerWijden done. Updated in the latest version of the commit git range-diff c75be1f61a18...5cbe6bd60c4d

@MarcoPolo MarcoPolo marked this pull request as ready for review December 17, 2025 19:06
Copy link
Member

@MariusVanDerWijden MariusVanDerWijden left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

getBlobsV3RequestHit = metrics.NewRegisteredCounter("engine/getblobsV3/hit", nil)

// Number of blobs getBlobsV3 could not return
getBlobsV3RequestMiss = metrics.NewRegisteredCounter("engine/getblobsV3/miss", nil)
Copy link
Contributor

@fjl fjl Dec 18, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't understand why it's a separate metric for v3. We can just use the existing getblobs metrics. If we add the V3 in metrics names, all the dashboards have to be changed.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The existing metric only counts if all blobs are available or unavailable. The new one counts how many were available. We could change the old one and integrate both

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Changing the old metric would change the semantics though. The old metric implicitly counts "number of requests that returned data". I'd keep v2 as is (and potentially add the blob-counting version too to v2, as it's not easy to get down to blob hits/misses today AFAIK -- multiplying the metric by blobs included is not an option).

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can't we just use these two metrics for counting how many blobs are available and how many are not?

	// Number of blobs requested via getBlobsV2
	getBlobsRequestedCounter = metrics.NewRegisteredCounter("engine/getblobs/requested", nil)

	// Number of blobs requested via getBlobsV2 that are present in the blobpool
	getBlobsAvailableCounter = metrics.NewRegisteredCounter("engine/getblobs/available", nil)

They are duplicated with the engine/getblobsV3/hit and engine/getblobsV3/miss right?

Copy link
Member

@rjl493456442 rjl493456442 Dec 19, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

something like this?

diff --git a/eth/catalyst/api.go b/eth/catalyst/api.go
index cd07465d99..073da9fba3 100644
--- a/eth/catalyst/api.go
+++ b/eth/catalyst/api.go
@@ -93,12 +93,6 @@ var (
 
 	// Number of times getBlobsV2 responded with “miss”
 	getBlobsV2RequestMiss = metrics.NewRegisteredCounter("engine/getblobs/miss", nil)
-
-	// Number of blobs getBlobsV3 could return
-	getBlobsV3RequestHit = metrics.NewRegisteredCounter("engine/getblobsV3/hit", nil)
-
-	// Number of blobs getBlobsV3 could not return
-	getBlobsV3RequestMiss = metrics.NewRegisteredCounter("engine/getblobsV3/miss", nil)
 )
 
 type ConsensusAPI struct {
@@ -571,7 +565,7 @@ func (api *ConsensusAPI) GetBlobsV2(hashes []common.Hash) ([]*engine.BlobAndProo
 	if api.config().LatestFork(head.Time) < forks.Osaka {
 		return nil, nil
 	}
-	return api.getBlobs(hashes, false)
+	return api.getBlobs(hashes, true)
 }
 
 // GetBlobsV3 returns a set of blobs from the transaction pool. Same as
@@ -582,12 +576,12 @@ func (api *ConsensusAPI) GetBlobsV3(hashes []common.Hash) ([]*engine.BlobAndProo
 	if api.config().LatestFork(head.Time) < forks.Osaka {
 		return nil, nil
 	}
-	return api.getBlobs(hashes, true)
+	return api.getBlobs(hashes, false)
 }
 
-// getBlobs returns all available blobs.
-// if allowPartials is not set, either all or no blobs are returned.
-func (api *ConsensusAPI) getBlobs(hashes []common.Hash, allowPartials bool) ([]*engine.BlobAndProofV2, error) {
+// getBlobs returns all available blobs. In v2, partial responses are not allowed,
+// while v3 supports partial responses.
+func (api *ConsensusAPI) getBlobs(hashes []common.Hash, v2 bool) ([]*engine.BlobAndProofV2, error) {
 	if len(hashes) > 128 {
 		return nil, engine.TooLargeRequest.With(fmt.Errorf("requested blob count too large: %v", len(hashes)))
 	}
@@ -596,7 +590,7 @@ func (api *ConsensusAPI) getBlobs(hashes []common.Hash, allowPartials bool) ([]*
 	getBlobsAvailableCounter.Inc(int64(available))
 
 	// Short circuit if partial response is not allowed
-	if !allowPartials && available != len(hashes) {
+	if v2 && available != len(hashes) {
 		getBlobsV2RequestMiss.Inc(1)
 		return nil, nil
 	}
@@ -609,9 +603,10 @@ func (api *ConsensusAPI) getBlobs(hashes []common.Hash, allowPartials bool) ([]*
 	// Validate the blobs from the pool and assemble the response
 	res := make([]*engine.BlobAndProofV2, len(hashes))
 	for i := range blobs {
+		// The blob has been evicted since the last AvailableBlobs call.
+		// Return null if partial response is not allowed.
 		if blobs[i] == nil {
-			if allowPartials {
-				getBlobsV3RequestMiss.Inc(1)
+			if !v2 {
 				continue
 			} else {
 				getBlobsV2RequestMiss.Inc(1)
@@ -627,9 +622,7 @@ func (api *ConsensusAPI) getBlobs(hashes []common.Hash, allowPartials bool) ([]*
 			CellProofs: cellProofs,
 		}
 	}
-	if allowPartials {
-		getBlobsV3RequestHit.Inc(int64(len(blobs)))
-	} else {
+	if v2 {
 		getBlobsV2RequestHit.Inc(1)
 	}
 	return res, nil

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can't we just use these two metrics for counting how many blobs are available and how many are not?

You're right. I'll make that change.

I think we might also want a metric that tracks the number of times we returned a partial response, as I don't think we can get that with the existing metrics.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  • I've added @rjl493456442 's diff above as commit 5aef119.
  • I've renamed the metric variables to drop the "v2" qualifier 45a9f25
  • I've added a partial hit metric efdb0d7. This gives us the metric of how many times getBlobs returned some, but not all, blobs.

rjl493456442
rjl493456442 previously approved these changes Dec 19, 2025
Copy link
Member

@rjl493456442 rjl493456442 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

@rjl493456442 rjl493456442 added this to the 1.16.9 milestone Dec 22, 2025
@rjl493456442 rjl493456442 modified the milestones: 1.16.9, 1.16.8 Dec 31, 2025
@rjl493456442 rjl493456442 merged commit b2843a1 into ethereum:master Dec 31, 2025
7 of 8 checks passed
weiihann added a commit to weiihann/go-ethereum that referenced this pull request Jan 8, 2026
commit 64d22fd
Author: LittleBingoo <[email protected]>
Date:   Thu Jan 8 11:49:13 2026 +0800

    internal/flags: update copyright year to 2026 (ethereum#33550)

commit 9623dcb
Author: rjl493456442 <[email protected]>
Date:   Thu Jan 8 11:48:45 2026 +0800

    core/state: add cache statistics of contract code reader (ethereum#33532)

commit 01b39c9
Author: Ng Wei Han <[email protected]>
Date:   Thu Jan 8 11:07:19 2026 +0800

    core/state, core/tracing: new state update hook (ethereum#33490)

    ### Description
    Add a new `OnStateUpdate` hook which gets invoked after state is
    committed.

    ### Rationale
    For our particular use case, we need to obtain the state size metrics at
    every single block when fuly syncing from genesis. With the current
    state sizer, whenever the node is stopped, the background process must
    be freshly initialized. During this re-initialization, it can skip some
    blocks while the node continues executing blocks, causing gaps in the
    recorded metrics.

    Using this state update hook allows us to customize our own data
    persistence logic, and we would never skip blocks upon node restart.

    ---------

    Co-authored-by: Gary Rong <[email protected]>

commit 957a360
Author: cui <[email protected]>
Date:   Wed Jan 7 10:02:27 2026 +0800

    core/vm: avoid escape to heap (ethereum#33537)

commit 7100084
Author: Csaba Kiraly <[email protected]>
Date:   Wed Jan 7 02:52:50 2026 +0100

    eth: txs fetch/send log at trace level only (ethereum#33541)

    This logging was too intensive at debug level, it is better to have it
    at trace level only.

    Signed-off-by: Csaba Kiraly <[email protected]>

commit eaaa5b7
Author: rjl493456442 <[email protected]>
Date:   Tue Jan 6 15:09:15 2026 +0800

    core: re-organize the stats category (ethereum#33525)

    Check out https://hackmd.io/dg7rizTyTXuCf2LSa2LsyQ for more details

commit a8a4804
Author: Andrew Davis <[email protected]>
Date:   Tue Jan 6 03:49:30 2026 +1100

    ethstats: report newPayload processing time to stats server (ethereum#33395)

    Add NewPayloadEvent to track engine API newPayload block processing
    times and report them to ethstats. This enables monitoring of block
    processing performance.

    https://notes.ethereum.org/@savid/block-observability

    related: ethereum#33231

    ---------

    Co-authored-by: MariusVanDerWijden <[email protected]>

commit de5ea2f
Author: Mask Weller <[email protected]>
Date:   Sun Jan 4 13:47:28 2026 +0700

    core/rawdb: add trienode freezer support to InspectFreezerTable (ethereum#33515)

    Adds missing trienode freezer case to InspectFreezerTable, making it
    consistent with InspectFreezer which already supports it.

    Co-authored-by: m6xwzzz <[email protected]>

commit b635e06
Author: rjl493456442 <[email protected]>
Date:   Thu Jan 1 02:52:25 2026 +0800

    eth/fetcher: improve the condition to stall peer in tx fetcher (ethereum#32725)

    Signed-off-by: Csaba Kiraly <[email protected]>
    Co-authored-by: Csaba Kiraly <[email protected]>

commit 32fea00
Author: shhhh <[email protected]>
Date:   Wed Dec 31 11:32:44 2025 +0530

    core/blockchain.go: cleanup finalized block on rewind in setHeadBeyondRoot (ethereum#33486)

    Fix ethereum#33390

    `setHeadBeyondRoot` was failing to invalidate finalized blocks because
    it compared against the original head instead of the rewound root. This
    fix updates the comparison to use the post-rewind block number,
    preventing the node from reporting a finalized block that no longer
    exists. Also added relevant test cases for it.

commit b2843a1
Author: Marco Munizaga <[email protected]>
Date:   Tue Dec 30 17:48:50 2025 -0800

    eth/catalyst: implement getBlobsV3 (ethereum#33404)

    This is used by cell-level dissemination (aka partial messages) to give
    the CL all blobs the EL knows about and let CL communicate efficiently
    about any other missing blobs. In other words, partial responses from
    the EL is useful now.

    See the related (closed) PR:
    ethereum/execution-apis#674 and the new PR:
    ethereum/execution-apis#719

commit 25439aa
Author: Bashmunta <[email protected]>
Date:   Wed Dec 31 03:40:43 2025 +0200

    core/state/snapshot: fix storageList memory accounting (ethereum#33505)

commit 52ae75a
Author: Rim Dinov <[email protected]>
Date:   Wed Dec 31 01:04:38 2025 +0500

    cmd/geth: remove deprecated vulnerability check command (ethereum#33498)

    This PR removes the version-check command and its associated logic as
    discussed in issue ethereum#31222.

    Removed versionCheckCommand from misccmd.go and main.go.

    Deleted version_check.go and its corresponding tests.

    Cleaned up testdata/vcheck directory (~800 lines of JSON/signatures
    removed).

    Verified build with make geth

commit d9aaab1
Author: Fibonacci747 <[email protected]>
Date:   Tue Dec 30 18:27:11 2025 +0100

    beacon/light/sync: clear reqFinalityEpoch on server unregistration (ethereum#33483)

    HeadSync kept reqFinalityEpoch entries for servers after receiving
    EvUnregistered, while other per-server maps were cleared. This left
    stale request.Server keys reachable from HeadSync, which can lead to a
    slow memory leak in setups that dynamically register and unregister
    servers.

    The fix adds deletion of the reqFinalityEpoch entry in the
    EvUnregistered handler. This aligns HeadSync with the cleanup pattern
    used by other sync modules and keeps the finality request bookkeeping
    strictly limited to currently registered servers.

commit b3e7d9e
Author: rjl493456442 <[email protected]>
Date:   Tue Dec 30 23:05:13 2025 +0800

    triedb/pathdb: optimize history indexing efficiency (ethereum#33303)

    This pull request optimizes history indexing by splitting a single large
    database
     batch into multiple smaller chunks.

    Originally, the indexer will resolve a batch of state histories and
    commit all
    corresponding index entries atomically together with the indexing
    marker.

    While indexing more state histories in a single batch improves
    efficiency, excessively
    large batches can cause significant memory issues.

    To mitigate this, the pull request splits the mega-batch into several
    smaller batches
    and flushes them independently during indexing. However, this introduces
    a potential
    inconsistency that some index entries may be flushed while the indexing
    marker is not,
    and an unclean shutdown may leave the database in a partially updated
    state.
    This can corrupt index data.

    To address this, head truncation is introduced. After a restart, any
    excessive index
    entries beyond the expected indexing marker are removed, ensuring the
    index remains
    consistent after an unclean shutdown.

commit b84097d
Author: Guillaume Ballet <[email protected]>
Date:   Tue Dec 30 14:43:45 2025 +0100

    .github/workflows: preventively close PRs that seem AI-generated (ethereum#33414)

    This is a new step in my crusade against the braindead fad of starting
    PR titles with a word that is completely redundant with github labels,
    thus wasting prime first-line real-estate for something that isn't
    necessary.

    I noticed that every single one of these PRs are low-quality AI-slop, so
    I think there is a strong case to be made for these PRs to be
    auto-closed. A message is added before closing the PR, redirecting to
    our contribution guidelines, so I expect quality first-time contributors
    to read them and reopen the PR. In the case of spam PRs, the author is
    unlikely to revisit a given PR, and so auto-closing might have a
    positive impact. That's an experiment worth trying, imo.

commit 3f641db
Author: Guillaume Ballet <[email protected]>
Date:   Tue Dec 30 13:44:04 2025 +0100

    trie, go.mod: remove all references to go-verkle and go-ipa (ethereum#33461)

    In order to reduce the amount of code that is embedded into the keeper
    binary, I am removing all the verkle code that uses go-verkle and
    go-ipa. This will be followed by further PRs that are more like stubs to
    replace code when the keeper build is detected.

    I'm keeping the binary tree of course. This means that you will still
    see `isVerkle` variables all over the codebase, but they will be renamed
    when code is touched (i.e. this is not an invitation for 30+ AI slop
    PRs).

    ---------

    Co-authored-by: Gary Rong <[email protected]>

commit 57f8486
Author: Archkon <[email protected]>
Date:   Mon Dec 29 20:57:29 2025 +0800

    params: fix wrong comment (ethereum#33503)

    It seems that the comment for CopyGas was wrongly associated to
    SloadGas.

commit b9702ed
Author: oooLowNeoNooo <[email protected]>
Date:   Mon Dec 29 09:23:51 2025 +0100

    console/prompt: use PromptInput in PromptConfirm method (ethereum#33445)

commit 4531bfe
Author: rjl493456442 <[email protected]>
Date:   Mon Dec 29 16:13:30 2025 +0800

    eth/downloader: fix stale beacon header deletion (ethereum#33481)

    In this PR, two things have been fixed:

    ---

    (a) truncate the stale beacon headers with latest snap block

    Originally, b.filled is used as the indicator for deleting stale beacon headers.
    This field is set only after synchronization has been scheduled, under the
    assumption that the skeleton chain is already linked to the local chain.

    However, the local chain can be mutated via `debug_setHead`, which may
    cause `b.filled` outdated. For instance, `b.filled` refers to the last head snap block
    in the last sync cycle while after `debug_setHead`, the head snap block has been
    rewounded to 1.

    As a result, Geth can enter an unintended loop: it repeatedly downloads
    the missing beacon headers for the skeleton chain and attempts to schedule the
    actual synchronization, but in the final step, all recently fetched headers are removed
    by `cleanStales` due to the stale `b.filled` value.

    This issue is addressed by always using the latest snap block as the indicator,
    without relying on any cached value. However, note that before the skeleton
    chain is linked to the local chain, the latest snap block will always be below
    skeleton.tail, and this condition should not be treated as an error.

    ---

    (b) merge the subchains once the skeleton chain links to local chain

    Once the skeleton chain links with local one, it will try to schedule the
    synchronization by fetching the missing blocks and import them then.
    It's possible the last subchain already overwrites the previous subchain and
    results in having two subchains leftover. As a result, an error log will printed
    https://github.com/ethereum/go-ethereum/blob/master/eth/downloader/skeleton.go#L1074
weiihann pushed a commit to weiihann/go-ethereum that referenced this pull request Jan 14, 2026
This is used by cell-level dissemination (aka partial messages) to give
the CL all blobs the EL knows about and let CL communicate efficiently
about any other missing blobs. In other words, partial responses from
the EL is useful now.

See the related (closed) PR:
ethereum/execution-apis#674 and the new PR:
ethereum/execution-apis#719
weiihann pushed a commit to weiihann/go-ethereum that referenced this pull request Jan 16, 2026
This is used by cell-level dissemination (aka partial messages) to give
the CL all blobs the EL knows about and let CL communicate efficiently
about any other missing blobs. In other words, partial responses from
the EL is useful now.

See the related (closed) PR:
ethereum/execution-apis#674 and the new PR:
ethereum/execution-apis#719
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants