Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement the UtxoSource interface for REST/RPC clients #2248

Merged
merged 5 commits into from
Aug 25, 2023

Conversation

TheBlueMatt
Copy link
Collaborator

In LDK, we expect users operating nodes on the public network to
implement the UtxoSource interface in order to validate the
gossip they receive from the network.

Sadly, because the DoS attack of flooding a node's gossip store
isn't a common issue, and because we do not provide an
implementation off-the-shelf to make doing so easily, many of our
downstream users do not have a UtxoSource implementation.

In order to change that, here we implement an async UtxoSource
in the lightning-block-sync crate, providing one for users who
sync the chain from Bitcoin Core's RPC or REST interfaces.

@TheBlueMatt TheBlueMatt force-pushed the 2023-04-gossip-check branch 3 times, most recently from aaff5be to 2edc4f4 Compare April 30, 2023 01:39
@codecov-commenter
Copy link

codecov-commenter commented Apr 30, 2023

Codecov Report

Patch coverage: 74.50% and project coverage change: +0.10% 🎉

Comparison is base (685f266) 90.33% compared to head (a5cb5e3) 90.44%.
Report is 131 commits behind head on main.

❗ Current head a5cb5e3 differs from pull request most recent head b691d4d. Consider uploading reports for the commit b691d4d to get more accurate results

❗ Your organization is not using the GitHub App Integration. As a result you may experience degraded service beginning May 15th. Please install the Github App Integration for your organization. Read more.

Additional details and impacted files
@@            Coverage Diff             @@
##             main    #2248      +/-   ##
==========================================
+ Coverage   90.33%   90.44%   +0.10%     
==========================================
  Files         106      106              
  Lines       55732    56698     +966     
  Branches    55732    56698     +966     
==========================================
+ Hits        50347    51279     +932     
- Misses       5385     5419      +34     
Files Changed Coverage Δ
lightning-block-sync/src/lib.rs 93.82% <ø> (ø)
lightning-block-sync/src/convert.rs 87.79% <41.66%> (-2.82%) ⬇️
lightning-block-sync/src/rest.rs 70.96% <79.31%> (+3.78%) ⬆️
lightning-block-sync/src/rpc.rs 78.08% <84.21%> (+0.84%) ⬆️
lightning/src/ln/functional_test_utils.rs 88.98% <100.00%> (+0.06%) ⬆️
lightning/src/routing/gossip.rs 89.73% <100.00%> (ø)
lightning/src/routing/router.rs 93.63% <100.00%> (ø)

... and 2 files with indirect coverage changes

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

Copy link
Contributor

@dunxen dunxen left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just a first pass for docs. Will look at caching a bit more in depth still today.

lightning-block-sync/src/rpc.rs Outdated Show resolved Hide resolved
lightning-block-sync/src/gossip.rs Outdated Show resolved Hide resolved
@tnull
Copy link
Contributor

tnull commented Jun 12, 2023

Unfortunately, this seems to be in need of a rebase.

@TheBlueMatt
Copy link
Collaborator Author

Rebased (just needed a use update).

Copy link
Contributor

@alecchendev alecchendev left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

First pass, generally things make sense. Correct me if I'm wrong, but the goal here is more to provide an implementation of UtxoLookup right? And implementing UtxoSource makes it easier to do so since you can just provide that to GossipVerifier instead of implementing UtxoLookup directly? The PR description confused me a little initially since it's mainly talking about UtxoSource

lightning-block-sync/src/convert.rs Outdated Show resolved Hide resolved
lightning-block-sync/src/gossip.rs Show resolved Hide resolved
Comment on lines +311 to +315
let pm = Arc::clone(&self.peer_manager);
self.spawn.spawn(async move {
let res = Self::retrieve_utxo(source, block_cache, short_channel_id).await;
fut.resolve(gossiper.network_graph(), &*gossiper, res);
pm.process_events();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is it necessary to call PeerManager::process_events here?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

process_events has to be called cause we now have a pending outbound message waiting to be sent (or possibly we are ready to process more messages from the peer's socket, which we stopped reading from cause we were busy). If we don't call it, the message will go out when we next hit a timer tick in the background processor (or some other regular event calls it for us) but this may be a while.

@TheBlueMatt
Copy link
Collaborator Author

First pass, generally things make sense. Correct me if I'm wrong, but the goal here is more to provide an implementation of UtxoLookup right? And implementing UtxoSource makes it easier to do so since you can just provide that to GossipVerifier instead of implementing UtxoLookup directly? The PR description confused me a little initially since it's mainly talking about UtxoSource

Right, a user could implement UtxoSource themselves, or, more likely, they'll use the new ones built into lightning-block-sync which do UTXO queries against Bitcoin Core's API.

Copy link
Contributor

@alecchendev alecchendev left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looking pretty good I think!

Comment on lines +211 to +217
let recent_blocks = block_cache.lock().unwrap();
for (height, block) in recent_blocks.iter() {
if *height == block_height {
process_block!(block);
break 'tx_found;
}
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Making a note that since we don't move the block to the end of the queue here, we may pop off a block even though it's been used recently - probably not a big deal, but just something that popped up

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yea, I think its fine. Making it LRU is possible, but we'd have to shift the array, which isn't bad but is kinda annoying. Also, I don't think the behavior of "LRFirstUsed" is all that bad, either, really.

lightning-block-sync/src/gossip.rs Show resolved Hide resolved
lightning-block-sync/src/gossip.rs Show resolved Hide resolved
This is actually a valid response in some cases, at least for the
`gettxout` command, where `null` is returned if no corresponding
UTXO was found, but the command otherwise succeeded.
@TheBlueMatt
Copy link
Collaborator Author

TheBlueMatt commented Jul 28, 2023

Rebased with a trivial syntax fix to hopefully fix CI.

@TheBlueMatt TheBlueMatt force-pushed the 2023-04-gossip-check branch 2 times, most recently from 117532c to a5cb5e3 Compare July 28, 2023 19:08
@TheBlueMatt
Copy link
Collaborator Author

Squashed fixups without changes.

Copy link
Contributor

@wpaulino wpaulino left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM, but will try to test this with the sample before approving

lightning-block-sync/src/convert.rs Outdated Show resolved Hide resolved
lightning-block-sync/src/gossip.rs Outdated Show resolved Hide resolved
lightning/src/ln/functional_test_utils.rs Show resolved Hide resolved
lightning-block-sync/src/gossip.rs Show resolved Hide resolved
@TheBlueMatt
Copy link
Collaborator Author

LGTM, but will try to test this with the sample before approving

I've been running this on my sample node on mainnet since basically when this PR was open, do you want a patch or do you want to try to do it to see how hard the API is to use?

@wpaulino
Copy link
Contributor

Feel free to squash but CI needs a small fix.

@TheBlueMatt
Copy link
Collaborator Author

TheBlueMatt commented Aug 23, 2023

Pushed incorporating the required fix:

$ git diff-tree -U1 71452c03 189c1fbe
diff --git a/lightning-block-sync/src/convert.rs b/lightning-block-sync/src/convert.rs
index e452cb807..bf9e95776 100644
--- a/lightning-block-sync/src/convert.rs
+++ b/lightning-block-sync/src/convert.rs
@@ -52,4 +52,4 @@ impl TryInto<BlockHash> for BinaryResponse {
 		BlockHash::from_slice(&self.0).map_err(|_|
-			Err(std::io::Error::new(std::io::ErrorKind::InvalidData, "bad block hash length"))
-		)?
+			std::io::Error::new(std::io::ErrorKind::InvalidData, "bad block hash length")
+		)
 	}

In LDK, we expect users operating nodes on the public network to
implement the `UtxoSource` interface in order to validate the
gossip they receive from the network.

Sadly, because the DoS attack of flooding a node's gossip store
isn't a common issue, and because we do not provide an
implementation off-the-shelf to make doing so easily, many of our
downstream users do not have a `UtxoSource` implementation.

In order to change that, here we implement an async `UtxoSource`
in the `lightning-block-sync` crate, providing one for users who
sync the chain from Bitcoin Core's RPC or REST interfaces.
Because a `UtxoLookup` implementation is likely to need a reference
to the `PeerManager` which contains a reference to the
`P2PGossipSync`, it is likely to be impossible to get a mutable
reference to the `P2PGossipSync` by the time we want to add a
`UtxoLookup` without a ton of boilerplate and trait wrapping.

Instead, we simply place the `UtxoLookup` in a `RwLock`, allowing
us to modify it without a mutable self reference.

The lifetime bounds updates in tests required in this commit are
entirely unclear to me, but do allow tests to continue building, so
somehow make rustc happier.
The BOLT spec mandates that channels not be announced until they
have at least six confirmations. This is important to enforce not
because we particularly care about any specific DoS concerns, but
because if we do not we may have to handle reorgs of channel
funding transactions which change their SCID or have conflicting
SCIDs.

let block_hash = source.get_block_hash_by_height(block_height).await
.map_err(|_| UtxoLookupError::UnknownTx)?;
let block_data = source.get_block(&block_hash).await
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We could end up fetching the block multiple times here since we're not holding the lock, but probably not a big deal since we're already ok with the bandwidth costs associated with P2P sync and validation.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yea, and we can't hold the lock across an await point. It does suck, I agree, but storing and poll'ing other futures sounded hard 🤷

Copy link
Contributor

@tnull tnull left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

///
/// If the `tokio` feature is enabled, this is implemented on `TokioSpawner` struct which
/// delegates to `tokio::spawn()`.
pub trait FutureSpawner : Send + Sync + 'static {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

non-blocking nit: Technically this does not spawn futures but tasks. :P

One day we may be able to replace that with an std trait: rust-lang/wg-async#283

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

non-blocking nit: Technically this does not spawn futures but tasks. :P

bleh. if you think its gonna confuse anyone ill change it but i doubt it otherwise.

One day we may be able to replace that with an std trait: rust-lang/wg-async#283

Yea, rust async is such an MVP...sadly its gotten all hung up on deciding big hairy decisions so never really got past MVP :(

@tnull tnull merged commit 3dffe54 into lightningdevkit:main Aug 25, 2023
14 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

6 participants