Skip to content

ChannelManager read errors start a fresh node #1026

Description

@ben-kaufman

TLDR

ldk-node treats any ChannelManager KV-store read error as a missing manager and creates a fresh, empty node. A timeout, connection failure, authentication error, or exhausted VSS 5xx can therefore return a successful node instead of BuildError::ReadFailed.

Vulnerable code

src/builder.rs:1935:

if let Ok(reader) = channel_manager_bytes_res {
    // Restore ChannelManager.
} else {
    // Create a fresh ChannelManager.
}

The else branch does not distinguish NotFound from other storage failures.

Impact

An established node can start with zero channels after a manager read failure. If the fresh manager is later persisted, it can overwrite the valid manager and permanently destroy the live off-chain channel state.

Loaded ChannelMonitors remain active, so the expected manager-only outcome is unilateral channel closure, fees, and delayed access to funds—not loss of the full channel balance. Principal loss requires the monitors to also be missing or unsafe.

Verification

The vulnerable behavior is confirmed by source inspection of upstream main.

The attached test poc.txt is an end-to-end regtest PoC using two real ldk-node instances and a real channel. It injects a manager-only ErrorKind::Other at the KVStore boundary and verifies that:

  • a total store outage fails closed;
  • a manager-only failure builds a node with zero channels;
  • the channel is unilaterally closed on-chain; and
  • graceful shutdown overwrites the valid manager with the smaller empty manager.

ErrorKind::Other is the error class produced by VSS for a 5xx after retry exhaustion. The PoC tests the resulting KV-store failure directly; it does not emulate the HTTP request or VSS retry layer itself.

Proposed fix

match channel_manager_bytes_res {
    Ok(reader) => restore_manager(reader)?,
    Err(error) if error.kind() == std::io::ErrorKind::NotFound => create_fresh_manager(),
    Err(error) => {
        log_error!(logger, "Failed to read ChannelManager: {}", error);
        return Err(BuildError::ReadFailed);
    }
}

Additionally, a missing manager with existing monitors should require an explicit recovery path rather than appearing as a normal empty node.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions