Skip to content

Feature/api server mempool txs#2029

Open
OBorce wants to merge 7 commits intomasterfrom
feature/api-server-mempool-txs
Open

Feature/api server mempool txs#2029
OBorce wants to merge 7 commits intomasterfrom
feature/api-server-mempool-txs

Conversation

@OBorce
Copy link
Copy Markdown
Contributor

@OBorce OBorce commented Mar 24, 2026

API Server now scans the mempool after finishing syncing to the latest block.
New address and transaction endpoints for things from the mempool.

New transaction dependency sorter, first builds a graph of dependencies from the mempool transactions and then sorts them for inclusion.

@OBorce OBorce changed the base branch from master to fix/api-server-balance-overflow March 24, 2026 09:39
@OBorce OBorce force-pushed the feature/api-server-mempool-txs branch 2 times, most recently from 554e4e2 to b00d078 Compare March 26, 2026 08:50
@OBorce OBorce force-pushed the fix/api-server-balance-overflow branch from e19ea86 to 184291a Compare March 26, 2026 08:51
@OBorce OBorce marked this pull request as draft March 27, 2026 09:49
@OBorce OBorce force-pushed the feature/api-server-mempool-txs branch 2 times, most recently from 8a9d0ce to 6496fe8 Compare April 1, 2026 08:47
@OBorce OBorce changed the base branch from fix/api-server-balance-overflow to master April 1, 2026 08:49
@OBorce OBorce force-pushed the feature/api-server-mempool-txs branch 2 times, most recently from b916d76 to 1f9d564 Compare April 1, 2026 08:57
@OBorce OBorce marked this pull request as ready for review April 1, 2026 08:57
Copy link
Copy Markdown
Contributor

@ImplOfAnImpl ImplOfAnImpl left a comment

Choose a reason for hiding this comment

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

CURRENT_STORAGE_VERSION wasn't updated - is this intended?

Comment thread CHANGELOG.md Outdated
Comment thread api-server/api-server-common/src/storage/impls/postgres/queries.rs Outdated
Comment thread api-server/scanner-lib/src/blockchain_state/mod.rs Outdated
&self,
tx_id: Id<Transaction>,
) -> Result<Option<SignedTransaction>, Self::Error>;
async fn mempool_get_transactions(&self) -> Result<Vec<SignedTransaction>, Self::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.

Btw, the doc comment for MempoolRpc::get_all_transactions still says "This function is mostly used for testing purposes", which is no longer true.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Removed that comment

Comment thread api-server/api-server-common/src/storage/storage_api/mod.rs Outdated
)
.await;
} else {
increase_mempool_locked_address_balance(
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.

I see that you increase locked balances in the mempool but never decrease them.
Mempool has some timelock tolerance defined by constants FUTURE_TIMELOCK_TOLERANCE/FUTURE_TIMELOCK_TOLERANCE_BLOCKS. So it should be possible to have e.g. a tx with LockThenTransfer output for 2 blocks and then a tx that consumes that output both in the mempool at the same time.

Probably you should decrease locked balances if a LockThenTransfer utxo is spent by a mempool tx.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

I added a test with a tx spending a locked UTXO with 1..FUTURE_TIMELOCK_TOLERANCE_BLOCKS.

Comment thread api-server/scanner-lib/src/blockchain_state/mod.rs Outdated
Comment thread api-server/scanner-lib/src/blockchain_state/mod.rs Outdated
Comment thread api-server/scanner-lib/src/blockchain_state/mod.rs
Comment thread api-server/web-server/src/api/v2.rs Outdated
@OBorce OBorce force-pushed the feature/api-server-mempool-txs branch 4 times, most recently from 71d7a6b to 1c9739a Compare April 20, 2026 07:54
@OBorce OBorce force-pushed the feature/api-server-mempool-txs branch from 6185680 to f6d5c31 Compare April 20, 2026 08:14
@OBorce OBorce changed the base branch from master to update_trezor_ref_and_docs April 20, 2026 08:14
@OBorce OBorce force-pushed the feature/api-server-mempool-txs branch 4 times, most recently from fa3a655 to 50cf265 Compare April 20, 2026 10:23
Base automatically changed from update_trezor_ref_and_docs to master April 20, 2026 11:57
@OBorce OBorce force-pushed the feature/api-server-mempool-txs branch from 50cf265 to 317964b Compare April 20, 2026 16:12
Copy link
Copy Markdown
Contributor

@ImplOfAnImpl ImplOfAnImpl left a comment

Choose a reason for hiding this comment

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

  1. The most complex changes are in the scanner-lib, but there are zero new tests in api-server/scanner-lib/src/sync/tests. I don't think that the tests at the stack-test-suite level are enough: the current tests are limited and in general tests at that level are limited in what they can check compared to the ones in scanner-lib. Additionally, there is functionality that is not currently exposed via the API, so it's basically not tested.
    Plz add tests at the scanner-lib level too. Plz make sure they're extensive enough.

  2. Regarding the pagination of the mempool transactions in the corresponding endpoint - obviously the current solution just wont work. I guess we should get rid of the pagination entirely in this case and return the entire contents in one call, as they do in bitcoin in getrawmempool (by default, they only return tx ids, so the response is not that large even for a full mempool). Also, let's maybe introduce something similar to their mempool_sequence counter, which would be incremented every time the mempool contents change, so that the caller can use that to check that the mempool has changed.

for (out_index, out) in tx.transaction().outputs().iter().enumerate() {
match out {
TxOutput::CreateOrder(order_data) => {
let order_id = make_order_id(inputs).expect("Valid TX from mempool");
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.

Plz don't expect. Create an error type specifically for dependency_graph and return it here.

P.S. also, this module is not mempool-specific, so mempool should not be mentioned here.

assert_eq!(dependency_graph[1].dependencies, vec![txa_id]);
}

// test txs not dependednt on UTXO input/outputs but on token creation/command
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.

typo - dependednt

let chain_config = create_regtest();
let block_height = BlockHeight::new(0);

// A creates a new token, B uses a comand on it
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.

typo - comand

.build();
let txb_id = txb.transaction().get_id();

let transactions = vec![txa, txb];
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.

Here and in other tests: shuffle the tx vector before building the graph?


// Order transactions by dependency between each other.
// Returns a Vec of transactions starting from the top-most parent transaction
// which doesn't depend on any other transaction fallowing it, and ending with the leafs.
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.

typo - fallowing

| OrderAccountCommand::FreezeOrder(order_id)
| OrderAccountCommand::ConcludeOrder(order_id) => {
let order = db_tx
.get_order(*order_id)
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.

get_mempool_order_with_fallback?

Same above, where legacy orders are handled.

}
}
}
_ => {}
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.

Using wildcard in matches is dangerous - when the enum changes, you won't notice that the code needs updating.

But here you actually omitted orders v1 entirely.

Plz write a test in scanner-lib for this.

}

// Save new UTXO as unspent in mempool
let utxo = Utxo::new(output.clone(), token_decimals_opt, None);
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.

Btw, you call set_mempool_utxo/set_mempool_locked_utxo even for non-spendable outputs, which are not really utxos. As I can see, in update_tables_from_transaction_outputs only spendable outputs are recorded as utxos, so I guess here we should do the same?

Comment thread api-server/CHANGELOG.md
Comment on lines 9 to +19
## [1.3.0] - 2026-04-09

### Added
- New endpoint was added: `/v2/transaction/{id}/output/{idx}`.
- New endpoint was added: `/v2/transaction/{id}/output/{idx}`, including an optional `with_mempool` parameter that controls the display of mempool spent status.
- New endpoint was added: `/v2/token/{id}/transactions` will return all transactions related to a token.\
Pagination works like the new absolute mode in `/v2/transaction`, using `offset` and `items`.
- New endpoints for addresses and transactions from the mempool:
- `/v2/mempool/transaction`
- `/v2/mempool/transaction/:id`
- `/v2/mempool/address/:address`
- `/v2/mempool/address/:address/all-utxos`
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.

Wrong place. Add the changes to the [Unreleased] section instead.

Comment on lines +69 to 96
let (best_block_height, best_block_id, _) = local_state
.best_block()
.await
.map_err(|e| SyncError::BestBlockRetrievalError(e.to_string()))?;

if chain_info.best_block_id == best_block_id {
logging::log::info!("Fully synced! Scanning mempool...");

// Fetch the current complete mempool
let all_mempool_txs = rpc_client
.mempool_get_transactions()
.await
.map_err(|e| SyncError::RemoteNode(e.to_string()))?;

let ordered_txs =
order_transactions_by_dependency(all_mempool_txs, chain_config, best_block_height)
.map_err(|e| SyncError::LocalNode(e.to_string()))?;

for tx in ordered_txs {
local_state
.add_mempool_tx(&tx)
.await
.map_err(|e| SyncError::LocalNode(e.to_string()))?;
}

return 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.

  1. As I can see, the caller function doesn't try to sleep between iterations. Previously most invocations of sync_once were cheap - check the block id, see that it's the best one, exit. Now you pull the entire mempool, which in theory may be 300 Mb. Probably the caller function should sleep for some time between iterations, e.g. for 1 sec?

  2. Transactions can be evicted from mempool if it reaches the max size. Not a problem for now of course, but it's better to implement things properly from the beginning. So, scanner-lib should check for transactions that are in the mempool-related tables, but no longer in the current mempool, and update the db accordingly, marking the corresponding utxos as unspent etc.

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