Gravity v1.7.1
Release: v1.7.1
Full Changelog: v1.7.0...v1.7.1
Related pinned components:
- gravity-reth: rev
1aec7b75— picks up five greth PRs since v1.7.0: #342 (randomness precompile), #346 (BLS PoP precompile gas guard), #355 (audit-#696 P0 filter hardening), #357 (audit-#710 InvalidTransaction filter), #356 (audit-#715 state-root wipe+recreate fix)
This release ships three audit follow-ups rolled up from gravity-audit (#696 P0 / #710 / #715), each of them a chain-halt or consensus-fork class issue, plus a randomness precompile and the BLS PoP gas guard.
Highlights
- CRITICAL: state-root wipe+recreate fix. When an account was selfdestructed and recreated in the same block, the prior
wipedbranch in greth's nested-trie code returnedEMPTY_ROOT_HASHand continued without applying the recreated slots. The new slots were silently dropped from both the computed state root and the persistedStoragesTrieV2table — a homogeneous gravity cluster would have agreed on the wrong state root (no self-halt) but forked from geth / upstream reth with permanent on-disk storage loss. Fixed in greth#356 / SDK#754. - Chain-halt class panics on byzantine inputs closed (greth#355 + greth#357). The pre-execution filter now covers the full
revm::InvalidTransactionvariant set reachable throughOrderedBlocktxs that bypass the local mempool — blob shape, EIP-3860 init-code limit, gas/priority-fee, empty authList, balance, chain-id, EIP-3607 sender-with-code. - BLS PoP precompile chain-halt closed (greth#346). The precompile returned
Ok(gas_used = 110_000)without checking forwarded gas; any funded account could panic every validator by calling the precompile address withgas_limit ∈ [intrinsic, 110_000), and because the poison tx was in the consensus log, restart-replay would re-panic. Fix: gas-first guard, returningPrecompileError::OutOfGasso the dispatcher takes the normal OOG branch. - Randomness precompile lands in greth#342 (Alpha-hardfork-gated, tiered gas).
- Consensus recovery completeness: replay the persisted highest-ordered-cert during
BlockStorestartup so a node that already has the ordered suffix in DB but receives no newer certs still sends it to execution (#737).
What's Changed
Bug Fixes — Consensus
- fix(consensus): replay ordered path after recovery by @Lchangliang in #737
- Moves
replay_ordered_path_if_neededintoBlockStoreso it can be shared by recovery and sync paths. - Calls it after
recover_blocks()duringBlockStorestartup; previously a node could restart with a persistedhighest_ordered_certahead ofordered_root, receive no newer certs from peers, and the old sync-only path would never call the replay helper — the ordered suffix would sit in DB but never be sent to execution. SyncManagercontinues to use the same replay helper after adding certs.
- Moves
Tests
Dependencies (greth) — Audit Follow-ups
-
chore(deps): bump gravity-reth to 408ef602 (audit-710 InvalidTransaction filter) + e2e by @nekomoto911 in #753
- Bumps greth
0adbb4c9 → 408ef602(gravity-reth#357), which closes the remaining 6 of 9revm::InvalidTransactionvariants at the admission filter (gas/priority fee, empty authList, balance, chainId, EIP-3607 sender-with-code). Closes the audit-#710 follow-up: the executor cannot recover fromEVMError, so the filter is the only practical defense. - Two new Prague e2e cases in
cluster_test_cases/prague/test_eip7702.py:- P-B7 — hand-built type-4 tx with empty
authorizationList(eth_account refuses to sign the malformed form, so the test encodes RLP directly — a hostile client would too). Oracle: RPC rejects at admission + chain keeps producing blocks (gravity_nodedidn't panic onInvalidTransaction::EmptyAuthorizationList). - P-B8 — EIP-3607 designator carve-out: an EOA with an installed 7702 delegation designator can still send a plain type-2 tx with its own key. Without the carve-out the filter would silently break every 7702-based smart-account flow.
- P-B7 — hand-built type-4 tx with empty
- 13/13 Prague cases passed locally in 92 s; existing P-B1..P-B6 + all EIP-2935 cases unaffected.
- Bumps greth
-
chore(deps): bump gravity-reth to 1aec7b75 (audit-715 wipe+recreate state root) by @nekomoto911 in #754
- Bumps greth
408ef602 → 1aec7b75(gravity-reth#356), which fixes the CRITICAL nested-trie bug surfaced by gravity-audit#715. When an account is selfdestructed and recreated in the same block, revm emitsDestroyedChangedwithHashedStorage { wiped: true, storage: { new slots } }. The priorwipedbranch incrates/trie/db/src/nested_hash.rsreturnedEMPTY_ROOT_HASHandcontinued without ever applying the recreated slots. The new slots were silently dropped from both the computed state root and the persistedStoragesTrieV2table. - Cluster-level impact: a homogeneous gravity cluster would have agreed on the wrong state root (no self-halt) but forked from geth / upstream reth with permanent on-disk storage loss — a funds-safety / consensus-divergence issue.
- Fix in greth: when
wipedis true, build a fresh storage trie from an empty base, insert the recreated non-zero slots, take the real storage root, and only emitEMPTY_ROOT_HASHwhen the post-wipe storage map is genuinely empty. Old on-disk nodes are still delete-marked. - Greth-side coverage ships in
wipe_recreate_e2e.rs+mainnet_replay.rs+ arepro_nested_hash_wiped_recreate_storage_rootdifferential test; no SDK-side wiring needed.
- Bumps greth
gravity-reth changes since v1.7.0
The greth pin moved from 0adbb4c9 to 1aec7b75. Five commits, two of which (greth#355 and greth#346) made it into v1.7.1 without an intermediate SDK bump PR — they were rolled into the same chain as #357 / #356:
#342 feat: add randomness lookup precompile (9b5b7316)
Adds a Gravity-native precompile that returns the current block's randomness, gated on the Alpha hardfork. Iterations during review: gas tiering, eth_call semantics (returns live block randomness), repricing, shared implementation across the production EVM and bundle simulations, custom-precompile registration moved into the rpc impl so it applies uniformly to RPC-only paths.
#346 fix(bls-precompile): guard gas limit to prevent assert-panic chain halt (f39cdf39)
The BLS pop-verify precompile (0x…1625f5001) is registered in the user-tx custom_precompiles set and charges a flat POP_VERIFY_GAS = 110_000 for any input, but bls_pop_verify_handler returned Ok(gas_used = 110_000) without checking the gas forwarded by the caller. The alloy-evm dispatcher then runs assert!(record_cost(110_000), "Gas underflow is not possible"): when forwarded gas is below the flat charge, record_cost returns false and the assert panics — deterministically aborting block execution. Because the handler returned Ok, the graceful PrecompileError::OutOfGas branch was never reached.
Exploitability: any funded account can trigger it with a single tx calling the address with gas_limit ∈ [intrinsic, 110_000) and 144 bytes of input. All validators panic executing the same ordered block, and the poison tx is already in the consensus log so restart-replay re-panics — a hard-to-recover network halt (gravity-audit#678).
Fix: guard the gas limit in bls_pop_verify_handler before the verification logic, returning PrecompileError::OutOfGas when POP_VERIFY_GAS > input.gas, so the dispatcher takes the normal PrecompileOOG branch — the gas-first pattern used by every standard revm precompile.
#355 fix(filter): close gravity-audit#696 P0 — blob + EIP-3860 + error-path hardening (91d2af85)
Audit #696 reported four panic triggers reachable by a byzantine proposer through OrderedBlock txs that bypass the local mempool. Two were already closed by greth#343. This PR closes the remaining two and hardens the error path so future filter gaps cannot double-panic:
- Trigger 2 (EIP-4844 blob shape) — Gravity does not support blob txs. The filter unconditionally rejects every type-3 tx, covering
EmptyBlobs,BlobVersionNotSupported,TooManyBlobs,BlobVersionedHashesNotSupported,BlobGasPriceGreaterThanMax,MaxFeePerBlobGasNotSupported, andBlobCreateTransaction. - Trigger 4 (EIP-3860 init-code overflow) — Create txs with
input.len() > MAX_INITCODE_SIZE(49 152) are rejected before reaching grevm, preventing theCreateInitCodeSizeLimitvalidation error from panicking the executor. - Defensive cleanup in
parallel_execute.rs:120-130— replaces a.nth(e.txid).unwrap()that could double-panic if a future filter gap surfaces.
#357 fix(filter): close gravity-audit#710 + add pipe panic smoke test (408ef602)
Audit #710 enumerated the full set of revm::InvalidTransaction variants reachable through OrderedBlock txs that bypass the local mempool. greth#355 closed two (EIP-4844 shape + EIP-3860 init-code). This closes the remaining six and reframes the filter's role:
GasPriceLessThanBasefee—tx.max_fee_per_gas()must be ≥base_fee_per_gas. The unified accessor collapses legacygas_priceinto the same predicate.PriorityFeeGreaterThanMaxFee— EIP-1559+ tx withmax_priority > max_fee.EmptyAuthorizationList— post-PragueTxEip7702withauthorization_list = []. The existing pre-Prague reject covered theEip7702NotSupportedbranch; this catches the post-Prague companion.LackOfFundForMaxFee— balance gate now usesmax_fee_per_gas * gas_limit(revm's worst-case pre-deduction), noteffective_gas_price * gas_limit. Per-sender simulated balance is still reduced by the effective cost so subsequent txs see what revm sees post-refund.- Chain-id mismatch — closed via the unified accessor.
- EIP-3607 sender-with-code — carved out for 7702-installed delegation designators so smart-account flows continue to work.
#356 fix(state_root): nested-trie wipe+recreate drops recreated storage (1aec7b75)
See SDK#754 above. Critical fix for a silent state-root divergence on selfdestruct+recreate, scoped to crates/trie/db/src/nested_hash.rs.