Skip to content

feat: add LMDB unit tests for BlockchainBackend read methods#7741

Open
0xPepeSilvia wants to merge 1 commit intotari-project:developmentfrom
0xPepeSilvia:feat/lmdb-unit-tests-7715
Open

feat: add LMDB unit tests for BlockchainBackend read methods#7741
0xPepeSilvia wants to merge 1 commit intotari-project:developmentfrom
0xPepeSilvia:feat/lmdb-unit-tests-7715

Conversation

@0xPepeSilvia
Copy link
Copy Markdown

Summary

  • Adds 22 unit tests that exercise LMDB BlockchainBackend read methods against a realistic chain with a fork and reorg
  • Test chain: genesis + 10 main blocks + 10-block fork from block 5 (triggers reorg, 5 reorged blocks)
  • Chain data serializable to JSON for reproducibility
  • Tests cover: fetch_outputs_in_block, fetch_output, fetch_unspent_output_hash_by_commitment, fetch_inputs_in_block, fetch_kernels_in_block, fetch_kernel_by_excess_sig, fetch_header_containing_kernel_mmr, fetch_outputs_in_block_with_spend_state, fetch_mined_info_by_payref

Chain topology

Genesis -> B1..B5 -> B6..B10 (original main chain)
                 \-> F6'..F15' (fork, triggers reorg)

After reorg: Genesis + B1..B5 + F6'..F15' (canonical)
Reorged: B6..B10

Test plan

  • All 22 tests pass: cargo test --package tari_core lmdb_unit_tests
  • Chain construction correctly triggers reorg (verified in write_tests)
  • Read tests verify correct data for shared blocks, fork blocks, and edge cases

Fixes #7715

🤖 Generated with Claude Code

@0xPepeSilvia 0xPepeSilvia requested a review from a team as a code owner April 9, 2026 21:10
@github-actions
Copy link
Copy Markdown

github-actions bot commented Apr 9, 2026

⚠️ This PR contains unsigned commits. To get your PR merged, please sign those commits (git rebase --exec 'git commit -S --amend --no-edit -n' @{upstream}) and force push them to this branch (git push --force-with-lease).

If you're new to commit signing, there are different ways to set it up:

Sign commits with gpg

Follow the steps below to set up commit signing with gpg:

  1. Generate a GPG key
  2. Add the GPG key to your GitHub account
  3. Configure git to use your GPG key for commit signing
Sign commits with ssh-agent

Follow the steps below to set up commit signing with ssh-agent:

  1. Generate an SSH key and add it to ssh-agent
  2. Add the SSH key to your GitHub account
  3. Configure git to use your SSH key for commit signing
Sign commits with 1Password

You can also sign commits using 1Password, which lets you sign commits with biometrics without the signing key leaving the local 1Password process.

Learn how to use 1Password to sign your commits.

Watch the demo

Copy link
Copy Markdown
Contributor

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

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

Code Review

This pull request introduces a new suite of LMDB unit tests to verify BlockchainDatabase read methods under conditions involving forks and chain reorganizations. The reviewer identified several areas for improvement: the apply_mmr_to_block helper is missing the block_output_mr field, the test suite efficiency could be improved by reducing redundant calls to build_test_chain, the assertion in the commitment lookup test is too weak, and the spend state test should explicitly verify that outputs are marked as unspent.

Comment on lines +131 to +137
block.header.input_mr = mmr_roots.input_mr;
block.header.output_mr = mmr_roots.output_mr;
block.header.output_smt_size = mmr_roots.output_smt_size;
block.header.kernel_mr = mmr_roots.kernel_mr;
block.header.kernel_mmr_size = mmr_roots.kernel_mmr_size;
block.header.validator_node_mr = mmr_roots.validator_node_mr;
block.header.validator_node_size = mmr_roots.validator_node_size;
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.

medium

The apply_mmr_to_block helper function is missing the assignment of block_output_mr from the calculated MMR roots. While the current tests use a mock validator that might not check this field, it should be included to ensure the block header is correctly populated and consistent with the chain state.

Suggested change
block.header.input_mr = mmr_roots.input_mr;
block.header.output_mr = mmr_roots.output_mr;
block.header.output_smt_size = mmr_roots.output_smt_size;
block.header.kernel_mr = mmr_roots.kernel_mr;
block.header.kernel_mmr_size = mmr_roots.kernel_mmr_size;
block.header.validator_node_mr = mmr_roots.validator_node_mr;
block.header.validator_node_size = mmr_roots.validator_node_size;
block.header.input_mr = mmr_roots.input_mr;
block.header.output_mr = mmr_roots.output_mr;
block.header.block_output_mr = mmr_roots.block_output_mr;
block.header.output_smt_size = mmr_roots.output_smt_size;
block.header.kernel_mr = mmr_roots.kernel_mr;
block.header.kernel_mmr_size = mmr_roots.kernel_mmr_size;
block.header.validator_node_mr = mmr_roots.validator_node_mr;
block.header.validator_node_size = mmr_roots.validator_node_size;

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

important

Comment on lines +462 to +472
// Coinbase outputs that haven't been spent should be findable.
// Some outputs may have been spent if they were inputs to later blocks,
// so we only assert that unspent ones return the correct hash.
if let Some(found_hash) = result {
assert_eq!(
found_hash,
output.hash(),
"Commitment lookup returned wrong output hash for block {}",
block.header.height
);
}
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.

medium

The assertion in fetch_unspent_output_hash_by_commitment_finds_canonical_outputs is too weak. Since the test chain construction does not include any transactions that spend these outputs, they should all be present in the UTXO set. Using if let Some(...) allows the test to pass silently if an output is missing. It should explicitly assert that the output is found. Note: In internal functions, using expect is acceptable for conditions that should be guaranteed by the calling code's logic.

Suggested change
// Coinbase outputs that haven't been spent should be findable.
// Some outputs may have been spent if they were inputs to later blocks,
// so we only assert that unspent ones return the correct hash.
if let Some(found_hash) = result {
assert_eq!(
found_hash,
output.hash(),
"Commitment lookup returned wrong output hash for block {}",
block.header.height
);
}
// Coinbase outputs that haven't been spent should be findable.
let found_hash = result.expect(&format!(
"Commitment for output in block {} should be found as unspent",
block.header.height
));
assert_eq!(
found_hash,
output.hash(),
"Commitment lookup returned wrong output hash for block {}",
block.header.height
);
References
  1. In internal functions, using expect is acceptable for conditions that should be guaranteed by the calling code's logic, as a panic indicates a programming error (broken invariant) rather than a recoverable error.

Comment on lines +515 to +520
outputs_with_state.len(),
block.body.outputs().len(),
"Should return all outputs from block at height {}",
block.header.height
);
}
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.

medium

This test verifies the number of outputs returned when no spend header is provided, but it doesn't check the actual spend state. When spend_status_at_header is None, all outputs should be marked as unspent (false). Adding this check would make the test more robust.

Suggested change
outputs_with_state.len(),
block.body.outputs().len(),
"Should return all outputs from block at height {}",
block.header.height
);
}
assert_eq!(
outputs_with_state.len(),
block.body.outputs().len(),
"Should return all outputs from block at height {}",
block.header.height
);
for (output, is_spent) in &outputs_with_state {
assert!(
!is_spent,
"Output {} in block at height {} should be marked as unspent when no spend header is provided",
output.hash(),
block.header.height
);
}

@0xPepeSilvia 0xPepeSilvia force-pushed the feat/lmdb-unit-tests-7715 branch from 7d14630 to 337e113 Compare April 9, 2026 21:18
Create comprehensive unit tests that exercise LMDB read operations against
a realistic blockchain containing a fork and reorg.

Test chain topology:
  Genesis -> B1..B5 -> B6..B10 (original)
                   \-> F6'..F15' (fork, triggers reorg)

After reorg: canonical = Genesis + B1..B5 + F6'..F15'
Reorged blocks: B6..B10

Tests cover:
- Chain construction and reorg verification
- JSON serialization for reproducibility
- fetch_outputs_in_block / fetch_output / fetch_unspent_output_hash_by_commitment
- fetch_inputs_in_block
- fetch_kernels_in_block / fetch_kernel_by_excess_sig
- fetch_header_containing_kernel_mmr
- fetch_outputs_in_block_with_spend_state
- fetch_mined_info_by_payref

22 test functions organized in write_tests and read_tests modules.

Fixes tari-project#7715

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@0xPepeSilvia 0xPepeSilvia force-pushed the feat/lmdb-unit-tests-7715 branch from 337e113 to c4148b9 Compare April 10, 2026 02:29
Copy link
Copy Markdown
Collaborator

@SWvheerden SWvheerden left a comment

Choose a reason for hiding this comment

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

these tests dont actually test anything, we need to test against a static generated json data and lmdb files. And we need to test against the actual lmdb file, not what we think is in them.

}

#[derive(Debug, Serialize, Deserialize)]
struct SerializableBlock {
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

we dont need this, blocks already impl Serialize, Deserialize

Comment on lines +131 to +137
block.header.input_mr = mmr_roots.input_mr;
block.header.output_mr = mmr_roots.output_mr;
block.header.output_smt_size = mmr_roots.output_smt_size;
block.header.kernel_mr = mmr_roots.kernel_mr;
block.header.kernel_mmr_size = mmr_roots.kernel_mmr_size;
block.header.validator_node_mr = mmr_roots.validator_node_mr;
block.header.validator_node_size = mmr_roots.validator_node_size;
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

important

Comment on lines +280 to +306
assert_eq!(chain.canonical_blocks.len(), 16);

// The reorged blocks should be the 5 original blocks that were replaced.
assert_eq!(chain.reorged_blocks.len(), 5);

// Verify heights of canonical blocks.
for (i, block) in chain.canonical_blocks.iter().enumerate() {
assert_eq!(
block.header.height, i as u64,
"Canonical block at index {} should have height {}",
i, i
);
}

// Verify reorged blocks had heights 6..10.
for (i, block) in chain.reorged_blocks.iter().enumerate() {
assert_eq!(
block.header.height,
(i + 6) as u64,
"Reorged block at index {} should have height {}",
i,
i + 6
);
}

// Verify the tip header matches the last canonical block.
let tip = chain.db.fetch_tip_header().unwrap();
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

all of these should test agains the alctual lmdb, not the memory of what we think is in the lmdb, this defeats the purpose of having to add lmdb tests

let json = serialize_chain_to_json(&chain);

// Basic sanity: the JSON should be parseable and contain the expected number of blocks.
let snapshot: TestChainSnapshot = serde_json::from_str(&json).expect("Failed to parse chain JSON");
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

this just tests if we can make a json file of the test data structs, this does not test anything.
we need to test the actual lmdb files

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.

Create lmdb unit tests

2 participants