Skip to content

[Besu-Plugin] Use blob compressor selector by timestamp#2569

Open
gauravahuja wants to merge 11 commits intomainfrom
gauravahuja/besu-plugin_updated_compressor
Open

[Besu-Plugin] Use blob compressor selector by timestamp#2569
gauravahuja wants to merge 11 commits intomainfrom
gauravahuja/besu-plugin_updated_compressor

Conversation

@gauravahuja
Copy link
Contributor

@gauravahuja gauravahuja commented Mar 9, 2026

This PR implements issue(s) #2503

Checklist

  • I wrote new tests for my new core changes.
  • I have successfully ran tests, style checker and build against my new changes locally.
  • If this change is deployed to any environment (including Devnet), E2E test coverage exists or is included in this
    PR.
  • I have informed the team of any breaking changes if there are any.

Note

Medium Risk
Touches sequencer transaction selection/compression paths and introduces timestamp-based compressor selection, which can affect block building and profitability calculations if activation times or caching keys are wrong. Changes are scoped and covered by updated/new unit tests, but impact runtime behavior under blob size limits.

Overview
Switches sequencer compression from a single GoBackedBlobCompressor instance to BlobCompressorSelectorByTimestamp, enabling compressor version selection by block timestamp and threading that timestamp through TransactionCompressor/CachingTransactionCompressor (including cache keys now incorporating compressor version).

Adds a hidden CLI option --plugin-linea-blob-compressor-version-timestamps parsed into LineaTransactionSelectorConfiguration, wires it into shared plugin initialization, and updates CompressionAwareTransactionSelector to use the pending block’s timestamp for both per-tx size estimates and full-block compression checks.

Bumps blob-compressor to 3.0.2, adds Kotlin stdlib dependency alignment in Gradle, and updates/extends tests (including new parsing tests) to use the selector-based compressor.

Written by Cursor Bugbot for commit d0af4b0. This will update automatically on new commits. Configure here.

@codecov-commenter
Copy link

codecov-commenter commented Mar 10, 2026

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 59.28%. Comparing base (76de532) to head (d0af4b0).

Additional details and impacted files
@@             Coverage Diff              @@
##               main    #2569      +/-   ##
============================================
+ Coverage     58.89%   59.28%   +0.39%     
- Complexity     1620     1627       +7     
============================================
  Files           454      453       -1     
  Lines         18530    18528       -2     
  Branches       2014     2014              
============================================
+ Hits          10913    10985      +72     
+ Misses         6948     6867      -81     
- Partials        669      676       +7     
Flag Coverage Δ *Carryforward flag
hardhat 96.63% <ø> (+0.10%) ⬆️ Carriedforward from e130a2f
kotlin 55.16% <ø> (+0.43%) ⬆️

*This pull request uses carry forward flags. Click here to find out more.
see 12 files with indirect coverage changes

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@@ -25,25 +26,30 @@
@Slf4j
public class CachingTransactionCompressor implements TransactionCompressor {
Copy link
Collaborator

@Filter94 Filter94 Mar 10, 2026

Choose a reason for hiding this comment

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

It looks like once we're past a certain timestamp:

  1. The caller of CachingTransactionCompressor may get a cached compression size, compressed by an old compressor, because the key is only tx's hash
  2. Cache is not invalidated after a switch timestamp is reached. However we don't need old cached entries for an old compressor version, so they will linger in memory for the expiry timeout

Copy link
Contributor Author

@gauravahuja gauravahuja Mar 10, 2026

Choose a reason for hiding this comment

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

CachingTransactionCompressor is not aware of the switch timestamps, neither does the BlobCompressorSelectorByTimestamp exposes this detail.So I don't know how cace invalidation could be accomplished. However there is TTL for the cache entry so it should automatically be invalidated after that.

I see two options:

  1. We might need to change the interface of BlobCompressorSelectorByTimestamp to be able to tell the caller that it has switched or
  2. The caller can always compare the previous BlobCompressor object with the one returned by BlobCompressorSelectorByTimestamp each time and if the object is different, invalidate the cache.

Which option do you prefer?

Copy link
Collaborator

@Filter94 Filter94 Mar 10, 2026

Choose a reason for hiding this comment

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

I think we need a deeper restructuring.

  • We can introduce a cache with a new key: (Blob compressor version, tx hash). Since the cache key is different, this cache can't conform to TransactionCompressor, it needs to accept Blob compressor version along with every transaction. This shared cache will be created in AbstractLineaSharedPrivateOptionsPlugin and will be referenced by the other instances as backend
  • This new cache can be used by the new implementation of TransactionCompressor which will accept the blob version on instantiation. This new implementation of TransactionCompressor will abstract users from the switch logic and will pass a constant blob compressor version, which was set in the constructor to the shared cache
  • Then we'll need custom implementations of BlobCompressorSelectorByTimestamp that would be aware of Blob compressor versions, the switch logic and would instantiate the TransactionCompressor implementation I described above

Instantiation

  • For tx selection it can be done in LineaTransactionSelector, because it's the first class that is aware of TransactionEvaluationContext which has the pending block's header, which has the timestamp
  • For txpool validation, instantiation will be in LineaTransactionPoolValidatorFactory::createTransactionValidator, by the current timestamp. It's called for every transaction before validation

BlobCompressorSelectorByTimestamp can be used in LineaTransactionSelector to instantiate BlobCompressor and pass it to CompressionAwareTransactionSelector's constructor

Copy link
Contributor Author

Choose a reason for hiding this comment

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

BlobCompressorSelectorByTimestamp can be used in LineaTransactionSelector to instantiate BlobCompressor and pass it to CompressionAwareTransactionSelector's constructor

It is not clear to me how CompressionAwareTransactionSelector be reinstantiated with new version of BlobCompressor when the new compressor version timestamp is reached.

Copy link
Collaborator

@Filter94 Filter94 Mar 12, 2026

Choose a reason for hiding this comment

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

You're right. We can't do it with the API we have, but in practice the timestamp we need is 1 frame away from the PluginTransactionSelectorFactory.create(...) call. So the way you create BlobCompressor inside CompressionAwareTransactionSelector is already optimal

@fab-10 Do you think it makes sense to pass some pending block metadata to PluginTransactionSelectorFactory? It would allow making the switch logic like that cleaner

Copy link
Collaborator

Choose a reason for hiding this comment

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

Could be a hard fork, could be a soft one. In this case it's a ground to switch a blob compressor. The idea is similar to the hard forks. Starting from a certain block timestamp we change the blob compressor version

Copy link
Contributor

@fab-10 fab-10 Mar 12, 2026

Choose a reason for hiding this comment

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

I think that's feasible, since selectors are created for each new block, they can take advantage of the pending block header.
Let me propose a quick PR

Copy link
Collaborator

Choose a reason for hiding this comment

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

In any case we won't be able to use your change in this PR, because we first need to finish the work on upgrading the Besu version

Copy link
Contributor

Choose a reason for hiding this comment

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

Copy link
Contributor

Choose a reason for hiding this comment

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

The new API is now on Besu main

Instant timestamp =
Instant.Companion.fromEpochSeconds(pendingHeader.getTimestamp(), 0L);
BlobCompressor blobCompressor =
blobCompressorSelectorByTimestamp.getBlobCompressor(timestamp);
Copy link
Collaborator

@Filter94 Filter94 Mar 10, 2026

Choose a reason for hiding this comment

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

I think that's wrong. Since blobCompressor can be injected into CompressionAwareTransactionSelector's constructor, why would we need to get it by timestamp here?

Copy link
Collaborator

Choose a reason for hiding this comment

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

Since this is the first class in the call stack which has access to evaluationContext, I think it makes the most sense to get a specific transaction compressor here. It would make even more sense in LineaTransactionSelectorFactory, but it seems that you can't access pending block's header from there

description =
"Comma-separated map of BlobCompressorVersion to Instant, e.g. V1_2=2025-01-01T00:00:00Z,V2=2026-01-01T00:00:00Z")
private String blobCompressorVersionTimestampsRaw =
String.format("%s=%s", BlobCompressorVersion.V2.name(), java.time.Instant.MIN);
Copy link

Choose a reason for hiding this comment

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

Private constructor removed breaking encapsulation of CLI options

Low Severity

The private no-arg constructor LineaTransactionSelectorCliOptions() was removed (previously on line 140 of old code) when the new blobCompressorVersionTimestampsRaw field was added. This changes the constructor from private to the implicit package-private default, allowing direct instantiation from test code (new LineaTransactionSelectorCliOptions()) but weakening the intended factory pattern enforced by create().

Fix in Cursor Fix in Web

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 do not know what issue this comment is referring to.

Copy link

@cursor cursor bot left a comment

Choose a reason for hiding this comment

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

Cursor Bugbot has reviewed your changes and found 1 potential issue.

There are 2 total unresolved issues (including 1 from previous review).

Fix All in Cursor

Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, have a team admin enable autofix in the Cursor dashboard.

hidden = true,
paramLabel = "<MAP>",
description =
"Comma-separated map of BlobCompressorVersion to Instant, e.g. V1_2=2025-01-01T00:00:00Z,V2=2026-01-01T00:00:00Z")
Copy link
Collaborator

Choose a reason for hiding this comment

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

It would be nice to include all supported versions in the description so user knows what is available and how to write versions exactly

apply from: lineaSequencerProject.file("gradle/dependency-management.gradle")
apply from: lineaSequencerProject.file("gradle/lint.gradle")

configurations.all {
Copy link
Collaborator

Choose a reason for hiding this comment

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

Can you please add a clarifying comment on why we need this and what's the resolution strategy we do here?

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