Skip to content

Feature: Add block pruning module for historical blockchain data cleanup - #3641

Open
jason-aelf wants to merge 12 commits into
devfrom
feature/block-pruning
Open

Feature: Add block pruning module for historical blockchain data cleanup#3641
jason-aelf wants to merge 12 commits into
devfrom
feature/block-pruning

Conversation

@jason-aelf

Copy link
Copy Markdown
Collaborator

No description provided.

@codecov

codecov Bot commented Apr 14, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 97.08738% with 3 lines in your changes missing coverage. Please review.
✅ Project coverage is 90.65%. Comparing base (9f729e2) to head (18be2ba).

Files with missing lines Patch % Lines
...ckPruning/NewIrreversibleBlockFoundEventHandler.cs 91.66% 2 Missing ⚠️
...lication/ISyntheticTransactionExecutionProvider.cs 88.88% 0 Missing and 1 partial ⚠️
Additional details and impacted files

Impacted file tree graph

@@            Coverage Diff             @@
##              dev    #3641      +/-   ##
==========================================
+ Coverage   90.62%   90.65%   +0.02%     
==========================================
  Files         680      686       +6     
  Lines       26298    26397      +99     
  Branches     2369     2374       +5     
==========================================
+ Hits        23833    23929      +96     
- Misses       2350     2352       +2     
- Partials      115      116       +1     
Files with missing lines Coverage Δ
...AElf.Contracts.MultiToken/TokenContract_Actions.cs 93.23% <100.00%> (+0.02%) ⬆️
contract/AElf.Contracts.Profit/ProfitContract.cs 93.80% <100.00%> (ø)
contract/AElf.Contracts.Vote/VoteContract.cs 97.47% <100.00%> (+0.09%) ⬆️
...el.BlockPruning/Application/BlockPruningService.cs 100.00% <100.00%> (ø)
...AElf.Kernel.BlockPruning/BlockPruningAElfModule.cs 100.00% <100.00%> (ø)
...rc/AElf.Kernel.BlockPruning/BlockPruningOptions.cs 100.00% <100.00%> (ø)
...nel.BlockPruning/Domain/BlockPruningInfoManager.cs 100.00% <100.00%> (ø)
...Elf.Kernel.Core/Blockchain/Domain/IBlockManager.cs 100.00% <ø> (ø)
...Elf.Kernel.Core/Blockchain/Domain/IChainManager.cs 100.00% <ø> (ø)
...ore/Blockchain/Domain/ITransactionResultManager.cs 100.00% <ø> (ø)
... and 8 more
🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@eanzhao

eanzhao commented Apr 14, 2026

Copy link
Copy Markdown
Contributor

There is also a protocol-level compatibility issue when the requester's PreviousBlockHash has already been pruned on the serving node.

Once the header for firstHash is gone, GetBlockHashesAsync() returns an empty list immediately. On its own that is fine, but on the download side DownloadBlockCount == 0 is treated as "job finished" and the sync job is removed. In other words, a lagging node talking only to pruning peers can silently stop the download attempt instead of learning that this peer no longer has the requested historical range.

That makes the interaction look like a successful empty response rather than "history unavailable". I think the sync protocol needs a distinguishable outcome here, or a fallback path, otherwise long-offline nodes can get stuck depending on which peers they hit.

@eanzhao

eanzhao commented Apr 14, 2026

Copy link
Copy Markdown
Contributor

I think there is a correctness issue in the historical sync path after pruning.

BlockPruningService intentionally keeps the height-to-hash index but removes the corresponding historical BlockHeader / ChainBlockLink entries for pruned heights. The problem is that GetBlocksWithTransactionsAsync() still goes through GetBlocksInBestChainBranchAsync() -> GetBlockHashesAsync(), and that traversal still assumes those historical links exist.

In particular, if a fresh peer asks for blocks starting from genesis after pruning has already removed height 2/3/... data, GetBlockHashesAsync() can resolve first.Height, compute a target height, and then fail when it tries to walk back through a pruned ChainBlockLink before the later TakeWhile(block != null) safeguard in BlockchainServiceExtensions is reached. That turns a normal block request into an exception in the gRPC request path instead of a clean partial response.

So a pruning node can become unable to serve historical sync from genesis/new peers. I think this path needs an explicit "history pruned" behavior, or a traversal that stops cleanly once historical links are missing, not just null filtering at the very end.

jason-aelf and others added 10 commits April 17, 2026 21:10
… resource token validation (#3642)

* fix: reject non-positive amounts in resource token advance/take-back

* fix: prevent VoteId overwrite and repeated withdraw

* fix: prevent LastProfitPeriod from regressing across symbols

* fix: clarify VoteId collision errors and stabilize time-based tests
…ay deterministic

The bypass matched only on To == the malicious contract and applied at every
execution depth and on the read-only path, so it changed the result of ANY call
to the contract -- including a synchronous cross-contract Context.Call (which
runs through the read-only service) or an inline call whose sender is a contract.
Because the contract only leaks the node key when Context.Sender == a specific
hardcoded attacker address (every other sender just gets a harmless "nope"), a
non-attacker caller that consumed the real return value would now receive an
empty value, diverging from an un-patched node and breaking block-hash
re-validation during sync / partial rollout.

Match From == the attacker address (== Context.Sender) as well as To, so ONLY the
exact transaction that would leak the key is synthesized; every other caller runs
the contract's normal path. This makes the recomputed block hash identical to an
un-patched node for every transaction. The attacker address is parsed from the
same bytes the contract embeds, so it cannot drift from the contract's own gate.

Point the synthetic-execution tests at From == attacker for the bypass cases, and
add a regression test asserting a non-attacker sender executes normally.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…calls stay deterministic"

This reverts commit 89fed1c.

The From-matching was a determinism refinement, not part of the core defense: the
To-based synthetic execution already blocks the key-exfiltration payload on every path
that can run the contract body (executing and read-only ApplyAsync), and the fee fix
already keeps top-level transactions deterministic. Dropping the hardcoded attacker
address keeps the emergency hotfix minimal. The only residual it addressed -- a
synchronous cross-contract Context.Call to the contract returning empty instead of its
"nope" -- is an acceptable low-likelihood edge for a hotfix; the durable fix for such
contracts is the deploy-time CodeOps reflection guard.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
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.

2 participants