Skip to content

feat(builder): inject validity predicates in shadow mode - #4472

Merged
haardikk21 merged 2 commits into
mainfrom
shadow-validity-injection
Aug 18, 2026
Merged

feat(builder): inject validity predicates in shadow mode#4472
haardikk21 merged 2 commits into
mainfrom
shadow-validity-injection

Conversation

@haardikk21

@haardikk21 haardikk21 commented Aug 17, 2026

Copy link
Copy Markdown
Collaborator

Summary

  • deterministically sample forwarded EIP-1559 transactions in an opt-in shadow-builder mode
  • attach a builder-local balance(sender) > 0 validity predicate without changing signed transaction bytes or hashes
  • preserve transactions carrying existing validity metadata or bundle bounds
  • add injection/evaluation metrics and configuration safety checks

The feature is disabled by default and requires both --builder.enable-experimental-validity-transactions and --builder.shadow-validity-injection.enabled. The sampling rate is configured in basis points with --builder.shadow-validity-injection.sample-rate-bps.

@cb-heimdall

cb-heimdall commented Aug 17, 2026

Copy link
Copy Markdown
Collaborator

✅ Heimdall Review Status

Requirement Status More Info
Reviews 2/2
Denominator calculation
Show calculation
1 if user is bot 0
1 if user is external 0
2 if repo is sensitive 0
From .codeflow.yml 1
Additional review requirements
Show calculation
Max 0
0
From CODEOWNERS 0
Global minimum 0
Max 1
1
1 if commit is unverified 1
Sum 2

@haardikk21
haardikk21 force-pushed the shadow-validity-injection branch from b304b7b to c8b4393 Compare August 17, 2026 20:36
Comment thread crates/builder/core/src/shadow_validity.rs Outdated
Comment thread crates/builder/cli/src/args.rs Outdated
BrianBland
BrianBland previously approved these changes Aug 17, 2026
@haardikk21
haardikk21 force-pushed the shadow-validity-injection branch from c8b4393 to 27fc6b9 Compare August 18, 2026 14:42
@cb-heimdall
cb-heimdall dismissed BrianBland’s stale review August 18, 2026 14:42

Approved review 4955427500 from BrianBland is now dismissed due to new commit. Re-request for approval.

@haardikk21
haardikk21 requested a review from BrianBland August 18, 2026 14:44
Comment thread crates/builder/core/src/shadow_validity.rs
Comment thread crates/builder/core/src/shadow_validity.rs

@refcell refcell left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Looks good except 1 reviewer bot comment

@haardikk21
haardikk21 requested a review from refcell August 18, 2026 16:05
@github-actions

Copy link
Copy Markdown
Contributor

Review Summary

Block-production sensitivity: This PR touches the builder RPC insertion path and the payload builder transaction selection loop (flashblocks/context.rs). I reviewed it against the block production review guide.

Findings

No critical or blocking issues found.

The flashblocks/context.rs changes are strictly additive metrics instrumentation — no control flow changes, no new error paths, no panics. The metrics calls are infallible.

The shadow injection path (ShadowValidityBuilderApi) is well-guarded:

  • Feature requires two opt-in flags (--builder.enable-experimental-validity-transactions + --builder.shadow-validity-injection.enabled)
  • Config validation enforces that injection cannot be enabled without validity support (with_shadow_validity returns Err)
  • The injected balance(sender) > 0 predicate is behavior-preserving: any sender with zero balance would fail gas payment anyway, so no valid transaction can be excluded by this predicate
  • The injection adds exactly 1 predicate to an empty vec (guarded by ExistingValidity and BundleValidity early returns), which stays well within max_validity_predicates (default 64)
  • The inner BuilderApiImpl performs its own validate() call after injection, providing defense-in-depth

Minor observations (non-blocking):

  • The samples() function uses the trailing bytes of the raw transaction as entropy. This is deterministic and avoids re-hashing, but the distribution quality depends on the s scalar's low bytes being reasonably uniform across transactions — which holds in practice for ECDSA signatures.
  • The rescan metrics path in context.rs correctly omits the has_validity_predicates guard because rescanned transactions are necessarily predicate-bearing (they come from the predicate_index).

Test coverage is thorough: unit tests for config validation, injection behavior, sampling determinism, and an integration test proving the injected predicate doesn't alter consensus bytes or state transitions.

@github-actions

Copy link
Copy Markdown
Contributor

Base Std historical fork tests

Fork Result Passed Failed Skipped base/base base-anvil base-std
Beryl pass 616 0 13 7e85aee3 6d744e03 4658f1b7
Cobalt pass 721 0 14 7e85aee3 ae7557c4 3f899009

View run

@haardikk21
haardikk21 enabled auto-merge August 18, 2026 16:43
@github-actions

Copy link
Copy Markdown
Contributor

Caution

This PR may regress performance. 4 benchmark(s) slower by more than 10% beyond the noise band: tx_selection_best_transactions_chained/1000_senders_100000 (+20.7%), tx_selection_parkable_payload/transactions/10000 (+12.1%), tx_selection_predicate_rescan/transactions=10000_predicate_transactions=1000_predicates=1_stat (+10.4%), tx_selection_predicate_rescan/transactions=10000_predicate_transactions=10_predicates=1_state= (+10.1%).

Benchmark results (advisory)

Median time on the PR head versus the base branch, measured on the same host. Wall-clock, so a change is only flagged when it clears ±10% and the confidence intervals do not overlap. This check never blocks a merge.

Benchmark Base Head Δ median
batch_queue/drain_cached_span_batches 21.20 µs 21.64 µs +2.0%
batch_transaction_encoding/encode_in_place 110.36 µs 117.31 µs +6.3%
batch_transaction_encoding/temporary_frame_buffers 267.84 µs 197.56 µs -26.2% ✅ faster
execution/Compute root, fully open trie - 4096 nodes 3.88 ms 3.91 ms +0.7%
execution/Compute root, fully open trie - 65,536 nodes 79.89 ms 78.29 ms -2.0%
execution/Delete 16 nodes - 4096 nodes 91.56 µs 92.85 µs +1.4%
execution/Delete 16 nodes - 65,536 nodes 4.25 ms 4.28 ms +0.5%
execution/Insertion - 4096 nodes 987.79 µs 991.03 µs +0.3%
execution/Insertion - 65,536 nodes 27.50 ms 27.17 ms -1.2%
execution/Open 1024 nodes - 4096 nodes 29.45 µs 29.76 µs +1.1%
execution/Open 1024 nodes - 65,536 nodes 37.66 µs 36.36 µs -3.4%
tx_selection_best_transactions/end_to_end/1000 185.92 µs 184.99 µs -0.5%
tx_selection_best_transactions/end_to_end/10000 2.53 ms 2.58 ms +1.9%
tx_selection_best_transactions/end_to_end/100000 39.20 ms 38.67 ms -1.3%
tx_selection_best_transactions/iterate/1000 142.65 µs 142.13 µs -0.4%
tx_selection_best_transactions/iterate/10000 1.93 ms 1.93 ms -0.3%
tx_selection_best_transactions/iterate/100000 27.44 ms 26.82 ms -2.3%
tx_selection_best_transactions/snapshot/1000 53.31 µs 53.62 µs +0.6%
tx_selection_best_transactions/snapshot/10000 684.57 µs 684.25 µs -0.0%
tx_selection_best_transactions/snapshot/100000 12.04 ms 12.30 ms +2.2%
tx_selection_best_transactions_chained/1000_senders_100000 40.55 ms 48.94 ms +20.7% ⚠️ slower
tx_selection_best_transactions_chained/1_senders_100000 26.63 ms 26.77 ms +0.5%
tx_selection_parkable_payload/transactions/1000 388.91 µs 414.73 µs +6.6%
tx_selection_parkable_payload/transactions/10000 4.65 ms 5.21 ms +12.1% ⚠️ slower
tx_selection_parkable_payload/transactions/100000 64.03 ms 65.32 ms +2.0%
tx_selection_predicate_index/parked=100000_state=shared 179.97 µs 176.96 µs -1.7%
tx_selection_predicate_index/parked=100000_state=unique 58.0 ns 55.4 ns -4.6%
tx_selection_predicate_index/parked=10000_state=shared 16.63 µs 16.75 µs +0.7%
tx_selection_predicate_index/parked=10000_state=unique 58.0 ns 56.8 ns -2.1%
tx_selection_predicate_index/parked=1000_state=shared 1.66 µs 1.65 µs -0.6%
tx_selection_predicate_index/parked=1000_state=unique 59.0 ns 55.4 ns -6.1%
tx_selection_predicate_rescan/transactions=10000_predicate_transactions=1000_predicates=1_stat 6.24 ms 6.89 ms +10.4% ⚠️ slower
tx_selection_predicate_rescan/transactions=10000_predicate_transactions=1000_predicates=8_stat 7.52 ms 6.42 ms -14.6% ✅ faster
tx_selection_predicate_rescan/transactions=10000_predicate_transactions=100_predicates=1_state 5.41 ms 5.84 ms +8.0%
tx_selection_predicate_rescan/transactions=10000_predicate_transactions=100_predicates=8_state 5.80 ms 5.28 ms -8.9%
tx_selection_predicate_rescan/transactions=10000_predicate_transactions=100_predicates=8_state_2 6.19 ms 5.55 ms -10.3% ✅ faster
tx_selection_predicate_rescan/transactions=10000_predicate_transactions=10_predicates=1_state= 5.39 ms 5.94 ms +10.1% ⚠️ slower
tx_selection_predicate_rescan/transactions=10000_predicate_transactions=10_predicates=8_state= 5.42 ms 5.23 ms -3.5%

View run · Re-run benchmarks

@haardikk21
haardikk21 added this pull request to the merge queue Aug 18, 2026
Merged via the queue into main with commit cea7945 Aug 18, 2026
53 of 57 checks passed
@haardikk21
haardikk21 deleted the shadow-validity-injection branch August 18, 2026 20:33
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