Skip to content

fix: all block transactions stored in the block tree for migration#1122

Merged
roberts-pumpurs merged 17 commits into
masterfrom
rob/commitment-tx
Feb 10, 2026
Merged

fix: all block transactions stored in the block tree for migration#1122
roberts-pumpurs merged 17 commits into
masterfrom
rob/commitment-tx

Conversation

@roberts-pumpurs

@roberts-pumpurs roberts-pumpurs commented Feb 6, 2026

Copy link
Copy Markdown
Contributor

Describe the changes
Issue experienced on testnet: a commitment tx was not migrated to the database because it was not present in the mempool. The mempool had rejected the commitment tx from the block pool service because it deemed that the tx should be of commitment type v2, even tho the node was syncing historical blocks.

  • fix for missing txs: This PR decouples the txs that need to be migrated to the database from the txs that are present in the mempool. Every block that enters the block tree now also stores the block transactions in the tree - when the block is supposed to be migrated to the database, it re-uses the txs from the tree.
  • fix for wrong EMA: while writing a test for the ☝️ , it outlined the following issue: When restoring the block tree from the database on node restart, it was using the latest block (end of the restored range) to build the initial EMA snapshot, but it should use the start block (beginning of the restored range). Nodes that had been restarted with the bug would have had incorrect EMA snapshots in their block tree cache. This could cause:
    1. False EmaMismatch errors - The worst case for an unfixed restarted node is that it rejects valid blocks due to EmaMismatch and falls behind. It wouldn't produce invalid blocks or cause other nodes to reject its blocks - it would just get "stuck" rejecting the chain tip.
    2. Incorrect fee calculations - The pricing_ema (from 2 intervals ago) would be wrong,
      causing transaction validation failures.
  • Storage modules going out of sync: A missing code branch in StorageModule::new() initialization. When a storage module's packing_params.toml on disk already had a matching partition_hash, but the ledger or slot had changed since the last run, the code did nothing — it silently continued with stale ledger/slot values on disk.

Checklist

  • Tests have been added/updated for the changes.
  • Documentation has been updated for the changes (if applicable).
  • The code follows Rust's style guidelines.

Summary by CodeRabbit

  • New Features

    • Block migrations now carry per-block transaction payloads end-to-end and persist them at startup.
  • Bug Fixes

    • Enforced strict consistency between block headers and attached transaction headers; migration no longer relies on transient mempool lookups.
    • Migration processing now derives and uses in-event transaction data for commitment/submit/publish flows and promoted-height recovery.
  • Tests

    • Tests updated and expanded to exercise real per-block transaction flows and hardfork recovery/peer-sync scenarios.

@coderabbitai

coderabbitai Bot commented Feb 6, 2026

Copy link
Copy Markdown
Contributor

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Block migration now includes per-block transaction data: BlockMigratedEvent and migrate/send paths accept and propagate Arc<BlockTransactions); mempool migration and validation consume transactions from the event instead of querying the mempool; BlockTree and domain model store and validate per-block BlockTransactions.

Changes

Cohort / File(s) Summary
Block tree service transaction propagation
crates/actors/src/block_tree_service.rs
Added transactions: Arc<BlockTransactions> to BlockMigratedEvent; send_block_migration_message now accepts transactions; migration extracts transactions from block-tree cache and passes them into events/messages, removing mempool header lookups.
Actors — mempool integration
crates/actors/src/mempool_service/facade.rs, crates/actors/src/mempool_service/lifecycle.rs
MempoolFacade::migrate_block signature updated to accept Arc<BlockTransactions>; lifecycle handler now consumes event.transactions for commitment/submit/publish processing, DB insertion, and mempool cleanup (replaces prior mempool-derived header/ID lookups).
Domain model & storage
crates/domain/src/models/block_tree.rs, crates/domain/src/models/storage_module.rs
Added transactions: BlockTransactions to BlockMetadata; add_common/add_block accept and validate transactions against header tx IDs; startup/restore load data transactions and attach per-block BlockTransactions; storage_module adds partition drift detection/sync.
Tests — chain & validation
crates/chain/tests/... (multiple files: validation/*, block_production/difficulty_adjustment.rs, api/hardfork_tests.rs)
Tests updated to produce and forward real BlockTransactions instead of defaults; produce_block signature updated in blobs_rejected; new heavy integration test added (duplicate copy present).
Actors — block_tree test utilities & validation tests
crates/actors/src/block_tree_service/test_utils.rs, crates/actors/src/validation_service/active_validations.rs
Test helpers/validation tests updated to pass BlockTransactions::default() into BlockTree::add_common; imports adjusted.
P2P & test stubs
crates/p2p/src/block_status_provider.rs, crates/p2p/src/tests/util.rs
Test helpers and MempoolStub signature updated to accept Arc<BlockTransactions>; test-only call sites pass default transactions and unused params are prefixed.
Cargo / workspace
crates/domain/Cargo.toml
Added itertools.workspace = true workspace dependency used by domain model changes.

Sequence Diagram(s)

sequenceDiagram
  autonumber
  participant BlockTree as BlockTreeService
  participant Mempool as MempoolService
  participant DB as Database
  participant Validator as Validator/Consumer

  BlockTree->>BlockTree: extract BlockTransactions from cache
  BlockTree->>Mempool: emit BlockMigratedEvent(block_header, transactions)
  Mempool->>Mempool: process event.transactions (commitment, submit, publish)
  Mempool->>DB: insert commitment records and data-tx headers/metadata
  Mempool->>Mempool: cleanup mempool entries using in-event tx IDs
  Mempool->>Validator: notify downstream components of migrated block (with transactions)
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

  • feat: block bodies #1057: Propagates BlockTransactions through block migration and updates mempool/block-tree call sites — strong overlap with these changes.
  • feat: status endpoint #1099: Modifies mempool block-migration paths and related APIs for sourcing/persisting transaction headers — closely related to lifecycle edits.

Suggested reviewers

  • DanMacDonald
  • antouhou
🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'fix: all block transactions stored in the block tree for migration' accurately summarizes the main change: refactoring transaction handling to store transactions in the block tree rather than relying on mempool lookups during block migration.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch rob/commitment-tx

Tip

Issue Planner is now in beta. Read the docs and try it out! Share your feedback on Discord.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
crates/actors/src/mempool_service/facade.rs (1)

276-286: ⚠️ Potential issue | 🟡 Minor

migrate_block sends empty BlockTransactions — code path appears unused but should be addressed.

The facade's migrate_block sends BlockTransactions::default() in the BlockMigratedEvent. The handler (handle_block_migrated in lifecycle.rs) reads event.transactions to extract commitment transactions (line 836-840), so empty transactions would skip that processing step.

However, this facade method is not called from any production code paths. Real migrations use the block_tree_service and block_index_service implementations directly, not the facade. The only usage is in the test mock (crates/p2p/src/tests/util.rs).

Consider either removing this unused method from the trait, or documenting why empty transactions are acceptable here (e.g., if this is a legacy fallback path that should never be used with real transactions).

crates/actors/src/block_tree_service.rs (1)

454-479: ⚠️ Potential issue | 🟠 Major

Don’t fall back to empty transactions during migration.

Silently defaulting to empty transactions can reintroduce missing‑tx migrations if the cache lookup fails. Treat this as an error instead.

✅ Suggested fix
-            let transactions = {
-                let cache = self.cache.read().expect("poisoned lock");
-                cache
-                    .blocks
-                    .get(&block_to_migrate.block_hash)
-                    .map(|meta| meta.transactions.clone())
-                    .unwrap_or_default()
-            };
+            let transactions = {
+                let cache = self.cache.read().expect("poisoned lock");
+                cache
+                    .blocks
+                    .get(&block_to_migrate.block_hash)
+                    .map(|meta| meta.transactions.clone())
+                    .ok_or_else(|| eyre::eyre!(
+                        "missing block metadata for migration block {}",
+                        block_to_migrate.block_hash
+                    ))?
+            };
🤖 Fix all issues with AI agents
In `@crates/chain/tests/api/hardfork_tests.rs`:
- Around line 777-781: The test currently awaits genesis_node.post_data_tx(...)
but drops the Result, which can hide submission failures; change the calls to
propagate errors by using the `?` operator on the awaited Result (e.g.,
`genesis_node.post_data_tx(anchor, data.into_bytes(), signer).await?`) or
otherwise return a Result from the test and propagate errors; update both
occurrences of genesis_node.post_data_tx(...) (around the current block and the
similar block at the other location) so failures surface immediately instead of
being silently ignored.

In `@crates/domain/src/models/block_tree.rs`:
- Around line 417-452: The data-tx validation currently compares the union of
all ledger tx IDs which allows mismatches across ledgers; change it to validate
per-ledger equality: for each ledger in block.data_ledgers compute expected_ids
from ledger.tx_ids and fetch the corresponding entry in transactions.data_txs
(use the ledger's identifier/key that maps into transactions.data_txs), then
ensure the HashSet of expected IDs equals the HashSet of actual IDs for that
ledger; also ensure there are no extra ledger entries in transactions.data_txs
(i.e., check that transactions.data_txs.keys() == block.data_ledgers.keys()).
Update the block_tree.rs checks around expected_data_ids/actual_data_ids to
iterate per-ledger and use eyre::ensure! per ledger with a clear error message
including the block hash and ledger identifier.

Comment thread crates/chain/tests/api/hardfork_tests.rs
Comment thread crates/domain/src/models/block_tree.rs

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

🤖 Fix all issues with AI agents
In `@crates/actors/src/mempool_service/lifecycle.rs`:
- Around line 847-858: The current flow removes commitment transactions from
mempool via mempool_state.remove_commitment_txs(...) before persisting them with
irys_db.update_eyre(...) and insert_commitment_tx(...), which risks losing them
if the DB commit fails; change the ordering so you first persist all
commitment_txs inside irys_db.update_eyre (calling insert_commitment_tx for
each) and only after that succeeds call
mempool_state.remove_commitment_txs(...), or alternatively wrap the DB commit in
a try and on failure re-insert the removed txs via mempool_state.add or a
rollback helper; update references in this block (remove_commitment_txs,
insert_commitment_tx, irys_db.update_eyre, mempool_state) accordingly so
removals happen only after a successful DB persist.
- Around line 889-892: The loop is unnecessarily cloning publish_txs; since
publish_tx_ids is already computed above and publish_txs isn't used afterward,
change the iteration inside the self.irys_db.update_eyre closure from for mut
header in publish_txs.clone() to consuming iteration (e.g., for mut header in
publish_txs.into_iter()) so the vector is moved instead of cloned, keeping the
surrounding update_eyre(...) and mut header usage intact.

In `@crates/chain/tests/api/hardfork_tests.rs`:
- Around line 1078-1101: After the second peer restart, add the same DB
integrity checks you ran in Step 7 (the block index hash checks, PoA chunk
validations and any DB content assertions) for the restarted peer_node: after
peer_node.wait_until_height(final_height, ...).await? and before
peer_node.stop().await, call the existing helper(s) used earlier (e.g. the
functions that validated block index hashes, PoA chunks and DB contents in Step
7—reuse those exact function names) to spot-check the restarted node’s database
contents and ensure they match expected state.
- Line 799: The subtraction pre_activation_height - block_migration_depth as u64
can underflow; change the calculation that sets pre_aurora_migrated_height to
use saturating_sub (e.g.,
pre_activation_height.saturating_sub(block_migration_depth as u64)) or add a
guard that ensures block_migration_depth as u64 <= pre_activation_height before
subtracting, and apply the same fix to the other occurrence mentioned (the
analogous subtraction at the second spot around line 918) so both use saturating
arithmetic or a proper check.

Comment on lines 847 to 858
self.mempool_state
.remove_commitment_txs(commitments.values().map(CommitmentTransaction::id))
.remove_commitment_txs(commitment_txs.iter().map(CommitmentTransaction::id))
.await;

// stage 1: insert commitment transactions into database
self.irys_db.update_eyre(|tx| {
for commitment_tx in commitments.values() {
for commitment_tx in commitment_txs {
// Insert the commitment transaction in to the db, perform migration
insert_commitment_tx(tx, commitment_tx)?;
}
Ok(())
})?;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick | 🔵 Trivial

Mempool removal precedes the DB commit — pre-existing risk, not introduced here.

If insert_commitment_tx fails (line 855 propagates via ?), the commitment transactions have already been removed from the mempool (line 847–849) but are not yet persisted to the database. The transactions remain available via event.transactions (and the block tree), so a retry of the migration event should recover them. Just flagging for awareness — this ordering existed before and is not new to this PR.

🤖 Prompt for AI Agents
In `@crates/actors/src/mempool_service/lifecycle.rs` around lines 847 - 858, The
current flow removes commitment transactions from mempool via
mempool_state.remove_commitment_txs(...) before persisting them with
irys_db.update_eyre(...) and insert_commitment_tx(...), which risks losing them
if the DB commit fails; change the ordering so you first persist all
commitment_txs inside irys_db.update_eyre (calling insert_commitment_tx for
each) and only after that succeeds call
mempool_state.remove_commitment_txs(...), or alternatively wrap the DB commit in
a try and on failure re-insert the removed txs via mempool_state.add or a
rollback helper; update references in this block (remove_commitment_txs,
insert_commitment_tx, irys_db.update_eyre, mempool_state) accordingly so
removals happen only after a successful DB persist.

Comment thread crates/actors/src/mempool_service/lifecycle.rs Outdated
Comment thread crates/chain/tests/api/hardfork_tests.rs
Comment thread crates/chain/tests/api/hardfork_tests.rs

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
crates/actors/src/block_tree_service.rs (1)

261-284: 🧹 Nitpick | 🔵 Trivial

Minor: all_txs Vec and all_txs_map HashMap duplicate the same data.

Both structures are built from the same submit_txs and publish_txs. The comment on line 277 acknowledges this as a TODO. No action needed now, but this is a candidate for a future clean-up when block_index is migrated to use HashMap.

🤖 Fix all issues with AI agents
In `@crates/actors/src/block_tree_service.rs`:
- Around line 452-461: The code currently uses unwrap_or_default() when reading
transactions from cache.blocks for each block_to_migrate, which silently returns
an empty BlockTransactions on a cache miss; replace this silent fallback with a
loud failure: change the lookup to unwrap the Option with expect (or otherwise
return/propagate an Err) so that a missing cache entry for
block_to_migrate.block_hash fails immediately with a clear message including the
block_hash and context (e.g., in the block migration function where
self.cache.read(), cache.blocks.get(&block_to_migrate.block_hash) and
.map(|meta| meta.transactions.clone()) are used).

In `@crates/domain/src/models/block_tree.rs`:
- Around line 2583-2616: The helper mock_transactions_for_block duplicates
integer-to-enum mapping for data ledgers; replace the manual match with
DataLedger::try_from(data_ledger.ledger_id) (the same approach used in
add_common) and skip entries on Err, so ledger conversion is consistent with
production code and avoids hardcoded IDs; update use sites in
mock_transactions_for_block to check the Result and only insert when Ok(ledger).
- Around line 1482-1510: The load_data_transactions function currently drops
missing transactions returned as None from tx_header_by_txid, which can hide DB
corruption for blocks inserted into the cache (start block) without add_common
validation; update load_data_transactions to detect when tx_header_by_txid
returns None for any tx_id and either (a) return an Err/eyre::Report describing
the missing tx and include the tx_id and DataLedger, or (b) at minimum log a
clear warning with the tx_id and ledger and then return an Err so callers
(including the start block insertion path) fail-fast; modify any callers
expecting Ok(HashMap...) accordingly and ensure references to
load_data_transactions, tx_header_by_txid, and the start-block insertion code
path are updated to handle the error.

Comment thread crates/actors/src/block_tree_service.rs
Comment thread crates/domain/src/models/block_tree.rs
Comment thread crates/domain/src/models/block_tree.rs

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
crates/domain/src/models/block_tree.rs (1)

1457-1479: ⚠️ Potential issue | 🟠 Major

Inconsistent error handling: load_commitment_transactions silently drops missing txs while load_data_transactions fails.

load_data_transactions (line 1498–1507) returns an Err when a header-referenced tx is absent from the DB, but load_commitment_transactions (line 1470–1474) silently skips it. For the start block (lines 275, 283–296), commitment transactions are loaded here and inserted directly into the cache without going through add_common validation, so a missing commitment tx would go undetected.

Consider aligning the two functions — either return an error or at least log a warning.

🐛 Proposed fix
     for tx_id in &commitment_tx_ids {
         if let Some(header) =
             commitment_tx_by_txid(&db_tx, tx_id).expect("to retrieve tx header from db")
         {
             txs.push(header);
+        } else {
+            return Err(eyre::eyre!(
+                "Missing commitment transaction in DB: tx_id={}, \
+                 This may indicate DB corruption.",
+                tx_id
+            ));
         }
     }
crates/actors/src/block_tree_service.rs (1)

470-483: 🧹 Nitpick | 🔵 Trivial

Avoid cloning BlockTransactions by wrapping in Arc earlier.

transactions.clone() on line 472 duplicates the entire BlockTransactions (which contains Vecs of transaction headers). Since both the BlockMigratedEvent and send_block_migration_message only need a shared reference, you can create the Arc once and reuse it.

♻️ Proposed fix
+            let arc_transactions = Arc::new(transactions);
+
             // NOTE: order of events is very important! block migration event
             // writes chunks to db, which is expected by `send_block_migration_message`.
             let block_migrated_event = BlockMigratedEvent {
                 block: Arc::clone(&block_to_migrate),
-                transactions: Arc::new(transactions.clone()),
+                transactions: Arc::clone(&arc_transactions),
             };
             if let Err(e) = self
                 .service_senders
                 .block_migrated_events
                 .send(block_migrated_event)
             {
                 debug!("No reorg subscribers: {:?}", e);
             }
             let block_hash = block_to_migrate.block_hash;
             let block_height = block_to_migrate.height;
-            self.send_block_migration_message(block_to_migrate, &transactions)
+            self.send_block_migration_message(block_to_migrate, &arc_transactions)
                 .await
🤖 Fix all issues with AI agents
In `@crates/actors/src/mempool_service/lifecycle.rs`:
- Around line 870-877: The loop over submit_txs currently swallows
insert_tx_header errors (logging and continuing) which can leave the DB in a
partially-migrated state; change the behavior in the update_eyre closure so any
failure in insert_tx_header aborts the operation and causes the DB transaction
to roll back — e.g., replace the error-logging-only branch in the for header in
&submit_txs loop with logic that propagates the error (use ? or return Err(...))
up from the closure so the transaction does not commit after a failed insert.

Comment on lines +870 to 877
for header in &submit_txs {
if let Err(err) = insert_tx_header(tx, header) {
error!(
"Could not insert transaction header - txid: {} err: {}",
header.id, err
);
}
}

@coderabbitai coderabbitai Bot Feb 6, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Submit tx insertion logs errors but continues — verify this is intentional.

If insert_tx_header fails for a submit tx (line 871), the error is logged but the loop continues. This means a partially-failed migration could leave the DB in an inconsistent state (some submit txs written, others not), yet the overall update_eyre closure returns Ok(()). If any insert fails, the DB transaction will still commit the successful ones.

For submit transactions that are referenced by the block header, a single failure likely indicates a serious issue. Consider propagating the error to abort the DB transaction.

🔧 Proposed fix
             self.irys_db.update_eyre(|tx| {
                 for header in &submit_txs {
-                    if let Err(err) = insert_tx_header(tx, header) {
-                        error!(
-                            "Could not insert transaction header - txid: {} err: {}",
-                            header.id, err
-                        );
-                    }
+                    insert_tx_header(tx, header)?;
                 }
                 Ok(())
             })?;
🤖 Prompt for AI Agents
In `@crates/actors/src/mempool_service/lifecycle.rs` around lines 870 - 877, The
loop over submit_txs currently swallows insert_tx_header errors (logging and
continuing) which can leave the DB in a partially-migrated state; change the
behavior in the update_eyre closure so any failure in insert_tx_header aborts
the operation and causes the DB transaction to roll back — e.g., replace the
error-logging-only branch in the for header in &submit_txs loop with logic that
propagates the error (use ? or return Err(...)) up from the closure so the
transaction does not commit after a failed insert.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

^, this seems like it should be a hard error

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@JesseTheRobot Agreed! Submit transactions are an integral part of the migrated block's state, so if any insertion fails, the entire migration should abort to maintain consistency. This aligns with the pattern elsewhere in the codebase where critical invariant violations cause the operation to fail rather than partially succeed.

The proposed fix of replacing the error-logging branch with insert_tx_header(tx, header)?; will ensure the database transaction rolls back on any failure, preventing partial migrations.

Would you like me to implement this change (along with the same fix for publish txs a few lines below), or will you handle it?


🧠 Learnings used
Learnt from: DanMacDonald
Repo: Irys-xyz/irys PR: 1104
File: crates/actors/src/mempool_service/data_txs.rs:31-37
Timestamp: 2026-01-24T02:18:04.459Z
Learning: In `crates/actors/src/mempool_service/data_txs.rs`, the precheck for DataLedger intentionally rejects OneYear and ThirtyDay ledgers during the staged rollout. Downstream match arms for these ledgers are placeholders for future implementation and should not be flagged as unreachable during this development phase.

Learnt from: glottologist
Repo: Irys-xyz/irys PR: 1090
File: crates/actors/src/supply_state_calculator.rs:121-130
Timestamp: 2026-01-20T13:27:23.880Z
Learning: In the supply state backfill logic (crates/actors/src/supply_state_calculator.rs), if the backfill process encounters failures such as batch count mismatches, the code should panic rather than return an error, as these represent critical invariant violations.

// Check commitment transactions
let expected_commitment_ids: HashSet<H256> =
block.get_commitment_ledger_tx_ids().into_iter().collect();
let actual_commitment_ids: HashSet<H256> = transactions

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

iirc the we have almost identical logic as part of the block body validation - it might make sense to unify the logic

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (3)
crates/actors/src/block_tree_service.rs (2)

258-280: ⚠️ Potential issue | 🟡 Minor

Stale doc comment references mempool/database access.

Line 264 still mentions "if any mempool/database access fails", but the method no longer performs mempool lookups. Minor doc inconsistency.

📝 Suggested doc fix
     /// Errors
-    /// Returns an error if the block header cannot be fetched or if any mempool/database access fails.
+    /// Returns an error if the block migration or chunk migration message fails to send.

475-488: 🧹 Nitpick | 🔵 Trivial

Consider wrapping in Arc before cloning to avoid a redundant deep copy.

transactions is cloned on line 477 into the Arc for the event, then borrowed on line 488. You could wrap it in Arc once and reuse it for both, avoiding one deep clone.

♻️ Proposed refactor
+            let transactions = Arc::new(transactions);
+
             let block_migrated_event = BlockMigratedEvent {
                 block: Arc::clone(&block_to_migrate),
-                transactions: Arc::new(transactions.clone()),
+                transactions: Arc::clone(&transactions),
             };
             if let Err(e) = self
                 .service_senders
@@ -486,7 +488,7 @@
             let block_hash = block_to_migrate.block_hash;
             let block_height = block_to_migrate.height;
-            self.send_block_migration_message(block_to_migrate, &transactions)
+            self.send_block_migration_message(block_to_migrate, &transactions)
                 .await

This requires send_block_migration_message to accept &BlockTransactions (which it already does — the Arc auto-derefs).

crates/chain/tests/validation/mod.rs (1)

60-79: 🧹 Nitpick | 🔵 Trivial

Verify that empty BlockTransactions is intentional for this path.

send_block_to_block_validation sends directly to the validation service with empty BlockTransactions. This is used only by heavy_block_validation_discards_a_block_if_its_too_old (line 1161), where the goal is testing cancellation — not transaction validation. If validation ever starts checking transactions in this path, this would silently break.

A brief comment explaining the intentional empty transactions would help future readers.

🤖 Fix all issues with AI agents
In `@crates/chain/tests/api/hardfork_tests.rs`:
- Around line 839-845: Add a brief clarifying comment around the
stop/sleep/activation sequence: explain that the sleep after calling
genesis_node.stop().await is to ensure the node has fully stopped before
sampling activation_timestamp (the variable activation_timestamp = now_secs()),
and note that the test assumes blocks mined by mine_block may have timestamps
equal to activation_timestamp and that Aurora activation treats timestamps >=
activation_timestamp as active; optionally mention increasing the sleep if you
want a larger gap.
- Around line 866-893: The V1 rejection check currently reuses the same signer
that just posted a V2 commitment (see v2_signer_idx, signers, create_stake_tx,
TxVersion::V2/V1 inside the epoch loop), which is correct but unclear; either
(a) reserve a dedicated "rejection probe" signer from the signers array and use
that signer when creating the V1 probe tx so intent is explicit, or (b) add a
short clarifying comment above the V1 probe explaining that reusing the same
signer is intentional because the rejection happens at mempool validation;
update references to v2_signer_idx/signers accordingly to avoid off-by-one
signing mix-ups.
- Around line 1065-1072: The manual reconstruction of IrysNodeTest is
unnecessary—replace the explicit IrysNodeTest { node_ctx: (), cfg:
stopped_peer.cfg.clone(), temp_dir: stopped_peer.temp_dir, name:
stopped_peer.name }.start().await with calling start() directly on the
stopped_peer returned by stop(); in other words, use let peer_node =
stopped_peer.start().await; so you rely on the start() method on the
stopped_peer (IrysNodeTest<()>) rather than rebuilding the struct.

Comment on lines +839 to +845
// Step 4: Stop Both Nodes, Activate Aurora, Restart
let mut stopped_genesis = genesis_node.stop().await;

tokio::time::sleep(Duration::from_secs(1)).await;

// Set activation in the past so all new blocks are post-activation
let activation_timestamp = now_secs();

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick | 🔵 Trivial

Verify the 1-second sleep is sufficient for the activation timestamp to be "in the past".

Line 842 sleeps for 1 second, then line 845 captures now_secs() as the activation timestamp. This means activation_timestamp equals the current time after the sleep. New blocks mined after this point will have timestamps ≥ activation_timestamp, so they should be post-activation. However, if mine_block produces a block with a timestamp exactly equal to activation_timestamp, the Aurora activation check needs to treat >= as active. This is likely the case (the test passes), but worth a brief comment clarifying the intent of the sleep — it appears to ensure genesis has fully stopped before capturing the timestamp rather than creating a gap.

🤖 Prompt for AI Agents
In `@crates/chain/tests/api/hardfork_tests.rs` around lines 839 - 845, Add a brief
clarifying comment around the stop/sleep/activation sequence: explain that the
sleep after calling genesis_node.stop().await is to ensure the node has fully
stopped before sampling activation_timestamp (the variable activation_timestamp
= now_secs()), and note that the test assumes blocks mined by mine_block may
have timestamps equal to activation_timestamp and that Aurora activation treats
timestamps >= activation_timestamp as active; optionally mention increasing the
sleep if you want a larger gap.

Comment on lines +866 to +893
let mut v2_signer_idx = NUM_EPOCHS_BEFORE_ACTIVATION * NUM_BLOCKS_IN_EPOCH;
for epoch in 0..NUM_EPOCHS_AFTER_ACTIVATION {
for block_in_epoch in 0..NUM_BLOCKS_IN_EPOCH {
let anchor = genesis_node.get_anchor().await?;
let signer = &signers[v2_signer_idx];

// Post V2 commitment (V1 would be rejected now)
let v2_tx = create_stake_tx(&genesis_node, signer, TxVersion::V2).await;
genesis_node.post_commitment_tx(&v2_tx).await?;

// Verify V1 is rejected after activation (only check once per epoch)
if block_in_epoch == 0 {
let v1_tx = create_stake_tx(&genesis_node, signer, TxVersion::V1).await;
assert!(
genesis_node.post_commitment_tx(&v1_tx).await.is_err(),
"V1 should be rejected after Aurora activation"
);
}

// Post data tx
let data = format!("post-aurora-epoch{}-block{}", epoch, block_in_epoch);
genesis_node
.post_data_tx(anchor, data.into_bytes(), signer)
.await;

genesis_node.mine_block().await?;
v2_signer_idx += 1;
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick | 🔵 Trivial

V1 rejection check reuses the same signer that just posted a V2 tx — this is fine but could be clearer.

At line 878, create_stake_tx(&genesis_node, signer, TxVersion::V1) uses the same signer that already posted a V2 commitment in the same loop iteration (line 873). Since the V1 tx is expected to be rejected at the mempool (never reaching state), this works. However, a dedicated "rejection probe" signer (or a comment) would make the intent more obvious and avoid any future confusion if mempool validation ever considers per-signer state before version checks.

🤖 Prompt for AI Agents
In `@crates/chain/tests/api/hardfork_tests.rs` around lines 866 - 893, The V1
rejection check currently reuses the same signer that just posted a V2
commitment (see v2_signer_idx, signers, create_stake_tx, TxVersion::V2/V1 inside
the epoch loop), which is correct but unclear; either (a) reserve a dedicated
"rejection probe" signer from the signers array and use that signer when
creating the V1 probe tx so intent is explicit, or (b) add a short clarifying
comment above the V1 probe explaining that reusing the same signer is
intentional because the rejection happens at mempool validation; update
references to v2_signer_idx/signers accordingly to avoid off-by-one signing
mix-ups.

Comment on lines +1065 to +1072
let peer_node = IrysNodeTest {
node_ctx: (),
cfg: stopped_peer.cfg.clone(),
temp_dir: stopped_peer.temp_dir,
name: stopped_peer.name,
}
.start()
.await;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick | 🔵 Trivial

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# First, let's find the stop() method implementation
rg -n 'fn stop\(self\)' crates/chain/tests/api/hardfork_tests.rs -A 15 | head -50

# Also check IrysNodeTest struct definition
rg -n 'struct IrysNodeTest' crates/chain/tests/api/ -A 15

Repository: Irys-xyz/irys

Length of output: 39


🏁 Script executed:

#!/bin/bash
# Let's read the beginning of the test file to see imports and structure
head -100 crates/chain/tests/api/hardfork_tests.rs | cat -n

# Search for IrysNodeTest definition across the entire codebase
fd . crates/chain/tests/ -name '*.rs' | head -20

Repository: Irys-xyz/irys

Length of output: 4039


🏁 Script executed:

#!/bin/bash
# Find files that might contain IrysNodeTest definition
find crates/chain/tests -name '*.rs' -type f

# Search for IrysNodeTest definition
rg -n 'struct IrysNodeTest' crates/chain/tests/ -A 10

# Search for stop() method definition
rg -n 'fn stop\(' crates/chain/tests/ -B 2 -A 10

Repository: Irys-xyz/irys

Length of output: 6442


🏁 Script executed:

#!/bin/bash
# Check for start() method implementations on IrysNodeTest
rg -n 'pub.*fn start\(' crates/chain/tests/utils.rs -B 2 -A 15 | head -100

# Check the exact code at lines 965 and 1065-1072 in the test file
sed -n '960,970p' crates/chain/tests/api/hardfork_tests.rs | cat -n
sed -n '1060,1075p' crates/chain/tests/api/hardfork_tests.rs | cat -n

Repository: Irys-xyz/irys

Length of output: 1736


Remove manual construction — call .start() directly on stopped peer for consistency.

In Step 6 (line 965), the peer is restarted via stopped_peer.start(). The stop() method already returns IrysNodeTest<()>, which has a .start() method available. The manual construction of IrysNodeTest { node_ctx: (), cfg: stopped_peer.cfg.clone(), ... } at lines 1065-1072 is unnecessary and inconsistent with the earlier usage. Simplify to: let peer_node = stopped_peer.start().await;

🤖 Prompt for AI Agents
In `@crates/chain/tests/api/hardfork_tests.rs` around lines 1065 - 1072, The
manual reconstruction of IrysNodeTest is unnecessary—replace the explicit
IrysNodeTest { node_ctx: (), cfg: stopped_peer.cfg.clone(), temp_dir:
stopped_peer.temp_dir, name: stopped_peer.name }.start().await with calling
start() directly on the stopped_peer returned by stop(); in other words, use let
peer_node = stopped_peer.start().await; so you rely on the start() method on the
stopped_peer (IrysNodeTest<()>) rather than rebuilding the struct.

@JesseTheRobot JesseTheRobot left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

@roberts-pumpurs roberts-pumpurs merged commit 52b832c into master Feb 10, 2026
18 checks passed
@roberts-pumpurs roberts-pumpurs deleted the rob/commitment-tx branch February 10, 2026 17:18
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