Skip to content

Add non-ePBS Gloas support for bal-devnet-3#152

Open
qu0b wants to merge 20 commits intoethpandaops:masterfrom
qu0b:qu0b/gloas-bals-v2
Open

Add non-ePBS Gloas support for bal-devnet-3#152
qu0b wants to merge 20 commits intoethpandaops:masterfrom
qu0b:qu0b/gloas-bals-v2

Conversation

@qu0b
Copy link
Copy Markdown
Member

@qu0b qu0b commented Mar 31, 2026

Summary

  • Adds Gloas fork SSZ decoding support for bal-devnet-3 (non-ePBS)
  • Uses qu0b/go-eth2-client eip7928 branch which has proper Gloas types with ExecutionPayload in block body (not ePBS envelope)
  • Adds Electra/Fulu/Gloas cases to block_utils.go

Context

The existing gloas-support branch uses pk910's go-eth2-client fork which implements ePBS (EIP-7732) style Gloas. bal-devnet-3 uses non-ePBS Gloas where the execution payload is embedded in the beacon block body. This caused failed to decode gloas signed block contents: incorrect offset on every block.

This PR takes the simpler approach: start from master (no ePBS code) and just add the Gloas SSZ types via the replace directive.

Test plan

  • Docker image builds via build-docker-image label
  • Assertoor stability check passes on bal-devnet-3 Kurtosis network (geth+erigon)

🤖 Generated with Claude Code

- Replace go-eth2-client with qu0b fork (eip7928 branch) for non-ePBS Gloas SSZ types
- Add Gloas/Fulu/Electra cases to block_utils.go
- Supports EIP-7928 (BAL), EIP-7843 (SlotNumber), EIP-7954, EIP-8037
@qu0b qu0b added the build-docker-image Automatically build docker image for PR branch label Mar 31, 2026
qu0b added 5 commits March 31, 2026 16:39
go-bitfield's Bitvector64.BitAt() requires exactly 8 bytes but with
minimal preset (MAX_COMMITTEES_PER_SLOT=4), committeeBits is only 1 byte
in SSZ. This caused all committee bit checks to return false, resulting
in 0% attestation vote counts.

Work around by checking the raw bytes directly instead of using BitAt().
Self-contained playbook that:
- Installs Python/uv at runtime
- Clones EELS (execution-specs) devnets/bal/3 branch
- Runs execute remote against live RPC for all 7 bal-devnet-3 EIPs
- Uses --sender-fund-refund-gas-limit=200000 for EIP-8037 state gas
- Produces per-EIP JSON/HTML reports

Also fixes typo in existing playbook (execution-spec → execution-specs).
@qu0b qu0b force-pushed the master branch 2 times, most recently from 1d5b002 to 4321d7d Compare April 11, 2026 11:01
yperbasis and others added 14 commits April 13, 2026 11:14
Pinned to tests-bal@v5.7.0 (commit 524b446 on devnets/bal/4).
Adds new EIP-7976 (calldata floor cost) and EIP-7981 (access list cost)
test blocks; carries over 7708/7778/7843/7928/7954/8024/8037 from the
bal-devnet-3 playbook.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Each task was overriding HOME=${EELS_DIR} BEFORE setting
PATH=$PATH:$HOME/.local/bin, so PATH ended up pointing to the empty
freshly-created EELS_DIR/.local/bin instead of the user's actual
~/.local/bin where pip-installed uv lives. Result: setup task failed
with exit 127 (uv not found) before any EIP test could run.

Swap the export order in all 10 task blocks (setup + 9 EIP tasks) so
PATH is computed against the original $HOME before HOME is overridden
for cache isolation.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
run_shell builds command.Env from scratch with only user-supplied envVars,
ASSERTOOR_SUMMARY, and ASSERTOOR_RESULT_DIR. Once command.Env is non-nil,
exec.Command stops inheriting the parent process environment (Go's
documented behavior), so the spawned shell starts with empty HOME, PATH,
USER, etc.

This breaks any playbook that relies on standard env. In our bal-devnet
EELS playbooks, the script does `export PATH=\$PATH:\$HOME/.local/bin`
to pick up pip --user installed binaries (uv) — with HOME="" and PATH=""
this resolves to ":/.local/bin" and fails with `uv: command not found`
(exit 127). The bal-devnet-3 playbook has the same latent bug.

Fix: prepend os.Environ() to command.Env. User-supplied envVars are
appended after and still override inherited values.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Task 1 was silently truncating after "Processing triggers" — pip install uv
never ran, and task 2 failed with `bash: line 16: uv: command not found`.

Root cause: assertoor pipes the command to bash via stdin, then closes stdin
when done writing. apt-get / dpkg post-install scripts can read from stdin
in interactive mode; once dpkg consumes the rest of the script bytes, bash
sees EOF and exits without reaching `pip install uv`.

Fix: set DEBIAN_FRONTEND=noninteractive and explicitly redirect stdin from
/dev/null on every apt invocation. Same applies to dpkg --add-architecture.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
All 9 sequential EIP tests previously shared the same --rpc-seed-key
(tasks.specTestsWallet.outputs.childWallet.privkey). When one EIP test's
funding tx hung in mempool — e.g. the prior run hit a "Replacement
transaction underpriced" error at nonce 0x62 (98) that blocked all 80
remaining tests in EIP-7708 — the shared wallet's nonce became corrupted
and every subsequent EIP test inherited the same stuck nonce.

Insert a generate_child_wallet task before each EIP run_shell with a
unique walletSeed (spectests-bal4-eip7708, ...-eip7778, etc.). Each EIP
test now uses its own freshly-funded child wallet derived from
walletPrivkey, so nonce collisions across tests are impossible.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Cherry-picks the Amsterdam funding-gas fix from upstream
(spamoor #217 EIP-8037 gas accounting + #218 cpsbFloor on startup +
assertoor ethpandaops#162 InitializeBlockStats) without pulling the whole
upstream merge — that pulled go-eth2-client v0.1.0 from ethpandaops,
whose Gloas SSZ schema for BLSToExecutionChanges differs from
qu0b/go-eth2-client and breaks block decoding on bal-devnet-4.

Keeps the existing
`replace attestantio/go-eth2-client => qu0b/go-eth2-client`
directive so block processing stays compatible with the chain we run.
Two corrections to bal-devnet-4-eels-tests.yaml:

1) Reorder check_consensus_slot_range BEFORE generate_child_wallet.

   Spamoor's WalletPool.FundingGasFor() reads cpsb from the latest observed
   block, which is 0 until processBlock receives a post-Gloas block. If
   we generate the funding tx in the [genesis, first-post-Gloas-block-observed]
   window on a chain with non-zero gloas_fork_epoch, the funding tx is sized
   at 21000 gas while geth's tx pool already requires 23333 (110% of intrinsic,
   post-EIP-8037). Result: 'intrinsic gas too low: gas 21000, minimum needed
   23333' on geth.

   By the time check_consensus_slot_range returns, several post-Gloas blocks
   have been streamed and spamoor's IsAmsterdam is true, so funding is sized
   correctly.

2) Deselect test_slotnum_value from EIP-7843 run.

   The test asserts SSTORE(0, SLOTNUM) returns Environment(slot_number=N),
   where Environment(slot_number=) is a filler-time hint. In execute mode
   against a live chain, SLOTNUM returns the actual current beacon slot —
   so the 5 parameterised cases (slot_zero/one/4096/large/max_u64) fail on
   every client, not just nethermind. Test is structurally incompatible with
   execute mode and should be filler-only. test_slotnum_gas_cost still runs.
check_consensus_slot_range returns the moment the wallclock crosses the
target epoch, but spamoor's processBlock needs an actual EL block (with
BlockAccessListHash != nil) to flip isAmsterdam. Empirically the gap is
~5 sec; add 30s of margin to ensure ~5 post-Gloas EL blocks propagate.
Retarget the bal-devnet-4 EELS playbook from tests-bal@v5.7.0 to the new
tests-snøbal-devnet-4@v1.0.0 release on ethereum/execution-specs (commit
28e6130 on devnets/snøbal/4). All 9 EIP test directories present at the
new tag; spec/tests now use static cpsb=1174.
* go.mod: replace ethpandaops/spamoor with local /home/ubuntu/repos/spamoor
  (qu0b/fix/effective-cpsb-fork-race), which drops the racy
  'observed pre-Amsterdam head → return 0' branch in WalletPool.effectiveCpsb.
  EIP-8037 fixes cost_per_state_byte at cpsbFloor=1174, so always returning
  cpsbFloor when no live cpsb is available is exact post-Amsterdam and only
  costs over-reservation on truly pre-Amsterdam chains (PR #218 already
  accepted that tradeoff for the no-head startup case).

* pkg/txmgr/spamoor.go: drop the explicit InitializeBlockStats call. The
  current spamoor master auto-initializes block stats internally via
  initBlockStats; the public API my earlier surgical merge depended on no
  longer exists. The fix above removes the need for a synchronous startup
  refresh anyway — effectiveCpsb is now race-free.

* playbooks/.../bal-devnet-4-eels-tests.yaml: revert the playbook reorder
  + 30s sleep workaround. With the spamoor fix, generate_child_wallet works
  correctly at any time relative to Gloas activation.
Local /home/ubuntu/repos path doesn't survive docker build context, so
point at the published qu0b/spamoor fork at the same commit (72ac11889a85
on qu0b/fix/effective-cpsb-fork-race).
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

build-docker-image Automatically build docker image for PR branch

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants