Skip to content
Open
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
4 changes: 2 additions & 2 deletions src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -124,7 +124,6 @@ pub struct Config {
#[cfg(feature = "liquid")]
pub asset_db_path: Option<PathBuf>,

#[cfg(feature = "electrum-discovery")]
pub electrum_public_hosts: Option<crate::electrum::ServerHosts>,
#[cfg(feature = "electrum-discovery")]
pub electrum_announce: bool,
Expand Down Expand Up @@ -706,6 +705,8 @@ impl Config {
let electrum_public_hosts = m
.value_of("electrum_public_hosts")
.map(|s| serde_json::from_str(s).expect("invalid --electrum-public-hosts"));
#[cfg(not(feature = "electrum-discovery"))]
let electrum_public_hosts: Option<crate::electrum::ServerHosts> = None;

let mut log = stderrlog::new();
// Base verbosity is 2 (Info), each -v flag adds one level:
Expand Down Expand Up @@ -789,7 +790,6 @@ impl Config {
#[cfg(feature = "liquid")]
asset_db_path,

#[cfg(feature = "electrum-discovery")]
electrum_public_hosts,
#[cfg(feature = "electrum-discovery")]
electrum_announce: m.is_present("electrum_announce"),
Expand Down
46 changes: 29 additions & 17 deletions src/electrum/server.rs
Original file line number Diff line number Diff line change
Expand Up @@ -35,8 +35,9 @@ use crate::util::{create_socket, spawn_thread, BlockId, BoolThen, Channel, FullH
const ELECTRS_VERSION: &str = env!("CARGO_PKG_VERSION");
const PROTOCOL_VERSION: ProtocolVersion = ProtocolVersion::new(1, 4);
const MAX_HEADERS: usize = 2016;
use crate::electrum::ServerFeatures;
#[cfg(feature = "electrum-discovery")]
use crate::electrum::{DiscoveryManager, ServerFeatures};
use crate::electrum::DiscoveryManager;

fn invalid_params(msg: impl Into<String>) -> Error {
ErrorKind::InvalidParams(msg.into()).into()
Expand Down Expand Up @@ -153,6 +154,7 @@ struct Connection {
write_timeout: Option<Duration>,
response_budget: Arc<ResponseBudget>,
checkpoint_proof_concurrency_limit: usize,
server_features: Arc<ServerFeatures>,
#[cfg(feature = "electrum-discovery")]
discovery: Option<Arc<DiscoveryManager>>,
rpc_logging: RpcLogging,
Expand Down Expand Up @@ -180,6 +182,7 @@ impl Connection {
write_timeout: Option<Duration>,
response_budget: Arc<ResponseBudget>,
checkpoint_proof_concurrency_limit: usize,
server_features: Arc<ServerFeatures>,
#[cfg(feature = "electrum-discovery")] discovery: Option<Arc<DiscoveryManager>>,
rpc_logging: RpcLogging,
salt: String,
Expand All @@ -199,6 +202,7 @@ impl Connection {
write_timeout,
response_budget,
checkpoint_proof_concurrency_limit,
server_features,
#[cfg(feature = "electrum-discovery")]
discovery,
rpc_logging,
Expand All @@ -225,13 +229,8 @@ impl Connection {
Ok(json!(self.query.config().electrum_banner.clone()))
}

#[cfg(feature = "electrum-discovery")]
fn server_features(&self) -> Result<Value> {
let discovery = self
.discovery
.as_ref()
.chain_err(|| "discovery is disabled")?;
Ok(json!(discovery.our_features()))
Ok(json!(self.server_features.as_ref()))
}

fn server_donation_address(&self) -> Result<Value> {
Expand Down Expand Up @@ -616,9 +615,8 @@ impl Connection {
"server.peers.subscribe" => self.server_peers_subscribe(),
"server.ping" => Ok(Value::Null),
"server.version" => self.server_version(),

#[cfg(feature = "electrum-discovery")]
"server.features" => self.server_features(),

#[cfg(feature = "electrum-discovery")]
"server.add_peer" => self.server_add_peer(&params),

Expand Down Expand Up @@ -1249,22 +1247,34 @@ impl RPC {

let notification = Channel::unbounded();

// Discovery is enabled when electrum-public-hosts is set
#[cfg(feature = "electrum-discovery")]
let discovery = config.electrum_public_hosts.clone().map(|hosts| {
let server_features = {
use crate::chain::genesis_hash;
let features = ServerFeatures {
hosts,
// Prefer the genesis block hash actually indexed at height 0 over the
// hardcoded genesis_hash(): on Liquid testnet/regtest the latter is an
// all-zero placeholder, and Elements regtest genesis is configurable.
// `start` runs after the initial sync, so height 0 is already indexed;
// fall back to genesis_hash() only if it somehow isn't.
let genesis_hash = query
.chain()
.hash_by_height(0)
.unwrap_or_else(|| genesis_hash(config.network_type));
Arc::new(ServerFeatures {
hosts: config.electrum_public_hosts.clone().unwrap_or_default(),
server_version: format!("electrs-esplora {}", ELECTRS_VERSION),
genesis_hash: genesis_hash(config.network_type),
genesis_hash,
protocol_min: PROTOCOL_VERSION,
protocol_max: PROTOCOL_VERSION,
hash_function: "sha256".into(),
pruning: None,
};
})
};

// Discovery is enabled when electrum-public-hosts is set
#[cfg(feature = "electrum-discovery")]
let discovery = config.electrum_public_hosts.as_ref().map(|_hosts| {
let discovery = Arc::new(DiscoveryManager::new(
config.network_type,
features,
server_features.as_ref().clone(),
PROTOCOL_VERSION,
config.electrum_announce,
config.tor_proxy,
Expand Down Expand Up @@ -1319,6 +1329,7 @@ impl RPC {
let rpc_logging = config.rpc_logging.clone();
#[cfg(feature = "electrum-discovery")]
let discovery = discovery.clone();
let server_features = Arc::clone(&server_features);

let (sender, receiver) = mpsc::sync_channel(10);
senders.lock().unwrap().push(sender.clone());
Expand All @@ -1340,6 +1351,7 @@ impl RPC {
write_timeout,
response_budget,
checkpoint_proof_concurrency_limit,
server_features,
#[cfg(feature = "electrum-discovery")]
discovery,
rpc_logging,
Expand Down
1 change: 1 addition & 0 deletions src/new_index/db.rs
Original file line number Diff line number Diff line change
Expand Up @@ -681,6 +681,7 @@ mod tests {
history_scan_limit: 100_000,
utxos_checkpoint_limit: 1_000,
electrum_txs_limit: 100,
electrum_public_hosts: None,
electrum_subscription_limit: 10_000,
electrum_checkpoint_proof_concurrency_limit: 2,
electrum_checkpoint_merkle_cache_mb: 256,
Expand Down
3 changes: 1 addition & 2 deletions tests/common.rs
Original file line number Diff line number Diff line change
Expand Up @@ -146,8 +146,7 @@ impl TestRunner {
db_write_buffer_size_mb: 256,
initial_sync_batch_size: 250,
db_cache_index_filter_blocks: false,
//#[cfg(feature = "electrum-discovery")]
//electrum_public_hosts: Option<crate::electrum::ServerHosts>,
electrum_public_hosts: None,
//#[cfg(feature = "electrum-discovery")]
//electrum_announce: bool,
//#[cfg(feature = "electrum-discovery")]
Expand Down
36 changes: 36 additions & 0 deletions tests/electrum.rs
Original file line number Diff line number Diff line change
Expand Up @@ -211,6 +211,42 @@ fn test_electrum_raw() {
assert_eq!(s, expected);
}

#[cfg_attr(not(feature = "liquid"), test)]
#[cfg_attr(feature = "liquid", allow(dead_code))]
fn test_electrum_server_features_without_public_hosts() {
// Electrum >= 4.7.0 calls server.features on connect, so it must answer
// even when peer discovery is off (no --electrum-public-hosts). See #229.
let (_electrum_server, electrum_addr, tester) = common::init_electrum_tester().unwrap();

let mut stream = TcpStream::connect(electrum_addr).unwrap();
let s = write_and_read(
&mut stream,
"{\"jsonrpc\": \"2.0\", \"method\": \"server.features\", \"params\": [], \"id\": 1}",
);
let reply: electrumd::jsonrpc::serde_json::Value =
electrumd::jsonrpc::serde_json::from_str(&s).unwrap();
assert!(reply.get("error").is_none(), "server.features errored: {}", s);

let features = &reply["result"];
assert_eq!(features["server_version"], "electrs-esplora 0.4.1");
assert_eq!(features["hash_function"], "sha256");

// The genesis hash must be the actual genesis block hash indexed at height 0,
// not the hardcoded genesis_hash() (which is an all-zero placeholder on
// Liquid testnet/regtest). The Electrum protocol serializes hashes reversed.
use bitcoin::hashes::Hash;
use bitcoin::hex::DisplayHex;
let mut genesis_bytes = tester.get_block_hash(0).unwrap().to_raw_hash().to_byte_array();
genesis_bytes.reverse();
let expected = genesis_bytes.to_lower_hex_string();
assert_eq!(
features["genesis_hash"].as_str(),
Some(expected.as_str()),
"genesis_hash must be the indexed height-0 block hash: {}",
s
);
}

#[cfg_attr(not(feature = "liquid"), test)]
#[cfg_attr(feature = "liquid", allow(dead_code))]
fn test_electrum_jsonrpc_errors() {
Expand Down