Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
21 changes: 21 additions & 0 deletions src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,9 @@ pub struct Config {
pub rest_default_max_address_summary_txs: usize,
pub rest_max_mempool_page_size: usize,
pub rest_max_mempool_txid_page_size: usize,
pub electrum_max_line_size: usize,
pub electrum_max_subscriptions: usize,
pub electrum_max_clients: usize,

#[cfg(feature = "liquid")]
pub parent_network: BNetwork,
Expand Down Expand Up @@ -278,6 +281,21 @@ impl Config {
.long("electrum-banner")
.help("Welcome banner for the Electrum server, shown in the console to clients.")
.takes_value(true)
).arg(
Arg::with_name("electrum_max_line_size")
.long("electrum-max-line-size")
.help("Maximum size of a single Electrum request line in bytes (default: 1 MiB).")
.default_value("1048576")
).arg(
Arg::with_name("electrum_max_subscriptions")
.long("electrum-max-subscriptions")
.help("Maximum number of scripthash subscriptions per client connection.")
.default_value("100")
).arg(
Arg::with_name("electrum_max_clients")
.long("electrum-max-clients")
.help("Maximum number of concurrent Electrum client connections.")
.default_value("10")
);

#[cfg(unix)]
Expand Down Expand Up @@ -547,6 +565,9 @@ impl Config {
"rest_max_mempool_txid_page_size",
usize
),
electrum_max_line_size: value_t_or_exit!(m, "electrum_max_line_size", usize),
electrum_max_subscriptions: value_t_or_exit!(m, "electrum_max_subscriptions", usize),
electrum_max_clients: value_t_or_exit!(m, "electrum_max_clients", usize),
jsonrpc_import: m.is_present("jsonrpc_import"),
light_mode: m.is_present("light_mode"),
main_loop_delay: value_t_or_exit!(m, "main_loop_delay", u64),
Expand Down
73 changes: 61 additions & 12 deletions src/electrum/server.rs
Original file line number Diff line number Diff line change
Expand Up @@ -122,17 +122,22 @@ struct Connection {
chan: SyncChannel<Message>,
stats: Arc<Stats>,
txs_limit: usize,
max_line_size: usize,
max_subscriptions: usize,
die_please: Option<Receiver<()>>,
#[cfg(feature = "electrum-discovery")]
discovery: Option<Arc<DiscoveryManager>>,
}

impl Connection {
#[allow(clippy::too_many_arguments)]
pub fn new(
query: Arc<Query>,
stream: ConnectionStream,
stats: Arc<Stats>,
txs_limit: usize,
max_line_size: usize,
max_subscriptions: usize,
die_please: Receiver<()>,
#[cfg(feature = "electrum-discovery")] discovery: Option<Arc<DiscoveryManager>>,
) -> Connection {
Expand All @@ -144,6 +149,8 @@ impl Connection {
chan: SyncChannel::new(10),
stats,
txs_limit,
max_line_size,
max_subscriptions,
die_please: Some(die_please),
#[cfg(feature = "electrum-discovery")]
discovery,
Expand Down Expand Up @@ -294,6 +301,16 @@ impl Connection {
fn blockchain_scripthash_subscribe(&mut self, params: &[Value]) -> Result<Value> {
let script_hash = hash_from_value(params.first()).chain_err(|| "bad script_hash")?;

// Enforce per-client subscription limit (don't count re-subscriptions to the same hash)
if !self.status_hashes.contains_key(&script_hash)
&& self.status_hashes.len() >= self.max_subscriptions
{
bail!(
"subscription limit reached ({} max per client)",
self.max_subscriptions
);
}

let history_txids = get_history(&self.query, &script_hash[..], self.txs_limit)?;
let status_hash = get_status_hash(history_txids, &self.query)
.map_or(Value::Null, |h| json!(hex::encode(full_hash(&h[..]))));
Expand Down Expand Up @@ -623,15 +640,25 @@ impl Connection {
fn handle_requests(
mut reader: BufReader<ConnectionStream>,
tx: crossbeam_channel::Sender<Message>,
max_line_size: usize,
) -> Result<()> {
loop {
let mut line = Vec::<u8>::new();
reader
// Read up to max_line_size + 1 bytes to detect oversized lines
let mut limited = (&mut reader).take((max_line_size as u64).saturating_add(1));
limited
.read_until(b'\n', &mut line)
.chain_err(|| "failed to read a request")?;
if line.is_empty() {
tx.send(Message::Done).chain_err(|| "channel closed")?;
return Ok(());
} else if line.len() > max_line_size {
let _ = tx.send(Message::Done);
bail!(
"request line too large ({} bytes, max is {})",
line.len(),
max_line_size
)
} else {
Comment thread
junderw marked this conversation as resolved.
if line.starts_with(&[22, 3, 1]) {
// (very) naive SSL handshake detection
Expand Down Expand Up @@ -671,7 +698,10 @@ impl Connection {
let _ = reply_killer.send(());
});

let child = spawn_thread("reader", || Connection::handle_requests(reader, tx));
let max_line_size = self.max_line_size;
let child = spawn_thread("reader", move || {
Connection::handle_requests(reader, tx, max_line_size)
});
if let Err(e) = self.handle_replies(reply_receiver) {
error!(
"[{}] connection handling failed: {}",
Expand Down Expand Up @@ -855,6 +885,9 @@ impl RPC {
});

let txs_limit = config.electrum_txs_limit;
let max_line_size = config.electrum_max_line_size;
let max_subscriptions = config.electrum_max_subscriptions;
let max_clients = config.electrum_max_clients;

RPC {
notification: notification.sender(),
Expand All @@ -872,10 +905,33 @@ impl RPC {
acceptor_shutdown_sender,
);

let mut threads = HashMap::new();
let mut threads: HashMap<thread::ThreadId, (thread::JoinHandle<()>, Sender<()>)> =
HashMap::new();
let (garbage_sender, garbage_receiver) = crossbeam_channel::unbounded();

while let Some(stream) = acceptor.receiver().recv().unwrap() {
// Clean up finished threads before checking connection limit
while let Ok(id) = garbage_receiver.try_recv() {
if let Some((thread, killer)) = threads.remove(&id) {
let _ = killer.send(());
if let Err(error) = thread.join() {
error!("failed to join {:?}: {:?}", id, error);
}
}
}

// Enforce maximum connection limit
if threads.len() >= max_clients {
warn!(
"[{}] rejecting connection: max clients reached ({}/{})",
stream.addr_string(),
threads.len(),
max_clients
);
let _ = stream.shutdown(Shutdown::Both);
continue;
}

let addr = stream.addr_string();
// explicitely scope the shadowed variables for the new thread
let query = Arc::clone(&query);
Expand All @@ -898,6 +954,8 @@ impl RPC {
stream,
stats,
txs_limit,
max_line_size,
max_subscriptions,
peace_receiver,
#[cfg(feature = "electrum-discovery")]
discovery,
Expand All @@ -911,15 +969,6 @@ impl RPC {

trace!("[{}] spawned {:?}", addr, spawned.thread().id());
threads.insert(spawned.thread().id(), (spawned, killer));
while let Ok(id) = garbage_receiver.try_recv() {
if let Some((thread, killer)) = threads.remove(&id) {
trace!("[{}] joining {:?}", addr, id);
let _ = killer.send(());
if let Err(error) = thread.join() {
error!("failed to join {:?}: {:?}", id, error);
}
}
}
}
// Drop these
drop(acceptor);
Expand Down
36 changes: 36 additions & 0 deletions start
Original file line number Diff line number Diff line change
Expand Up @@ -153,6 +153,9 @@ do
# prepare run-time variables
UTXOS_LIMIT=500
ELECTRUM_TXS_LIMIT=500
ELECTRUM_MAX_LINE_SIZE=1048576 # 1 MiB
ELECTRUM_MAX_SUBSCRIPTIONS=100
ELECTRUM_MAX_CLIENTS=10
MAIN_LOOP_DELAY=500
DAEMON_CONF="${HOME}/${DAEMON}.conf"
HTTP_SOCKET_FILE="${HOME}/socket/esplora-${DAEMON}-${NETWORK}"
Expand All @@ -167,43 +170,73 @@ do
if [ "${NODENAME}" = "node201" ];then
UTXOS_LIMIT=9000
ELECTRUM_TXS_LIMIT=9000
ELECTRUM_MAX_LINE_SIZE=16777216 # 16 MiB
ELECTRUM_MAX_SUBSCRIPTIONS=100000
ELECTRUM_MAX_CLIENTS=10000
MAIN_LOOP_DELAY=14000
fi
if [ "${NODENAME}" = "node204" ] && [ "${LOCATION}" = "sg1" ];then
UTXOS_LIMIT=9000
ELECTRUM_TXS_LIMIT=9000
ELECTRUM_MAX_LINE_SIZE=16777216 # 16 MiB
ELECTRUM_MAX_SUBSCRIPTIONS=100000
ELECTRUM_MAX_CLIENTS=10000
fi
if [ "${NODENAME}" = "node204" ] && [ "${LOCATION}" = "hnl" ];then
UTXOS_LIMIT=9000
ELECTRUM_TXS_LIMIT=9000
ELECTRUM_MAX_LINE_SIZE=16777216 # 16 MiB
ELECTRUM_MAX_SUBSCRIPTIONS=100000
ELECTRUM_MAX_CLIENTS=10000
fi
if [ "${NODENAME}" = "node206" ] && [ "${LOCATION}" = "tk7" ];then
UTXOS_LIMIT=9000
ELECTRUM_TXS_LIMIT=9000
ELECTRUM_MAX_LINE_SIZE=16777216 # 16 MiB
ELECTRUM_MAX_SUBSCRIPTIONS=100000
ELECTRUM_MAX_CLIENTS=10000
fi
if [ "${NODENAME}" = "node211" ];then
UTXOS_LIMIT=9000
ELECTRUM_TXS_LIMIT=9000
ELECTRUM_MAX_LINE_SIZE=16777216 # 16 MiB
ELECTRUM_MAX_SUBSCRIPTIONS=100000
ELECTRUM_MAX_CLIENTS=10000
fi
if [ "${NODENAME}" = "node212" ];then
UTXOS_LIMIT=9000
ELECTRUM_TXS_LIMIT=9000
ELECTRUM_MAX_LINE_SIZE=16777216 # 16 MiB
ELECTRUM_MAX_SUBSCRIPTIONS=100000
ELECTRUM_MAX_CLIENTS=10000
fi
if [ "${NODENAME}" = "node213" ];then
UTXOS_LIMIT=9000
ELECTRUM_TXS_LIMIT=9000
ELECTRUM_MAX_LINE_SIZE=16777216 # 16 MiB
ELECTRUM_MAX_SUBSCRIPTIONS=100000
ELECTRUM_MAX_CLIENTS=10000
fi
if [ "${NODENAME}" = "node214" ];then
UTXOS_LIMIT=9000
ELECTRUM_TXS_LIMIT=9000
ELECTRUM_MAX_LINE_SIZE=16777216 # 16 MiB
ELECTRUM_MAX_SUBSCRIPTIONS=100000
ELECTRUM_MAX_CLIENTS=10000
fi
if [ "${NETWORK}" = "testnet4" ];then
UTXOS_LIMIT=9000
ELECTRUM_TXS_LIMIT=9000
ELECTRUM_MAX_LINE_SIZE=16777216 # 16 MiB
ELECTRUM_MAX_SUBSCRIPTIONS=100000
ELECTRUM_MAX_CLIENTS=10000
fi
if [ "${LOCATION}" = "fmt" ];then
UTXOS_LIMIT=9000
ELECTRUM_TXS_LIMIT=9000
ELECTRUM_MAX_LINE_SIZE=16777216 # 16 MiB
ELECTRUM_MAX_SUBSCRIPTIONS=100000
ELECTRUM_MAX_CLIENTS=10000
fi

if [ ! -e "${POPULAR_SCRIPTS_FILE}" ];then
Expand All @@ -229,6 +262,9 @@ do
--address-search \
--utxos-limit "${UTXOS_LIMIT}" \
--electrum-txs-limit "${ELECTRUM_TXS_LIMIT}" \
--electrum-max-line-size "${ELECTRUM_MAX_LINE_SIZE}" \
--electrum-max-subscriptions "${ELECTRUM_MAX_SUBSCRIPTIONS}" \
--electrum-max-clients "${ELECTRUM_MAX_CLIENTS}" \
-vv
sleep 1
done
Loading