Remove `Send + Sync` bounds when `no-std`
What changed, and why it matters
This commit relaxes Rust type-system requirements (Send + Sync bounds) for the library's 'no-std' build mode and generalizes an internal future type alias. It is a portability and API cleanup change. There is no direct evidence in the commit that it fixes an exploitable security vulnerability; it is more likely a correctness/compatibility improvement for embedded or single-threaded environments.
Treat as a routine maintenance/portability patch. Review downstream implementations of the affected traits to ensure they still compile and behave correctly under no-std. No urgent security response is indicated by the diff alone.
Security signals we found
Trait-bound relaxation in public async APIs
Conditional Send/Sync depending on std feature flag
No changes to cryptographic, consensus, or network message handling code
Evidence from the diff
The patch removes Send + Sync trait bounds from generic parameters in lightning-background-processor and changes AsyncResult<'a, T> to AsyncResult<'a, T, E> with conditional Send only under std. Under no-std, the future is no longer required to be Send. This broadens the types that can be used with these APIs, particularly for single-threaded or no-std targets where Send/Sync may not be available or meaningful. The change is pervasive but mechanical; it does not alter runtime behavior, cryptographic checks, or network parsing logic.
Changed components
lightning-background-processorlightning::events::bump_transactionlightning::signlightning::util::async_polllightning::util::persistlightning::util::sweeplightning::util::test_utilsInspect captured patch +43 / −49
diff --git a/lightning-background-processor/src/lib.rs b/lightning-background-processor/src/lib.rs
index 4d13925..47a731f 100644
--- a/lightning-background-processor/src/lib.rs
+++ b/lightning-background-processor/src/lib.rs
@@ -300,7 +300,7 @@ where
/// Updates scorer based on event and returns whether an update occurred so we can decide whether
/// to persist.
-fn update_scorer<'a, S: Deref<Target = SC> + Send + Sync, SC: 'a + WriteableScore<'a>>(
+fn update_scorer<'a, S: Deref<Target = SC>, SC: 'a + WriteableScore<'a>>(
scorer: &'a S, event: &Event, duration_since_epoch: Duration,
) -> bool {
match event {
@@ -887,10 +887,8 @@ pub async fn process_events_async<
P: Deref,
EventHandlerFuture: core::future::Future<Output = Result<(), ReplayEvent>>,
EventHandler: Fn(Event) -> EventHandlerFuture,
- ES: Deref + Send,
- M: Deref<Target = ChainMonitor<<CM::Target as AChannelManager>::Signer, CF, T, F, L, P, ES>>
- + Send
- + Sync,
+ ES: Deref,
+ M: Deref<Target = ChainMonitor<<CM::Target as AChannelManager>::Signer, CF, T, F, L, P, ES>>,
CM: Deref,
OM: Deref,
PGS: Deref<Target = P2PGossipSync<G, UL, L>>,
@@ -901,7 +899,7 @@ pub async fn process_events_async<
O: Deref,
K: Deref,
OS: Deref<Target = OutputSweeper<T, D, F, CF, K, L, O>>,
- S: Deref<Target = SC> + Send + Sync,
+ S: Deref<Target = SC>,
SC: for<'b> WriteableScore<'b>,
SleepFuture: core::future::Future<Output = bool> + core::marker::Unpin,
Sleeper: Fn(Duration) -> SleepFuture,
@@ -1356,15 +1354,13 @@ pub async fn process_events_async_with_kv_store_sync<
T: Deref,
F: Deref,
G: Deref<Target = NetworkGraph<L>>,
- L: Deref + Send + Sync,
+ L: Deref,
P: Deref,
EventHandlerFuture: core::future::Future<Output = Result<(), ReplayEvent>>,
EventHandler: Fn(Event) -> EventHandlerFuture,
- ES: Deref + Send,
- M: Deref<Target = ChainMonitor<<CM::Target as AChannelManager>::Signer, CF, T, F, L, P, ES>>
- + Send
- + Sync,
- CM: Deref + Send + Sync,
+ ES: Deref,
+ M: Deref<Target = ChainMonitor<<CM::Target as AChannelManager>::Signer, CF, T, F, L, P, ES>>,
+ CM: Deref,
OM: Deref,
PGS: Deref<Target = P2PGossipSync<G, UL, L>>,
RGS: Deref<Target = RapidGossipSync<G, L>>,
@@ -1374,7 +1370,7 @@ pub async fn process_events_async_with_kv_store_sync<
O: Deref,
K: Deref,
OS: Deref<Target = OutputSweeperSync<T, D, F, CF, K, L, O>>,
- S: Deref<Target = SC> + Send + Sync,
+ S: Deref<Target = SC>,
SC: for<'b> WriteableScore<'b>,
SleepFuture: core::future::Future<Output = bool> + core::marker::Unpin,
Sleeper: Fn(Duration) -> SleepFuture,
diff --git a/lightning/src/events/bump_transaction/mod.rs b/lightning/src/events/bump_transaction/mod.rs
index 3e8c647..ec55c92 100644
--- a/lightning/src/events/bump_transaction/mod.rs
+++ b/lightning/src/events/bump_transaction/mod.rs
@@ -391,13 +391,13 @@ pub trait CoinSelectionSource {
fn select_confirmed_utxos<'a>(
&'a self, claim_id: ClaimId, must_spend: Vec<Input>, must_pay_to: &'a [TxOut],
target_feerate_sat_per_1000_weight: u32, max_tx_weight: u64,
- ) -> AsyncResult<'a, CoinSelection>;
+ ) -> AsyncResult<'a, CoinSelection, ()>;
/// Signs and provides the full witness for all inputs within the transaction known to the
/// trait (i.e., any provided via [`CoinSelectionSource::select_confirmed_utxos`]).
///
/// If your wallet does not support signing PSBTs you can call `psbt.extract_tx()` to get the
/// unsigned transaction and then sign it with your wallet.
- fn sign_psbt<'a>(&'a self, psbt: Psbt) -> AsyncResult<'a, Transaction>;
+ fn sign_psbt<'a>(&'a self, psbt: Psbt) -> AsyncResult<'a, Transaction, ()>;
}
/// An alternative to [`CoinSelectionSource`] that can be implemented and used along [`Wallet`] to
@@ -406,17 +406,17 @@ pub trait CoinSelectionSource {
/// For a synchronous version of this trait, see [`sync::WalletSourceSync`].
pub trait WalletSource {
/// Returns all UTXOs, with at least 1 confirmation each, that are available to spend.
- fn list_confirmed_utxos<'a>(&'a self) -> AsyncResult<'a, Vec<Utxo>>;
+ fn list_confirmed_utxos<'a>(&'a self) -> AsyncResult<'a, Vec<Utxo>, ()>;
/// Returns a script to use for change above dust resulting from a successful coin selection
/// attempt.
- fn get_change_script<'a>(&'a self) -> AsyncResult<'a, ScriptBuf>;
+ fn get_change_script<'a>(&'a self) -> AsyncResult<'a, ScriptBuf, ()>;
/// Signs and provides the full [`TxIn::script_sig`] and [`TxIn::witness`] for all inputs within
/// the transaction known to the wallet (i.e., any provided via
/// [`WalletSource::list_confirmed_utxos`]).
///
/// If your wallet does not support signing PSBTs you can call `psbt.extract_tx()` to get the
/// unsigned transaction and then sign it with your wallet.
- fn sign_psbt<'a>(&'a self, psbt: Psbt) -> AsyncResult<'a, Transaction>;
+ fn sign_psbt<'a>(&'a self, psbt: Psbt) -> AsyncResult<'a, Transaction, ()>;
}
/// A wrapper over [`WalletSource`] that implements [`CoinSelection`] by preferring UTXOs that would
@@ -608,7 +608,7 @@ where
fn select_confirmed_utxos<'a>(
&'a self, claim_id: ClaimId, must_spend: Vec<Input>, must_pay_to: &'a [TxOut],
target_feerate_sat_per_1000_weight: u32, max_tx_weight: u64,
- ) -> AsyncResult<'a, CoinSelection> {
+ ) -> AsyncResult<'a, CoinSelection, ()> {
Box::pin(async move {
let utxos = self.source.list_confirmed_utxos().await?;
// TODO: Use fee estimation utils when we upgrade to bitcoin v0.30.0.
@@ -659,7 +659,7 @@ where
})
}
- fn sign_psbt<'a>(&'a self, psbt: Psbt) -> AsyncResult<'a, Transaction> {
+ fn sign_psbt<'a>(&'a self, psbt: Psbt) -> AsyncResult<'a, Transaction, ()> {
self.source.sign_psbt(psbt)
}
}
diff --git a/lightning/src/events/bump_transaction/sync.rs b/lightning/src/events/bump_transaction/sync.rs
index 50e8a8a..c987b87 100644
--- a/lightning/src/events/bump_transaction/sync.rs
+++ b/lightning/src/events/bump_transaction/sync.rs
@@ -58,17 +58,17 @@ impl<T: Deref> WalletSource for WalletSourceSyncWrapper<T>
where
T::Target: WalletSourceSync,
{
- fn list_confirmed_utxos<'a>(&'a self) -> AsyncResult<'a, Vec<Utxo>> {
+ fn list_confirmed_utxos<'a>(&'a self) -> AsyncResult<'a, Vec<Utxo>, ()> {
let utxos = self.0.list_confirmed_utxos();
Box::pin(async move { utxos })
}
- fn get_change_script<'a>(&'a self) -> AsyncResult<'a, ScriptBuf> {
+ fn get_change_script<'a>(&'a self) -> AsyncResult<'a, ScriptBuf, ()> {
let script = self.0.get_change_script();
Box::pin(async move { script })
}
- fn sign_psbt<'a>(&'a self, psbt: Psbt) -> AsyncResult<'a, Transaction> {
+ fn sign_psbt<'a>(&'a self, psbt: Psbt) -> AsyncResult<'a, Transaction, ()> {
let signed_psbt = self.0.sign_psbt(psbt);
Box::pin(async move { signed_psbt })
}
@@ -171,7 +171,7 @@ where
fn select_confirmed_utxos<'a>(
&'a self, claim_id: ClaimId, must_spend: Vec<Input>, must_pay_to: &'a [TxOut],
target_feerate_sat_per_1000_weight: u32, max_tx_weight: u64,
- ) -> AsyncResult<'a, CoinSelection> {
+ ) -> AsyncResult<'a, CoinSelection, ()> {
let coins = self.0.select_confirmed_utxos(
claim_id,
must_spend,
@@ -182,7 +182,7 @@ where
Box::pin(async move { coins })
}
- fn sign_psbt<'a>(&'a self, psbt: Psbt) -> AsyncResult<'a, Transaction> {
+ fn sign_psbt<'a>(&'a self, psbt: Psbt) -> AsyncResult<'a, Transaction, ()> {
let psbt = self.0.sign_psbt(psbt);
Box::pin(async move { psbt })
}
diff --git a/lightning/src/sign/mod.rs b/lightning/src/sign/mod.rs
index 471b006..88a9f32 100644
--- a/lightning/src/sign/mod.rs
+++ b/lightning/src/sign/mod.rs
@@ -1064,7 +1064,7 @@ pub trait ChangeDestinationSource {
///
/// This method should return a different value each time it is called, to avoid linking
/// on-chain funds controlled to the same user.
- fn get_change_destination_script<'a>(&'a self) -> AsyncResult<'a, ScriptBuf>;
+ fn get_change_destination_script<'a>(&'a self) -> AsyncResult<'a, ScriptBuf, ()>;
}
/// A synchronous helper trait that describes an on-chain wallet capable of returning a (change) destination script.
@@ -1096,7 +1096,7 @@ impl<T: Deref> ChangeDestinationSource for ChangeDestinationSourceSyncWrapper<T>
where
T::Target: ChangeDestinationSourceSync,
{
- fn get_change_destination_script<'a>(&'a self) -> AsyncResult<'a, ScriptBuf> {
+ fn get_change_destination_script<'a>(&'a self) -> AsyncResult<'a, ScriptBuf, ()> {
let script = self.0.get_change_destination_script();
Box::pin(async move { script })
}
diff --git a/lightning/src/util/async_poll.rs b/lightning/src/util/async_poll.rs
index 3edfd52..7161bc7 100644
--- a/lightning/src/util/async_poll.rs
+++ b/lightning/src/util/async_poll.rs
@@ -93,11 +93,11 @@ pub(crate) fn dummy_waker() -> Waker {
}
#[cfg(feature = "std")]
-/// A type alias for a future that returns a result of type T.
-pub type AsyncResult<'a, T> = Pin<Box<dyn Future<Output = Result<T, ()>> + 'a + Send>>;
+/// A type alias for a future that returns a result of type `T` or error `E`.
+pub type AsyncResult<'a, T, E> = Pin<Box<dyn Future<Output = Result<T, E>> + 'a + Send>>;
#[cfg(not(feature = "std"))]
-/// A type alias for a future that returns a result of type T.
-pub type AsyncResult<'a, T> = Pin<Box<dyn Future<Output = Result<T, ()>> + 'a>>;
+/// A type alias for a future that returns a result of type `T` or error `E`.
+pub type AsyncResult<'a, T, E> = Pin<Box<dyn Future<Output = Result<T, E>> + 'a>>;
/// Marker trait to optionally implement `Sync` under std.
#[cfg(feature = "std")]
diff --git a/lightning/src/util/persist.rs b/lightning/src/util/persist.rs
index 0b4ba19..434e16d 100644
--- a/lightning/src/util/persist.rs
+++ b/lightning/src/util/persist.rs
@@ -34,7 +34,7 @@ use crate::chain::transaction::OutPoint;
use crate::ln::types::ChannelId;
use crate::sign::{ecdsa::EcdsaChannelSigner, EntropySource, SignerProvider};
use crate::sync::Mutex;
-use crate::util::async_poll::{dummy_waker, MaybeSend, MaybeSync};
+use crate::util::async_poll::{dummy_waker, AsyncResult, MaybeSend, MaybeSync};
use crate::util::logger::Logger;
use crate::util::native_async::FutureSpawner;
use crate::util::ser::{Readable, ReadableArgs, Writeable};
@@ -160,7 +160,7 @@ where
{
fn read(
&self, primary_namespace: &str, secondary_namespace: &str, key: &str,
- ) -> Pin<Box<dyn Future<Output = Result<Vec<u8>, io::Error>> + 'static + Send>> {
+ ) -> AsyncResult<'static, Vec<u8>, io::Error> {
let res = self.0.read(primary_namespace, secondary_namespace, key);
Box::pin(async move { res })
@@ -168,7 +168,7 @@ where
fn write(
&self, primary_namespace: &str, secondary_namespace: &str, key: &str, buf: Vec<u8>,
- ) -> Pin<Box<dyn Future<Output = Result<(), io::Error>> + 'static + Send>> {
+ ) -> AsyncResult<'static, (), io::Error> {
let res = self.0.write(primary_namespace, secondary_namespace, key, buf);
Box::pin(async move { res })
@@ -176,7 +176,7 @@ where
fn remove(
&self, primary_namespace: &str, secondary_namespace: &str, key: &str,
- ) -> Pin<Box<dyn Future<Output = Result<(), io::Error>> + 'static + Send>> {
+ ) -> AsyncResult<'static, (), io::Error> {
let res = self.0.remove(primary_namespace, secondary_namespace, key);
Box::pin(async move { res })
@@ -184,7 +184,7 @@ where
fn list(
&self, primary_namespace: &str, secondary_namespace: &str,
- ) -> Pin<Box<dyn Future<Output = Result<Vec<String>, io::Error>> + 'static + Send>> {
+ ) -> AsyncResult<'static, Vec<String>, io::Error> {
let res = self.0.list(primary_namespace, secondary_namespace);
Box::pin(async move { res })
@@ -222,7 +222,7 @@ pub trait KVStore {
/// [`ErrorKind::NotFound`]: io::ErrorKind::NotFound
fn read(
&self, primary_namespace: &str, secondary_namespace: &str, key: &str,
- ) -> Pin<Box<dyn Future<Output = Result<Vec<u8>, io::Error>> + 'static + Send>>;
+ ) -> AsyncResult<'static, Vec<u8>, io::Error>;
/// Persists the given data under the given `key`.
///
/// The order of multiple writes to the same key needs to be retained while persisting
@@ -242,7 +242,7 @@ pub trait KVStore {
/// Will create the given `primary_namespace` and `secondary_namespace` if not already present in the store.
fn write(
&self, primary_namespace: &str, secondary_namespace: &str, key: &str, buf: Vec<u8>,
- ) -> Pin<Box<dyn Future<Output = Result<(), io::Error>> + 'static + Send>>;
+ ) -> AsyncResult<'static, (), io::Error>;
/// Removes any data that had previously been persisted under the given `key`.
///
/// Returns successfully if no data will be stored for the given `primary_namespace`,
@@ -250,7 +250,7 @@ pub trait KVStore {
/// invokation or not.
fn remove(
&self, primary_namespace: &str, secondary_namespace: &str, key: &str,
- ) -> Pin<Box<dyn Future<Output = Result<(), io::Error>> + 'static + Send>>;
+ ) -> AsyncResult<'static, (), io::Error>;
/// Returns a list of keys that are stored under the given `secondary_namespace` in
/// `primary_namespace`.
///
@@ -258,7 +258,7 @@ pub trait KVStore {
/// returned keys. Returns an empty list if `primary_namespace` or `secondary_namespace` is unknown.
fn list(
&self, primary_namespace: &str, secondary_namespace: &str,
- ) -> Pin<Box<dyn Future<Output = Result<Vec<String>, io::Error>> + 'static + Send>>;
+ ) -> AsyncResult<'static, Vec<String>, io::Error>;
}
/// Provides additional interface methods that are required for [`KVStore`]-to-[`KVStore`]
diff --git a/lightning/src/util/sweep.rs b/lightning/src/util/sweep.rs
index 99a6296..95a7585 100644
--- a/lightning/src/util/sweep.rs
+++ b/lightning/src/util/sweep.rs
@@ -35,11 +35,10 @@ use bitcoin::{BlockHash, ScriptBuf, Transaction, Txid};
use core::future::Future;
use core::ops::Deref;
-use core::pin::Pin;
use core::sync::atomic::{AtomicBool, Ordering};
use core::task;
-use super::async_poll::dummy_waker;
+use super::async_poll::{dummy_waker, AsyncResult};
/// The number of blocks we wait before we prune the tracked spendable outputs.
pub const PRUNE_DELAY_BLOCKS: u32 = ARCHIVAL_DELAY_BLOCKS + ANTI_REORG_DELAY;
@@ -604,9 +603,7 @@ where
sweeper_state.dirty = true;
}
- fn persist_state<'a>(
- &self, sweeper_state: &SweeperState,
- ) -> Pin<Box<dyn Future<Output = Result<(), io::Error>> + 'a + Send>> {
+ fn persist_state<'a>(&self, sweeper_state: &SweeperState) -> AsyncResult<'a, (), io::Error> {
let encoded = sweeper_state.encode();
self.kv_store.write(
diff --git a/lightning/src/util/test_utils.rs b/lightning/src/util/test_utils.rs
index 4c5b0e7..1931287 100644
--- a/lightning/src/util/test_utils.rs
+++ b/lightning/src/util/test_utils.rs
@@ -50,6 +50,7 @@ use crate::sign::{self, ReceiveAuthKey};
use crate::sign::{ChannelSigner, PeerStorageKey};
use crate::sync::RwLock;
use crate::types::features::{ChannelFeatures, InitFeatures, NodeFeatures};
+use crate::util::async_poll::AsyncResult;
use crate::util::config::UserConfig;
use crate::util::dyn_signer::{
DynKeysInterface, DynKeysInterfaceTrait, DynPhantomKeysInterface, DynSigner,
@@ -1011,13 +1012,13 @@ impl TestStore {
impl KVStore for TestStore {
fn read(
&self, primary_namespace: &str, secondary_namespace: &str, key: &str,
- ) -> Pin<Box<dyn Future<Output = Result<Vec<u8>, io::Error>> + 'static + Send>> {
+ ) -> AsyncResult<'static, Vec<u8>, io::Error> {
let res = self.read_internal(&primary_namespace, &secondary_namespace, &key);
Box::pin(async move { res })
}
fn write(
&self, primary_namespace: &str, secondary_namespace: &str, key: &str, buf: Vec<u8>,
- ) -> Pin<Box<dyn Future<Output = Result<(), io::Error>> + 'static + Send>> {
+ ) -> AsyncResult<'static, (), io::Error> {
let path = format!("{primary_namespace}/{secondary_namespace}/{key}");
let future = Arc::new(Mutex::new((None, None)));
@@ -1030,13 +1031,13 @@ impl KVStore for TestStore {
}
fn remove(
&self, primary_namespace: &str, secondary_namespace: &str, key: &str,
- ) -> Pin<Box<dyn Future<Output = Result<(), io::Error>> + 'static + Send>> {
+ ) -> AsyncResult<'static, (), io::Error> {
let res = self.remove_internal(&primary_namespace, &secondary_namespace, &key);
Box::pin(async move { res })
}
fn list(
&self, primary_namespace: &str, secondary_namespace: &str,
- ) -> Pin<Box<dyn Future<Output = Result<Vec<String>, io::Error>> + 'static + Send>> {
+ ) -> AsyncResult<'static, Vec<String>, io::Error> {
let res = self.list_internal(primary_namespace, secondary_namespace);
Box::pin(async move { res })
}
Why this scored 17/100
Community notes
Notes can correct, qualify, or add evidence to the AI analysis. Every note shown here has been validated by a human moderator.
The AI analysis stands alone for now. Submit a note if you can add evidence or important context.