Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
d0c0f23
fix(rest): keep mempool tx prevout lookup atomic
EddieHouston Aug 21, 2026
4209795
add test that demonstrates abort under mempool eviction events
philippem Aug 21, 2026
03ed55c
fix(rest): address review feedback on mempool snapshot atomicity
EddieHouston Aug 24, 2026
8a8ac15
test(rest): verify mempool snapshot blocks eviction
EddieHouston Aug 24, 2026
383e5df
fix(query): recover from a poisoned mempool lock on the write path
EddieHouston Aug 28, 2026
46e37ae
fix(rest): preserve mempool snapshots across confirmation and eviction
EddieHouston Sep 14, 2026
aa2fc45
fix(rest): avoid mempool contention and classify lookup failures
EddieHouston Sep 15, 2026
61cd939
test: share node fixture across unit tests
EddieHouston Sep 16, 2026
2ea0212
test: remove duplicate query method on TestRunner
EddieHouston Sep 28, 2026
6d591e2
fix(db): pin index_unspendables and address_search into DB compatibil…
agoodminute Sep 14, 2026
f5587f8
bound scripthash history scan cost and evaluate utxos_limit against t…
agoodminute Sep 14, 2026
91840bb
fix stats_delta double-counting when a capped scan resumes from check…
agoodminute Sep 14, 2026
9a7f304
add integration tests for utxo/history scan limits
agoodminute Sep 14, 2026
0085986
only cap history scans at height boundaries to avoid stalling forever
agoodminute Sep 14, 2026
8bf81bb
fix mempool scan bound, history pagination truncation
agoodminute Sep 15, 2026
bfb0c94
lock in that the utxos-limit holds until consolidation confirms
agoodminute Sep 18, 2026
a233207
save a checkpoint at the scan limit and reject truncated history pages
agoodminute Sep 22, 2026
f3332f4
Added rpc response limits.
Randy808 Sep 15, 2026
532bd5c
replace checkpoint Merkle proof concurrency cap with a tree cache
agoodminute Sep 14, 2026
6d4ed16
fix checkpoint Merkle cache eviction and reorg races
agoodminute Sep 28, 2026
14a0ed7
Avoid holding the mempool write lock during broadcast RPCs
EddieHouston Sep 24, 2026
da60cec
harden mempool sync and daemon reconnect against transient failures
agoodminute Sep 28, 2026
0231fff
integration: route TooBigHistory through HTTP 400 after mempool-snaps…
agoodminute Sep 30, 2026
9412b5c
Harden scan limits, DB compatibility, mempool sync and Electrum respo…
EddieHouston Sep 30, 2026
b33362f
test: make Electrum response budget recovery check portable
agoodminute Sep 30, 2026
371ca75
fix: index the daemon's actual witness after transaction broadcast
agoodminute Sep 30, 2026
cb54329
fix(mempool): report partial sync as FailedToIndex
agoodminute Oct 1, 2026
80c273f
test(scan_limits): fund the foreign tx explicitly so coin selection c…
agoodminute Oct 1, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 9 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -65,11 +65,19 @@ In addition to electrs's original configuration options, a few new options are a
- `--http-addr <addr:port>` - HTTP server address/port to listen on (default: `127.0.0.1:3000`).
- `--lightmode` - enable light mode (see above)
- `--cors <origins>` - origins allowed to make cross-site request (optional, defaults to none).
- `--address-search` - enables the by-prefix address search index.
- `--address-search` - enables the by-prefix address search index. Best-effort: entries are
written when a block is indexed but are not removed if that block is later reorged out, so
the index may return addresses with no transaction in the current best chain. Run a full
reindex to clear stale entries if this matters for your deployment.
- `--index-unspendables` - enables indexing of provably unspendable outputs.
- `--enable-mining-rest` - enables cached mining-related HTTP endpoints.
- `--utxos-limit <num>` - maximum number of utxos to return per address.
- `--history-scan-limit <num>` - approximate number of history rows scanned per address lookup before failing with a "too popular" error; progress is checkpointed so repeated lookups make headway. Must be at least `1`. Default: `100000`.
- `--utxos-checkpoint-limit <num>` - maximum number of utxos held in a partially scanned utxo set and its saved checkpoint, and of unconfirmed history entries scanned per address. Must be at least `--utxos-limit`. Default: 10 times `--utxos-limit`.
- `--electrum-txs-limit <num>` - maximum number of txs to return per address in the electrum server (does not apply for the http api).
- `--electrum-rpc-write-timeout <seconds>` - maximum time to transmit a complete Electrum reply, including a whole batch and its newline, an error reply, or a subscription notification. The deadline starts at the first write and is not extended by partial progress. Expiry closes the connection and releases its response buffers. Idle subscriptions and command execution are unaffected. Default: `30`, matching the RPC proxy's `CLIENT_WRITE_TIMEOUT_SECS`; `0` disables the bound, which is only accepted with `--electrum-rpc-conn-max-age` set or the global response budget disabled. The `electrum_client_write_timeouts_total` counter records connections closed by this deadline.
- `--electrum-rpc-max-response-num-bytes <num>` - maximum size, in bytes, of a single Electrum solicited reply line (brackets, commas, error objects, and newline included). Overflowing replies return a correlated error (code 1) and the rest of the batch is not executed; the command that overflowed may already have completed. Subscription notifications are exempt. `0` disables the cap. Default: `8388608` (8 MiB) on Bitcoin, `33554432` (32 MiB) on Liquid (sized for a 2016-header window during a dynafed parameter vote).
- `--electrum-rpc-global-response-budget-bytes <num>` - aggregate cap, in bytes, on Electrum solicited-reply buffer memory retained across all connections at any instant, including batch replies held while later batch elements execute. Subscription notifications and queued request lines are not counted. Small replies below a 16 KiB per-connection floor are exempt; above the floor each reply charges the budget in 64 KiB chunks (or a smaller final chunk) and releases on drop. Requests that would exceed the budget are rejected with a server error (code 2) instead of allocating, and the connection stays usable. Must be `>= --electrum-rpc-max-response-num-bytes`, and requires a non-zero `--electrum-rpc-write-timeout` or `--electrum-rpc-conn-max-age`. `0` disables the budget. Default: `67108864` (64 MiB) on Bitcoin, `268435456` (256 MiB) on Liquid.
- `--electrum-banner <text>` - welcome banner text for electrum server.

Additional options with the `liquid` feature:
Expand Down
71 changes: 56 additions & 15 deletions src/bin/electrs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ extern crate log;
extern crate electrs;

use crossbeam_channel::{self as channel};
use error_chain::ChainedError;
use error_chain::{bail, ChainedError};
use std::{env, process, thread};
use std::sync::{Arc, RwLock};
use std::time::Duration;
Expand All @@ -20,7 +20,9 @@ use electrs::{
electrum::RPC as ElectrumRPC,
errors::*,
metrics::Metrics,
new_index::{precache, zmq, ChainQuery, FetchFrom, Indexer, Mempool, Query, Store},
new_index::{
precache, zmq, ChainQuery, FetchFrom, Indexer, Mempool, MempoolSyncStatus, Query, Store,
},
rest,
signal::Waiter,
};
Expand All @@ -35,6 +37,14 @@ use electrs::metrics::MetricOpts;
/// Default salt rotation interval in seconds (24 hours)
const DEFAULT_SALT_ROTATION_INTERVAL_SECS: u64 = 24 * 3600;

/// Maximum number of `FailedToIndex` retries during the initial mempool
/// sync before giving up.
const MAX_INITIAL_MEMPOOL_SYNC_RETRIES: u32 = 100;

/// Maximum number of consecutive `FailedToIndex` main loop cycles before giving up,
/// about an hour at the 5 second loop interval.
const MAX_CONSECUTIVE_MEMPOOL_SYNC_FAILURES: u32 = 720;

fn fetch_from(config: &Config, store: &Store) -> FetchFrom {
let mut jsonrpc_import = config.jsonrpc_import;
if !jsonrpc_import {
Expand Down Expand Up @@ -122,18 +132,32 @@ fn run_server(config: Arc<Config>, salt_rwlock: Arc<RwLock<String>>) -> Result<(
Arc::clone(&config),
)));

// A mempool update is retried whenever the tip moves, so index and try again. An update
// that cannot advance the tip never will here, and no listener is bound yet.
while !Mempool::update(&mempool, &daemon, &tip)? {
let new_tip = indexer.update(&daemon)?;
if new_tip == tip {
warn!(
"index could not advance, starting up with a partial index tip='{}'",
tip
);
break;
let mut mempool_sync_retries = 0;
loop {
match Mempool::update(&mempool, &daemon, &tip)? {
MempoolSyncStatus::Synced => break,
MempoolSyncStatus::TipMoved => {
let new_tip = indexer.update(&daemon)?;
if new_tip == tip {
warn!(
"index could not advance, starting up with a partial index tip='{}'",
tip
);
break;
}
tip = new_tip;
}
MempoolSyncStatus::FailedToIndex => {
mempool_sync_retries += 1;
if mempool_sync_retries >= MAX_INITIAL_MEMPOOL_SYNC_RETRIES {
bail!(
"initial mempool sync failed to index {} times, giving up",
MAX_INITIAL_MEMPOOL_SYNC_RETRIES
);
}
signal.wait(Duration::from_secs(3), false)?;
}
}
tip = new_tip;
}

#[cfg(feature = "liquid")]
Expand Down Expand Up @@ -168,6 +192,7 @@ fn run_server(config: Arc<Config>, salt_rwlock: Arc<RwLock<String>>) -> Result<(
"count of iterations of electrs main loop each 5 seconds or after interrupts",
));

let mut mempool_sync_failures = 0;
loop {
main_loop_count.inc();

Expand All @@ -185,8 +210,24 @@ fn run_server(config: Arc<Config>, salt_rwlock: Arc<RwLock<String>>) -> Result<(
};

// Update mempool
if !Mempool::update(&mempool, &daemon, &tip)? {
warn!("skipped failed mempool update, trying again in 5 seconds");
match Mempool::update(&mempool, &daemon, &tip)? {
MempoolSyncStatus::Synced => mempool_sync_failures = 0,
MempoolSyncStatus::TipMoved => {
warn!("mempool sync aborted: chain tip moved, retrying next cycle");
}
MempoolSyncStatus::FailedToIndex => {
mempool_sync_failures += 1;
if mempool_sync_failures >= MAX_CONSECUTIVE_MEMPOOL_SYNC_FAILURES {
bail!(
"mempool sync failed to index {} consecutive times, giving up",
MAX_CONSECUTIVE_MEMPOOL_SYNC_FAILURES
);
}
warn!(
"mempool_sync_failed_to_index: retrying next cycle failures='{}'",
mempool_sync_failures
);
}
}

// Update subscribed clients
Expand Down
3 changes: 2 additions & 1 deletion src/chain.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
pub use bitcoin::{
address, blockdata::block::Header as BlockHeader, blockdata::script, consensus::deserialize,
hash_types::TxMerkleNode, Address, Block, BlockHash, OutPoint, ScriptBuf as Script, Sequence,
Transaction, TxIn, TxOut, Txid,
Transaction, TxIn, TxOut, Txid, Wtxid,
};

#[cfg(feature = "liquid")]
Expand All @@ -11,6 +11,7 @@ pub use {
elements::{
address, confidential, encode::deserialize, script, Address, AssetId, Block, BlockHash,
BlockHeader, OutPoint, Script, Sequence, Transaction, TxIn, TxMerkleNode, TxOut, Txid,
Wtxid,
},
};

Expand Down
Loading
Loading