Skip to content

FCMP++: cleaner/safer get_outs_by_last_locked_block - #460

Open
j-berman wants to merge 1 commit into
seraphis-migration:fcmp++-stagefrom
j-berman:fix-transp-amt-handling
Open

FCMP++: cleaner/safer get_outs_by_last_locked_block#460
j-berman wants to merge 1 commit into
seraphis-migration:fcmp++-stagefrom
j-berman:fix-transp-amt-handling

Conversation

@j-berman

Copy link
Copy Markdown
Collaborator

While reviewing monero-project#11088, @selsta pointed out that this std::move(bl) here comes before handle_fcmp_tree that reads tx_refs here. Those tx_refs also include a ref to the bl.miner_tx, so if the bl actually gets moved before reading the tx refs, we'd have UB.

Technically the bl is const, so the std::move is actually just a copy.

But it seems sketchy enough that I think some hardening here is in order.

I decided not to expect external callers to pass tx refs around (and manage them correctly), and instead just pass in the txs directly by reference. I figure this approach is simpler to reason through and avoid mistakes.

@j-berman
j-berman force-pushed the fix-transp-amt-handling branch from feb33db to 463a9db Compare August 21, 2026 01:15
uint64_t long_term_block_weight = core.get_blockchain_storage().get_next_long_term_block_weight(block_weight);
const uint64_t new_height = core.get_blockchain_storage().get_db().add_block(std::make_pair(b, block_to_blob(b)), block_weight, long_term_block_weight, cumulative_difficulty, coins_generated, txs, transparent_amount_commitments);
cryptonote::handle_fcmp_tree(&core.get_blockchain_storage().get_db(), new_height - 1, first_unified_id, tx_refs, transparent_amount_commitments);
CHECK_AND_ASSERT_THROW_MES(h == (new_height-1), "Unexpected height");

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

This throw would leave the db in an invalid state, with new block added but fcmp unhandled. Maybe debug_assert?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

The db would be in an invalid state if it doesn't throw and proceeds

@UkoeHB UkoeHB Aug 21, 2026

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Could make the whole sequence atomic but that may not be worth the effort. If h == (new_height-1) is an invariant of add_block then debug_assert should be adequate. And BlockchainDB::add_block should explicitly call out that invariant in the header comment.

Like if the comment said "Returns the previous height + 1." then the assert wouldn't be necessary.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

On the other hand handle_block_to_main_chain has the same validity properties, so I'll retract my complaint.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

FWIW handle_block_to_main_chain is guaranteed always atomic because it's always called in the context of a batch db txn (prepare_handle starts the db txn, cleanup_handle commits it)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Would that be appropriate to include here? I'm not well versed in db stuff.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

It does it here when use_batch is true. This is the blockchain import tool which I expect is a pretty rarely used tool, so I don't think worth spending a whole bunch of time on it

Comment on lines +2033 to +2035
std::vector<std::reference_wrapper<const transaction>> tx_refs;
tx_refs.reserve(1 + tx_pairs.size());
tx_refs.push_back(std::cref(miner_tx));

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.

Since you set up the API to do so, it would be nice to collect without this intermediate allocate + copy.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Not sure I see what you mean, it's using reference wrappers to avoid allocating and copying the actual transactions, while allowing the distinct vectors holding transactions

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.

Yeah, I meant skipping allocating vectors of references

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

I see it as a tradeoff of duplicating a good bit of internal logic versus allocating vectors of references. The latter seemed the desirable tradeoff to me and is an insignificant perf hit.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

No impl Iterator here sadly.

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.

3 participants