Skip to content

fix: select_transactions_records edge case#2285

Open
sergerad wants to merge 2 commits into
nextfrom
sergerad-selext-tx-records-page
Open

fix: select_transactions_records edge case#2285
sergerad wants to merge 2 commits into
nextfrom
sergerad-selext-tx-records-page

Conversation

@sergerad

Copy link
Copy Markdown
Collaborator

Summary

select_transactions_records paginates by payload size, but it inferred truncation from total_size >= cap, which missed the common case where a transaction fails to fit while the running total is still below the cap — making a partial page look complete.

The fix tracks an explicit "transaction didn't fit" signal so a truncated page reports the last complete block as the cursor, and errors when a single block exceeds the payload cap.

Changelog

changelog = "none"
reason    = "Internal change only."

@sergerad sergerad changed the title Report last complete block as cursor fix: select_transactions_records edge case Jun 25, 2026
Comment on lines +75 to +76
"transaction records for block {block_num} exceed the maximum response payload size and \
cannot be paginated"

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 should help the caller understand what they can do in this situation.

Suggested change
"transaction records for block {block_num} exceed the maximum response payload size and \
cannot be paginated"
"transactions for block {block_num} would exceed maximum response size, \
use a stricter filter to reduce the number of transactions returned"

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.

I'm finding this function quite confusing. The fix works - I think - but given the function layout I'm a bit worried there's something I'm overlooking.

However I think what I want to suggest isn't possible because the table is without rowid.

Maybe @kkovaacs can offer some insight.

What I want to do is roughly:

// Find the first applicable row.
let mut row_start = db.select(
    "SELECT rowid FROM transactions WHERE <account+block start filter>"
);

// The last completed full block page index ito tx records.
let mut iblock = 0usize;
let mut records = Vec::new();
let mut last_block = BlockNumber::MAX;

loop {
    let chunk = db.select(
        "SELECT tx, rowid, block FROM transactions WHERE <filter> AND rowid >= row_start LIMIT 1000 ORDER BY rowid"
    );

    for row in chunk {
        if records.size() + row > capacity {
            break;
        }

        row_start = row.rowid;
        if row.block != last_block {
            last_block = row.block;
            iblock = records.len();
        }
        records.push(row.tx);
    }
}

// Truncate to the last full block.
records = records[..iblock];
Ok(records);

Comment on lines +228 to +231
// Set to the block number of the first transaction that did not fit within the payload cap.
// This is the explicit "we truncated" signal; the accumulated byte total cannot be used as a
// proxy, since a transaction can fail to fit while `total_size` is still below the cap.
let mut truncated_at_block: Option<i64> = None;

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.

I would suggest an alternate approach, like I outlined in https://github.com/0xMiden/node/pull/2285/changes#r3474272591.

Comment on lines +294 to +297
let complete_transactions: Vec<_> = all_transactions
.into_iter()
.take_while(|row| row.block_num < truncation_block)
.collect();

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.

If you track the last index in addition to the last completed block, then you can instead all_transactions.truncate(ilast_completed).

"transaction records for block {block_num} exceed the maximum response payload size and \
cannot be paginated"
)]
TransactionPageExceedsPayloadLimit { block_num: BlockNumber },

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.

This new error case is mapped to a gRPC internal error, while in reality it should probably be Status::resource_exhausted() or something like that.

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