56045 bc insight block packing starvation via oversized priority transactions

Submitted on Oct 9th 2025 at 18:50:50 UTC by @OxPrince for Attackathon | VeChain Hayabusa Upgrade

  • Report ID: #56045

  • Report Type: Blockchain/DLT

  • Report severity: Insight

  • Target: https://github.com/vechain/thor/compare/master...release/hayabusa

  • Impacts:

    • Temporary freezing of network transactions by delaying one block by 500% or more of the average block time of the preceding 24 hours beyond standard difficulty adjustments

Description

Brief/Intro

The mempool wash routine sorts executable transactions by effective priority fee and truncates the slice to the global limit (txpool/tx_pool.go:498-514). When block construction starts, the packer takes this capped snapshot (cmd/thor/node/packer_loop.go:119-147) and, for each entry, calls flow.Adopt. If executing a transaction would exceed the remaining block gas but there is still room for a minimum-sized clause, flow.Adopt yields errTxNotAdoptableNow instead of failing hard (packer/flow.go:157-163). The packer simply skips to the next transaction on that same priority-sorted list; it never falls back to lower-fee entries that were pruned out. An attacker who keeps the executable slice saturated with “almost full block” transactions can therefore ensure that every post-seed candidate is skipped, leaving most of the gas limit unused.

Vulnerability Details

  • wash evicts any executable transactions beyond Options.Limit, favouring the highest priority fees (txpool/tx_pool.go:498-514). Legitimate lower-paying transactions never reach Executables() once the attacker fills the cap.

  • pack captures a single Executables() snapshot per block and does not refresh it (cmd/thor/node/packer_loop.go:119-149). Any transaction skipped with errTxNotAdoptableNow simply persists in the pool for the next block.

  • flow.Adopt returns errTxNotAdoptableNow whenever the candidate needs more gas than remains yet the block still has capacity for the minimum clause (packer/flow.go:157-163), reflecting the intent to try “smaller” transactions later. Because the trimmed list no longer contains smaller ones, this degenerates into a permanent skip.

  • Default node settings (cmd/thor/main.go:51-55) cap the executable set at 10 000 entries with at most 128 per account. An attacker can span ~79 accounts to populate the cap with high-gas transactions while complying with per-account quotas.

Exploit Walkthrough

1

Prepare funding and accounts

Pre-fund ~79 accounts with enough VTHO to satisfy the pending-cost check (txpool/tx_pool.go:290-305) for 128 transactions each. With gas ≈9 980 000 and gas price ≈ base fee, each pending transaction ties up ≈100 VTHO but is not spent if it never executes.

2

Submit oversized high-priority transactions

Each account submits transactions whose declared gas is just below the block gas limit (≈10 000 000) and sets priority fees marginally above prevailing demand so they dominate the sorted list.

3

Seed a small transaction before packing

Just before a block is packed, submit a single small “seed” transaction with an equal or higher priority fee so it sorts to the top. The remaining entries in the executable slice are all oversized.

4

Packing causes skipping of oversized entries

During packing, the seed executes, leaving the block with <9 980 000 gas available. Every oversized transaction now triggers errTxNotAdoptableNow, so the packer skips them and never pulls in lower-fee transactions (they were truncated in the previous steps). The block is published mostly empty.

5

Repeat to sustain the effect

Repeat a fresh seed transaction before each block; the oversized pool persists indefinitely because it is never executed or evicted.

Impact Details

  • Sustained throughput degradation: blocks can be reduced to a single minimum transaction while the chain stays live.

  • Honest users’ transactions are continually dropped from the executable set, effectively censoring them until the attacker stops.

  • Attack cost is limited to temporarily parking VTHO as pending collateral; the oversized transactions never execute, so the attacker does not burn gas.

References

Add any relevant links to documentation or code

Proof of Concept

Was this helpful?