From 03dafa9c30f16e2dcc39f3eba0678347a2af136f Mon Sep 17 00:00:00 2001 From: Elias Rohrer Date: Fri, 7 Aug 2026 11:14:07 +0200 Subject: [PATCH] Fail closed on manager read errors Only a missing ChannelManager should create a fresh node. Propagate other storage errors so transient failures cannot replace live channel state with an empty manager. Fixes #1026 Co-Authored-By: HAL 9000 --- src/builder.rs | 96 ++++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 94 insertions(+), 2 deletions(-) diff --git a/src/builder.rs b/src/builder.rs index f11780099..37a35ea46 100644 --- a/src/builder.rs +++ b/src/builder.rs @@ -1934,7 +1934,16 @@ fn build_with_store_internal( // Initialize the ChannelManager let channel_manager = { - if let Ok(reader) = channel_manager_bytes_res { + let channel_manager_bytes = match channel_manager_bytes_res { + Ok(reader) => Some(reader), + Err(e) if e.kind() == lightning::io::ErrorKind::NotFound => None, + Err(e) => { + log_error!(logger, "Failed to read channel manager from store: {}", e); + return Err(BuildError::ReadFailed); + }, + }; + + if let Some(reader) = channel_manager_bytes { let channel_monitor_references = channel_monitors.iter().map(|(_, chanmon)| chanmon).collect(); let read_args = ChannelManagerReadArgs::new( @@ -2426,7 +2435,90 @@ pub(crate) fn sanitize_alias(alias_str: &str) -> Result { #[cfg(test)] mod tests { - use super::{sanitize_alias, BuildError, NodeAlias}; + use std::future::Future; + use std::sync::Arc; + + use lightning::io; + use lightning::util::persist::{ + KVStore, PageToken, PaginatedKVStore, PaginatedListResponse, + CHANNEL_MANAGER_PERSISTENCE_KEY, CHANNEL_MANAGER_PERSISTENCE_PRIMARY_NAMESPACE, + CHANNEL_MANAGER_PERSISTENCE_SECONDARY_NAMESPACE, + }; + + use super::{sanitize_alias, BuildError, NodeAlias, NodeBuilder}; + use crate::entropy::NodeEntropy; + use crate::io::test_utils::InMemoryStore; + use crate::logger::Logger; + + struct ChannelManagerReadFailingStore(InMemoryStore); + + impl KVStore for ChannelManagerReadFailingStore { + fn read( + &self, primary_namespace: &str, secondary_namespace: &str, key: &str, + ) -> impl Future, io::Error>> + 'static + Send { + let fail_read = primary_namespace == CHANNEL_MANAGER_PERSISTENCE_PRIMARY_NAMESPACE + && secondary_namespace == CHANNEL_MANAGER_PERSISTENCE_SECONDARY_NAMESPACE + && key == CHANNEL_MANAGER_PERSISTENCE_KEY; + let read = KVStore::read(&self.0, primary_namespace, secondary_namespace, key); + async move { + if fail_read { + Err(io::Error::new(io::ErrorKind::Other, "channel manager read failed")) + } else { + read.await + } + } + } + + fn write( + &self, primary_namespace: &str, secondary_namespace: &str, key: &str, buf: Vec, + ) -> impl Future> + 'static + Send { + KVStore::write(&self.0, primary_namespace, secondary_namespace, key, buf) + } + + fn remove( + &self, primary_namespace: &str, secondary_namespace: &str, key: &str, lazy: bool, + ) -> impl Future> + 'static + Send { + KVStore::remove(&self.0, primary_namespace, secondary_namespace, key, lazy) + } + + fn list( + &self, primary_namespace: &str, secondary_namespace: &str, + ) -> impl Future, io::Error>> + 'static + Send { + KVStore::list(&self.0, primary_namespace, secondary_namespace) + } + } + + impl PaginatedKVStore for ChannelManagerReadFailingStore { + fn list_paginated( + &self, primary_namespace: &str, secondary_namespace: &str, + page_token: Option, + ) -> impl Future> + 'static + Send { + PaginatedKVStore::list_paginated( + &self.0, + primary_namespace, + secondary_namespace, + page_token, + ) + } + } + + #[test] + fn channel_manager_read_failure_fails_build() { + let builder = NodeBuilder::new(); + let logger = Arc::new(Logger::new_log_facade()); + #[cfg(not(feature = "uniffi"))] + let node_entropy = NodeEntropy::from_seed_bytes([42; 64]); + #[cfg(feature = "uniffi")] + let node_entropy = NodeEntropy::from_seed_bytes(vec![42; 64]).unwrap(); + + let result = builder.build_with_store_and_logger( + node_entropy, + ChannelManagerReadFailingStore(InMemoryStore::new()), + logger, + ); + + assert!(matches!(result, Err(BuildError::ReadFailed))); + } #[test] fn sanitize_empty_node_alias() {