Skip to content

server.features should not be tied to peer discovery (returns -32603 when --electrum-public-hosts unset) #229

Description

@EddieHouston

Summary

server.features is currently wired through the optional peer-discovery subsystem, so it returns a JSON-RPC -32603 ("discovery is disabled") internal error whenever --electrum-public-hosts is not configured. It would be more protocol-correct (and consistent with upstream romanz/electrs) to serve server.features independently of discovery.

Current behavior

server.features is gated behind the electrum-discovery feature and reads its payload from the DiscoveryManager:

#[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()))
}

When electrum-discovery is built but --electrum-public-hosts is unset, self.discovery is None, so a normal client calling server.features (e.g. for a genesis-hash / protocol-version compatibility check) receives:

-32603  "discovery is disabled"

-32603 is InternalError, which is misleading for what is a valid, standard request.

Why this is a problem

server.features is general server-info metadata, not a discovery operation — clients call it to check chain/protocol compatibility before anything else. Of the fields in ServerFeatures, only hosts depends on discovery config; everything else is always available:

Field Source Needs discovery config?
genesis_hash genesis_hash(network_type) no
server_version constant no
protocol_min / protocol_max PROTOCOL_VERSION no
hash_function "sha256" no
pruning None no
hosts --electrum-public-hosts yes

The Electrum protocol explicitly allows hosts to be an empty object, so server.features can return everything else with empty hosts when discovery is unconfigured. This is what upstream romanz/electrs does — it serves server.features unconditionally.

Proposed change

  1. Construct the ServerFeatures payload from config (currently built inline inside the discovery .map()), defaulting hosts to empty when --electrum-public-hosts is unset.
  2. Have server.features return that payload directly instead of going through self.discovery, and ungate it from #[cfg(feature = "electrum-discovery")] so it works in plain Bitcoin builds.
  3. Pass the same payload into DiscoveryManager::new() so there is a single source of truth.

server.add_peer should stay tied to discovery (it genuinely needs the DiscoveryManager). As a minor follow-up, when discovery is disabled it could return -32601 (method not found) rather than -32603, since "this server does not accept peers" is closer to method-not-found than an internal error.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions