Drop required `Box`ing of `lightning-block-sync` `Future`s
What changed, and why it matters
This commit is a routine code cleanup in the Lightning Dev Kit's block synchronization module. It takes advantage of a newer Rust language feature (returning 'impl Trait' from trait methods, now that the project's minimum supported Rust version is 1.75) to remove unnecessary heap-allocated boxed futures. There is no security-relevant change here; it is purely a refactoring to simplify the code and remove an old workaround.
No security action required. Treat as a normal dependency update/refactoring commit. Reviewers may verify that the crate still compiles and tests pass under the declared MSRV (1.75).
Security signals we found
No strong security signals were identified.
Evidence from the diff
The commit refactors the lightning-block-sync crate to use Rust 1.75’s ‘impl Trait in trait return position’ (RPITIT) for async trait methods. Previously, methods returned Pin<Box<dyn Future<…>>> via the AsyncBlockSourceResult type alias to allow trait methods to return concrete future types. The patch removes AsyncBlockSourceResult entirely, replaces returns with impl Future
Changed components
lightning-block-sync/src/lib.rslightning-block-sync/src/poll.rslightning-block-sync/src/rest.rslightning-block-sync/src/rpc.rslightning-block-sync/src/gossip.rslightning-block-sync/src/test_utils.rsInspect captured patch +101 / −92
diff --git a/lightning-block-sync/src/gossip.rs b/lightning-block-sync/src/gossip.rs
index 0fe221b..5960983 100644
--- a/lightning-block-sync/src/gossip.rs
+++ b/lightning-block-sync/src/gossip.rs
@@ -2,7 +2,7 @@
//! current UTXO set. This module defines an implementation of the LDK API required to do so
//! against a [`BlockSource`] which implements a few additional methods for accessing the UTXO set.
-use crate::{AsyncBlockSourceResult, BlockData, BlockSource, BlockSourceError};
+use crate::{BlockData, BlockSource, BlockSourceError, BlockSourceResult};
use bitcoin::block::Block;
use bitcoin::constants::ChainHash;
@@ -18,7 +18,7 @@ use lightning::util::native_async::FutureSpawner;
use std::collections::VecDeque;
use std::future::Future;
use std::ops::Deref;
-use std::pin::Pin;
+use std::pin::{pin, Pin};
use std::sync::{Arc, Mutex};
use std::task::Poll;
@@ -35,11 +35,13 @@ pub trait UtxoSource: BlockSource + 'static {
/// for gossip validation.
fn get_block_hash_by_height<'a>(
&'a self, block_height: u32,
- ) -> AsyncBlockSourceResult<'a, BlockHash>;
+ ) -> impl Future<Output = BlockSourceResult<BlockHash>> + Send + 'a;
/// Returns true if the given output has *not* been spent, i.e. is a member of the current UTXO
/// set.
- fn is_output_unspent<'a>(&'a self, outpoint: OutPoint) -> AsyncBlockSourceResult<'a, bool>;
+ fn is_output_unspent<'a>(
+ &'a self, outpoint: OutPoint,
+ ) -> impl Future<Output = BlockSourceResult<bool>> + Send + 'a;
}
#[cfg(feature = "tokio")]
@@ -55,34 +57,37 @@ impl FutureSpawner for TokioSpawner {
/// A trivial future which joins two other futures and polls them at the same time, returning only
/// once both complete.
pub(crate) struct Joiner<
- A: Future<Output = Result<(BlockHash, Option<u32>), BlockSourceError>> + Unpin,
- B: Future<Output = Result<BlockHash, BlockSourceError>> + Unpin,
+ 'a,
+ A: Future<Output = Result<(BlockHash, Option<u32>), BlockSourceError>>,
+ B: Future<Output = Result<BlockHash, BlockSourceError>>,
> {
- pub a: A,
- pub b: B,
+ pub a: Pin<&'a mut A>,
+ pub b: Pin<&'a mut B>,
a_res: Option<(BlockHash, Option<u32>)>,
b_res: Option<BlockHash>,
}
impl<
- A: Future<Output = Result<(BlockHash, Option<u32>), BlockSourceError>> + Unpin,
- B: Future<Output = Result<BlockHash, BlockSourceError>> + Unpin,
- > Joiner<A, B>
+ 'a,
+ A: Future<Output = Result<(BlockHash, Option<u32>), BlockSourceError>>,
+ B: Future<Output = Result<BlockHash, BlockSourceError>>,
+ > Joiner<'a, A, B>
{
- fn new(a: A, b: B) -> Self {
+ fn new(a: Pin<&'a mut A>, b: Pin<&'a mut B>) -> Self {
Self { a, b, a_res: None, b_res: None }
}
}
impl<
- A: Future<Output = Result<(BlockHash, Option<u32>), BlockSourceError>> + Unpin,
- B: Future<Output = Result<BlockHash, BlockSourceError>> + Unpin,
- > Future for Joiner<A, B>
+ 'a,
+ A: Future<Output = Result<(BlockHash, Option<u32>), BlockSourceError>>,
+ B: Future<Output = Result<BlockHash, BlockSourceError>>,
+ > Future for Joiner<'a, A, B>
{
type Output = Result<((BlockHash, Option<u32>), BlockHash), BlockSourceError>;
fn poll(mut self: Pin<&mut Self>, ctx: &mut core::task::Context<'_>) -> Poll<Self::Output> {
if self.a_res.is_none() {
- match Pin::new(&mut self.a).poll(ctx) {
+ match self.a.as_mut().poll(ctx) {
Poll::Ready(res) => {
if let Ok(ok) = res {
self.a_res = Some(ok);
@@ -94,7 +99,7 @@ impl<
}
}
if self.b_res.is_none() {
- match Pin::new(&mut self.b).poll(ctx) {
+ match self.b.as_mut().poll(ctx) {
Poll::Ready(res) => {
if let Ok(ok) = res {
self.b_res = Some(ok);
@@ -200,10 +205,12 @@ where
}
}
- let ((_, tip_height_opt), block_hash) =
- Joiner::new(source.get_best_block(), source.get_block_hash_by_height(block_height))
- .await
- .map_err(|_| UtxoLookupError::UnknownTx)?;
+ let ((_, tip_height_opt), block_hash) = Joiner::new(
+ pin!(source.get_best_block()),
+ pin!(source.get_block_hash_by_height(block_height)),
+ )
+ .await
+ .map_err(|_| UtxoLookupError::UnknownTx)?;
if let Some(tip_height) = tip_height_opt {
// If the block doesn't yet have five confirmations, error out.
//
diff --git a/lightning-block-sync/src/lib.rs b/lightning-block-sync/src/lib.rs
index 8656ba6..0259304 100644
--- a/lightning-block-sync/src/lib.rs
+++ b/lightning-block-sync/src/lib.rs
@@ -53,7 +53,6 @@ use lightning::chain::{BestBlock, Listen};
use std::future::Future;
use std::ops::Deref;
-use std::pin::Pin;
/// Abstract type for retrieving block headers and data.
pub trait BlockSource: Sync + Send {
@@ -65,12 +64,13 @@ pub trait BlockSource: Sync + Send {
/// when `height_hint` is `None`.
fn get_header<'a>(
&'a self, header_hash: &'a BlockHash, height_hint: Option<u32>,
- ) -> AsyncBlockSourceResult<'a, BlockHeaderData>;
+ ) -> impl Future<Output = BlockSourceResult<BlockHeaderData>> + Send + 'a;
/// Returns the block for a given hash. A headers-only block source should return a `Transient`
/// error.
- fn get_block<'a>(&'a self, header_hash: &'a BlockHash)
- -> AsyncBlockSourceResult<'a, BlockData>;
+ fn get_block<'a>(
+ &'a self, header_hash: &'a BlockHash,
+ ) -> impl Future<Output = BlockSourceResult<BlockData>> + Send + 'a;
/// Returns the hash of the best block and, optionally, its height.
///
@@ -78,18 +78,14 @@ pub trait BlockSource: Sync + Send {
/// to allow for a more efficient lookup.
///
/// [`get_header`]: Self::get_header
- fn get_best_block(&self) -> AsyncBlockSourceResult<'_, (BlockHash, Option<u32>)>;
+ fn get_best_block<'a>(
+ &'a self,
+ ) -> impl Future<Output = BlockSourceResult<(BlockHash, Option<u32>)>> + Send + 'a;
}
/// Result type for `BlockSource` requests.
pub type BlockSourceResult<T> = Result<T, BlockSourceError>;
-// TODO: Replace with BlockSourceResult once `async` trait functions are supported. For details,
-// see: https://areweasyncyet.rs.
-/// Result type for asynchronous `BlockSource` requests.
-pub type AsyncBlockSourceResult<'a, T> =
- Pin<Box<dyn Future<Output = BlockSourceResult<T>> + 'a + Send>>;
-
/// Error type for `BlockSource` requests.
///
/// Transient errors may be resolved when re-polling, but no attempt will be made to re-poll on
diff --git a/lightning-block-sync/src/poll.rs b/lightning-block-sync/src/poll.rs
index 843cc96..13e0403 100644
--- a/lightning-block-sync/src/poll.rs
+++ b/lightning-block-sync/src/poll.rs
@@ -1,14 +1,12 @@
//! Adapters that make one or more [`BlockSource`]s simpler to poll for new chain tip transitions.
-use crate::{
- AsyncBlockSourceResult, BlockData, BlockHeaderData, BlockSource, BlockSourceError,
- BlockSourceResult,
-};
+use crate::{BlockData, BlockHeaderData, BlockSource, BlockSourceError, BlockSourceResult};
use bitcoin::hash_types::BlockHash;
use bitcoin::network::Network;
use lightning::chain::BestBlock;
+use std::future::Future;
use std::ops::Deref;
/// The `Poll` trait defines behavior for polling block sources for a chain tip and retrieving
@@ -22,17 +20,17 @@ pub trait Poll {
/// Returns a chain tip in terms of its relationship to the provided chain tip.
fn poll_chain_tip<'a>(
&'a self, best_known_chain_tip: ValidatedBlockHeader,
- ) -> AsyncBlockSourceResult<'a, ChainTip>;
+ ) -> impl Future<Output = BlockSourceResult<ChainTip>> + Send + 'a;
/// Returns the header that preceded the given header in the chain.
fn look_up_previous_header<'a>(
&'a self, header: &'a ValidatedBlockHeader,
- ) -> AsyncBlockSourceResult<'a, ValidatedBlockHeader>;
+ ) -> impl Future<Output = BlockSourceResult<ValidatedBlockHeader>> + Send + 'a;
/// Returns the block associated with the given header.
fn fetch_block<'a>(
&'a self, header: &'a ValidatedBlockHeader,
- ) -> AsyncBlockSourceResult<'a, ValidatedBlock>;
+ ) -> impl Future<Output = BlockSourceResult<ValidatedBlock>> + Send + 'a;
}
/// A chain tip relative to another chain tip in terms of block hash and chainwork.
@@ -217,8 +215,8 @@ impl<B: Deref<Target = T> + Sized + Send + Sync, T: BlockSource + ?Sized> Poll
{
fn poll_chain_tip<'a>(
&'a self, best_known_chain_tip: ValidatedBlockHeader,
- ) -> AsyncBlockSourceResult<'a, ChainTip> {
- Box::pin(async move {
+ ) -> impl Future<Output = BlockSourceResult<ChainTip>> + Send + 'a {
+ async move {
let (block_hash, height) = self.block_source.get_best_block().await?;
if block_hash == best_known_chain_tip.header.block_hash() {
return Ok(ChainTip::Common);
@@ -231,13 +229,13 @@ impl<B: Deref<Target = T> + Sized + Send + Sync, T: BlockSource + ?Sized> Poll
} else {
Ok(ChainTip::Worse(chain_tip))
}
- })
+ }
}
fn look_up_previous_header<'a>(
&'a self, header: &'a ValidatedBlockHeader,
- ) -> AsyncBlockSourceResult<'a, ValidatedBlockHeader> {
- Box::pin(async move {
+ ) -> impl Future<Output = BlockSourceResult<ValidatedBlockHeader>> + Send + 'a {
+ async move {
if header.height == 0 {
return Err(BlockSourceError::persistent("genesis block reached"));
}
@@ -252,15 +250,13 @@ impl<B: Deref<Target = T> + Sized + Send + Sync, T: BlockSource + ?Sized> Poll
header.check_builds_on(&previous_header, self.network)?;
Ok(previous_header)
- })
+ }
}
fn fetch_block<'a>(
&'a self, header: &'a ValidatedBlockHeader,
- ) -> AsyncBlockSourceResult<'a, ValidatedBlock> {
- Box::pin(async move {
- self.block_source.get_block(&header.block_hash).await?.validate(header.block_hash)
- })
+ ) -> impl Future<Output = BlockSourceResult<ValidatedBlock>> + Send + 'a {
+ async move { self.block_source.get_block(&header.block_hash).await?.validate(header.block_hash) }
}
}
diff --git a/lightning-block-sync/src/rest.rs b/lightning-block-sync/src/rest.rs
index 1f79ab4..619981b 100644
--- a/lightning-block-sync/src/rest.rs
+++ b/lightning-block-sync/src/rest.rs
@@ -4,13 +4,14 @@
use crate::convert::GetUtxosResponse;
use crate::gossip::UtxoSource;
use crate::http::{BinaryResponse, HttpClient, HttpEndpoint, JsonResponse};
-use crate::{AsyncBlockSourceResult, BlockData, BlockHeaderData, BlockSource};
+use crate::{BlockData, BlockHeaderData, BlockSource, BlockSourceResult};
use bitcoin::hash_types::BlockHash;
use bitcoin::OutPoint;
use std::convert::TryFrom;
use std::convert::TryInto;
+use std::future::Future;
use std::sync::Mutex;
/// A simple REST client for requesting resources using HTTP `GET`.
@@ -49,49 +50,51 @@ impl RestClient {
impl BlockSource for RestClient {
fn get_header<'a>(
&'a self, header_hash: &'a BlockHash, _height: Option<u32>,
- ) -> AsyncBlockSourceResult<'a, BlockHeaderData> {
- Box::pin(async move {
+ ) -> impl Future<Output = BlockSourceResult<BlockHeaderData>> + Send + 'a {
+ async move {
let resource_path = format!("headers/1/{}.json", header_hash.to_string());
Ok(self.request_resource::<JsonResponse, _>(&resource_path).await?)
- })
+ }
}
fn get_block<'a>(
&'a self, header_hash: &'a BlockHash,
- ) -> AsyncBlockSourceResult<'a, BlockData> {
- Box::pin(async move {
+ ) -> impl Future<Output = BlockSourceResult<BlockData>> + Send + 'a {
+ async move {
let resource_path = format!("block/{}.bin", header_hash.to_string());
Ok(BlockData::FullBlock(
self.request_resource::<BinaryResponse, _>(&resource_path).await?,
))
- })
+ }
}
- fn get_best_block<'a>(&'a self) -> AsyncBlockSourceResult<'a, (BlockHash, Option<u32>)> {
- Box::pin(
- async move { Ok(self.request_resource::<JsonResponse, _>("chaininfo.json").await?) },
- )
+ fn get_best_block<'a>(
+ &'a self,
+ ) -> impl Future<Output = BlockSourceResult<(BlockHash, Option<u32>)>> + Send + 'a {
+ async move { Ok(self.request_resource::<JsonResponse, _>("chaininfo.json").await?) }
}
}
impl UtxoSource for RestClient {
fn get_block_hash_by_height<'a>(
&'a self, block_height: u32,
- ) -> AsyncBlockSourceResult<'a, BlockHash> {
- Box::pin(async move {
+ ) -> impl Future<Output = BlockSourceResult<BlockHash>> + Send + 'a {
+ async move {
let resource_path = format!("blockhashbyheight/{}.bin", block_height);
Ok(self.request_resource::<BinaryResponse, _>(&resource_path).await?)
- })
+ }
}
- fn is_output_unspent<'a>(&'a self, outpoint: OutPoint) -> AsyncBlockSourceResult<'a, bool> {
- Box::pin(async move {
+ fn is_output_unspent<'a>(
+ &'a self, outpoint: OutPoint,
+ ) -> impl Future<Output = BlockSourceResult<bool>> + Send + 'a {
+ async move {
let resource_path =
format!("getutxos/{}-{}.json", outpoint.txid.to_string(), outpoint.vout);
let utxo_result =
self.request_resource::<JsonResponse, GetUtxosResponse>(&resource_path).await?;
Ok(utxo_result.hit_bitmap_nonempty)
- })
+ }
}
}
diff --git a/lightning-block-sync/src/rpc.rs b/lightning-block-sync/src/rpc.rs
index 3df50a2..d851ba2 100644
--- a/lightning-block-sync/src/rpc.rs
+++ b/lightning-block-sync/src/rpc.rs
@@ -3,7 +3,7 @@
use crate::gossip::UtxoSource;
use crate::http::{HttpClient, HttpEndpoint, HttpError, JsonResponse};
-use crate::{AsyncBlockSourceResult, BlockData, BlockHeaderData, BlockSource};
+use crate::{BlockData, BlockHeaderData, BlockSource, BlockSourceResult};
use bitcoin::hash_types::BlockHash;
use bitcoin::OutPoint;
@@ -16,6 +16,7 @@ use std::convert::TryFrom;
use std::convert::TryInto;
use std::error::Error;
use std::fmt;
+use std::future::Future;
use std::sync::atomic::{AtomicUsize, Ordering};
/// An error returned by the RPC server.
@@ -135,47 +136,51 @@ impl RpcClient {
impl BlockSource for RpcClient {
fn get_header<'a>(
&'a self, header_hash: &'a BlockHash, _height: Option<u32>,
- ) -> AsyncBlockSourceResult<'a, BlockHeaderData> {
- Box::pin(async move {
+ ) -> impl Future<Output = BlockSourceResult<BlockHeaderData>> + Send + 'a {
+ async move {
let header_hash = serde_json::json!(header_hash.to_string());
Ok(self.call_method("getblockheader", &[header_hash]).await?)
- })
+ }
}
fn get_block<'a>(
&'a self, header_hash: &'a BlockHash,
- ) -> AsyncBlockSourceResult<'a, BlockData> {
- Box::pin(async move {
+ ) -> impl Future<Output = BlockSourceResult<BlockData>> + Send + 'a {
+ async move {
let header_hash = serde_json::json!(header_hash.to_string());
let verbosity = serde_json::json!(0);
Ok(BlockData::FullBlock(self.call_method("getblock", &[header_hash, verbosity]).await?))
- })
+ }
}
- fn get_best_block<'a>(&'a self) -> AsyncBlockSourceResult<'a, (BlockHash, Option<u32>)> {
- Box::pin(async move { Ok(self.call_method("getblockchaininfo", &[]).await?) })
+ fn get_best_block<'a>(
+ &'a self,
+ ) -> impl Future<Output = BlockSourceResult<(BlockHash, Option<u32>)>> + Send + 'a {
+ async move { Ok(self.call_method("getblockchaininfo", &[]).await?) }
}
}
impl UtxoSource for RpcClient {
fn get_block_hash_by_height<'a>(
&'a self, block_height: u32,
- ) -> AsyncBlockSourceResult<'a, BlockHash> {
- Box::pin(async move {
+ ) -> impl Future<Output = BlockSourceResult<BlockHash>> + Send + 'a {
+ async move {
let height_param = serde_json::json!(block_height);
Ok(self.call_method("getblockhash", &[height_param]).await?)
- })
+ }
}
- fn is_output_unspent<'a>(&'a self, outpoint: OutPoint) -> AsyncBlockSourceResult<'a, bool> {
- Box::pin(async move {
+ fn is_output_unspent<'a>(
+ &'a self, outpoint: OutPoint,
+ ) -> impl Future<Output = BlockSourceResult<bool>> + Send + 'a {
+ async move {
let txid_param = serde_json::json!(outpoint.txid.to_string());
let vout_param = serde_json::json!(outpoint.vout);
let include_mempool = serde_json::json!(false);
let utxo_opt: serde_json::Value =
self.call_method("gettxout", &[txid_param, vout_param, include_mempool]).await?;
Ok(!utxo_opt.is_null())
- })
+ }
}
}
diff --git a/lightning-block-sync/src/test_utils.rs b/lightning-block-sync/src/test_utils.rs
index d307c45..40788e4 100644
--- a/lightning-block-sync/src/test_utils.rs
+++ b/lightning-block-sync/src/test_utils.rs
@@ -1,7 +1,6 @@
use crate::poll::{Validate, ValidatedBlockHeader};
use crate::{
- AsyncBlockSourceResult, BlockData, BlockHeaderData, BlockSource, BlockSourceError,
- UnboundedCache,
+ BlockData, BlockHeaderData, BlockSource, BlockSourceError, BlockSourceResult, UnboundedCache,
};
use bitcoin::block::{Block, Header, Version};
@@ -17,6 +16,7 @@ use lightning::chain::BestBlock;
use std::cell::RefCell;
use std::collections::VecDeque;
+use std::future::Future;
#[derive(Default)]
pub struct Blockchain {
@@ -141,8 +141,8 @@ impl Blockchain {
impl BlockSource for Blockchain {
fn get_header<'a>(
&'a self, header_hash: &'a BlockHash, _height_hint: Option<u32>,
- ) -> AsyncBlockSourceResult<'a, BlockHeaderData> {
- Box::pin(async move {
+ ) -> impl Future<Output = BlockSourceResult<BlockHeaderData>> + Send + 'a {
+ async move {
if self.without_headers {
return Err(BlockSourceError::persistent("header not found"));
}
@@ -158,13 +158,13 @@ impl BlockSource for Blockchain {
}
}
Err(BlockSourceError::transient("header not found"))
- })
+ }
}
fn get_block<'a>(
&'a self, header_hash: &'a BlockHash,
- ) -> AsyncBlockSourceResult<'a, BlockData> {
- Box::pin(async move {
+ ) -> impl Future<Output = BlockSourceResult<BlockData>> + Send + 'a {
+ async move {
for (height, block) in self.blocks.iter().enumerate() {
if block.header.block_hash() == *header_hash {
if let Some(without_blocks) = &self.without_blocks {
@@ -181,11 +181,13 @@ impl BlockSource for Blockchain {
}
}
Err(BlockSourceError::transient("block not found"))
- })
+ }
}
- fn get_best_block<'a>(&'a self) -> AsyncBlockSourceResult<'a, (BlockHash, Option<u32>)> {
- Box::pin(async move {
+ fn get_best_block<'a>(
+ &'a self,
+ ) -> impl Future<Output = BlockSourceResult<(BlockHash, Option<u32>)>> + Send + 'a {
+ async move {
match self.blocks.last() {
None => Err(BlockSourceError::transient("empty chain")),
Some(block) => {
@@ -193,7 +195,7 @@ impl BlockSource for Blockchain {
Ok((block.block_hash(), Some(height)))
},
}
- })
+ }
}
}
Why this scored 15/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.