Summary
Ethlambda's build_block in crates/blockchain/src/store.rs:1018 greedily accumulates aggregated attestations from the payload pool with no per-validator dedup or size cap. During a stall, the pool grows unbounded and the next proposed block can exceed the spec's MAX_PAYLOAD_SIZE = 10 MiB (from leanSpec/networking/config.py), triggering MessageTooLarge on gossip publish and snappy decoded len exceeds max on receiving peers.
Reproduction
- Run a multi-client devnet
- Cause any disruption that backs up the attestation pool (peer drop, brief
network partition, slow processor, etc.)
- The next block ethlambda proposes will accumulate all pending payloads
- With current XMSS proof sizes (~253 KB each), more than ~39 attestations
exceeds the 10 MiB spec limit
- Observe
Swarm adapter: publish failed err=MessageTooLarge in ethlambda's
own logs, and snappy decoded len N exceeds max 10485760 in any
peer that tries to receive the block
Root cause
build_block() iterates the entire aggregated_payloads pool and includes every entry whose source matches the current justified checkpoint. There is no per-validator dedup, no size cap, and no count cap. In normal operation the pool is small (5-10 entries) so this works fine. During stall recovery the pool can grow to 100+ entries, all of which get packed into a single block.
Spec references
Summary
Ethlambda's
build_blockincrates/blockchain/src/store.rs:1018greedily accumulates aggregated attestations from the payload pool with no per-validator dedup or size cap. During a stall, the pool grows unbounded and the next proposed block can exceed the spec'sMAX_PAYLOAD_SIZE = 10 MiB(fromleanSpec/networking/config.py), triggeringMessageTooLargeon gossip publish andsnappy decoded len exceeds maxon receiving peers.Reproduction
network partition, slow processor, etc.)
exceeds the 10 MiB spec limit
Swarm adapter: publish failed err=MessageTooLargein ethlambda'sown logs, and
snappy decoded len N exceeds max 10485760in anypeer that tries to receive the block
Root cause
build_block() iterates the entire aggregated_payloads pool and includes every entry whose source matches the current justified checkpoint. There is no per-validator dedup, no size cap, and no count cap. In normal operation the pool is small (5-10 entries) so this works fine. During stall recovery the pool can grow to 100+ entries, all of which get packed into a single block.
Spec references
leanSpec/src/lean_spec/subspecs/networking/config.py:12MAX_PAYLOAD_SIZE: Final[int] = 10 * 1024 * 1024leanSpec/src/lean_spec/subspecs/sync/config.py:11MAX_BLOCKS_PER_REQUEST: Final[int] = 10