2023-01-21 03:28:35 +00:00
|
|
|
// This file is Copyright its original authors, visible in version control
|
|
|
|
// history.
|
|
|
|
//
|
|
|
|
// This file is licensed under the Apache License, Version 2.0 <LICENSE-APACHE
|
|
|
|
// or http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
|
|
|
|
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your option.
|
|
|
|
// You may not use this file except in accordance with one or both of these
|
|
|
|
// licenses.
|
|
|
|
|
|
|
|
//! This module contains traits for LDK to access UTXOs to check gossip data is correct.
|
|
|
|
//!
|
|
|
|
//! When lightning nodes gossip channel information, they resist DoS attacks by checking that each
|
|
|
|
//! channel matches a UTXO on-chain, requiring at least some marginal on-chain transacting in
|
|
|
|
//! order to announce a channel. This module handles that checking.
|
|
|
|
|
|
|
|
use bitcoin::{BlockHash, TxOut};
|
2023-01-21 03:48:46 +00:00
|
|
|
use bitcoin::hashes::hex::ToHex;
|
|
|
|
|
|
|
|
use crate::ln::chan_utils::make_funding_redeemscript_from_slices;
|
|
|
|
use crate::ln::msgs::{self, LightningError, ErrorAction};
|
2023-02-08 22:06:11 +00:00
|
|
|
use crate::routing::gossip::{NetworkGraph, NodeId};
|
|
|
|
use crate::util::logger::{Level, Logger};
|
2023-01-21 03:48:46 +00:00
|
|
|
use crate::util::ser::Writeable;
|
|
|
|
|
|
|
|
use crate::prelude::*;
|
|
|
|
|
2023-02-08 22:06:11 +00:00
|
|
|
use alloc::sync::{Arc, Weak};
|
|
|
|
use crate::sync::Mutex;
|
2023-01-21 03:48:46 +00:00
|
|
|
use core::ops::Deref;
|
2023-01-21 03:28:35 +00:00
|
|
|
|
|
|
|
/// An error when accessing the chain via [`UtxoLookup`].
|
|
|
|
#[derive(Clone, Debug)]
|
|
|
|
pub enum UtxoLookupError {
|
|
|
|
/// The requested chain is unknown.
|
|
|
|
UnknownChain,
|
|
|
|
|
|
|
|
/// The requested transaction doesn't exist or hasn't confirmed.
|
|
|
|
UnknownTx,
|
|
|
|
}
|
|
|
|
|
2023-02-08 22:06:11 +00:00
|
|
|
/// The result of a [`UtxoLookup::get_utxo`] call. A call may resolve either synchronously,
|
|
|
|
/// returning the `Sync` variant, or asynchronously, returning an [`UtxoFuture`] in the `Async`
|
|
|
|
/// variant.
|
|
|
|
pub enum UtxoResult {
|
|
|
|
/// A result which was resolved synchronously. It either includes a [`TxOut`] for the output
|
|
|
|
/// requested or a [`UtxoLookupError`].
|
|
|
|
Sync(Result<TxOut, UtxoLookupError>),
|
|
|
|
/// A result which will be resolved asynchronously. It includes a [`UtxoFuture`], a `clone` of
|
|
|
|
/// which you must keep locally and call [`UtxoFuture::resolve`] on once the lookup completes.
|
|
|
|
///
|
|
|
|
/// Note that in order to avoid runaway memory usage, the number of parallel checks is limited,
|
|
|
|
/// but only fairly loosely. Because a pending checks block all message processing, leaving
|
|
|
|
/// checks pending for an extended time may cause DoS of other functions. It is recommended you
|
|
|
|
/// keep a tight timeout on lookups, on the order of a few seconds.
|
|
|
|
Async(UtxoFuture),
|
|
|
|
}
|
|
|
|
|
2023-01-21 03:28:35 +00:00
|
|
|
/// The `UtxoLookup` trait defines behavior for accessing on-chain UTXOs.
|
|
|
|
pub trait UtxoLookup {
|
|
|
|
/// Returns the transaction output of a funding transaction encoded by [`short_channel_id`].
|
|
|
|
/// Returns an error if `genesis_hash` is for a different chain or if such a transaction output
|
|
|
|
/// is unknown.
|
|
|
|
///
|
|
|
|
/// [`short_channel_id`]: https://github.com/lightning/bolts/blob/master/07-routing-gossip.md#definition-of-short_channel_id
|
2023-02-08 22:06:11 +00:00
|
|
|
fn get_utxo(&self, genesis_hash: &BlockHash, short_channel_id: u64) -> UtxoResult;
|
|
|
|
}
|
|
|
|
|
|
|
|
enum ChannelAnnouncement {
|
|
|
|
Full(msgs::ChannelAnnouncement),
|
|
|
|
Unsigned(msgs::UnsignedChannelAnnouncement),
|
|
|
|
}
|
|
|
|
|
|
|
|
struct UtxoMessages {
|
|
|
|
complete: Option<Result<TxOut, UtxoLookupError>>,
|
|
|
|
channel_announce: Option<ChannelAnnouncement>,
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Represents a future resolution of a [`UtxoLookup::get_utxo`] query resolving async.
|
|
|
|
///
|
|
|
|
/// See [`UtxoResult::Async`] and [`UtxoFuture::resolve`] for more info.
|
|
|
|
#[derive(Clone)]
|
|
|
|
pub struct UtxoFuture {
|
|
|
|
state: Arc<Mutex<UtxoMessages>>,
|
|
|
|
}
|
|
|
|
|
|
|
|
/// A trivial implementation of [`UtxoLookup`] which is used to call back into the network graph
|
|
|
|
/// once we have a concrete resolution of a request.
|
|
|
|
struct UtxoResolver(Result<TxOut, UtxoLookupError>);
|
|
|
|
impl UtxoLookup for UtxoResolver {
|
|
|
|
fn get_utxo(&self, _genesis_hash: &BlockHash, _short_channel_id: u64) -> UtxoResult {
|
|
|
|
UtxoResult::Sync(self.0.clone())
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl UtxoFuture {
|
|
|
|
/// Builds a new future for later resolution.
|
|
|
|
pub fn new() -> Self {
|
|
|
|
Self { state: Arc::new(Mutex::new(UtxoMessages {
|
|
|
|
complete: None,
|
|
|
|
channel_announce: None,
|
|
|
|
}))}
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Resolves this future against the given `graph` and with the given `result`.
|
|
|
|
pub fn resolve<L: Deref>(&self, graph: &NetworkGraph<L>, result: Result<TxOut, UtxoLookupError>)
|
|
|
|
where L::Target: Logger {
|
|
|
|
let announcement = {
|
|
|
|
let mut async_messages = self.state.lock().unwrap();
|
|
|
|
|
|
|
|
if async_messages.channel_announce.is_none() {
|
|
|
|
// We raced returning to `check_channel_announcement` which hasn't updated
|
|
|
|
// `channel_announce` yet. That's okay, we can set the `complete` field which it will
|
|
|
|
// check once it gets control again.
|
|
|
|
async_messages.complete = Some(result);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
async_messages.channel_announce.take().unwrap()
|
|
|
|
};
|
|
|
|
|
|
|
|
// Now that we've updated our internal state, pass the pending messages back through the
|
|
|
|
// network graph with a different `UtxoLookup` which will resolve immediately.
|
|
|
|
// Note that we ignore errors as we don't disconnect peers anyway, so there's nothing to do
|
|
|
|
// with them.
|
|
|
|
let resolver = UtxoResolver(result);
|
|
|
|
match announcement {
|
|
|
|
ChannelAnnouncement::Full(signed_msg) => {
|
|
|
|
let _ = graph.update_channel_from_announcement(&signed_msg, &Some(&resolver));
|
|
|
|
},
|
|
|
|
ChannelAnnouncement::Unsigned(msg) => {
|
|
|
|
let _ = graph.update_channel_from_unsigned_announcement(&msg, &Some(&resolver));
|
|
|
|
},
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/// A set of messages which are pending UTXO lookups for processing.
|
|
|
|
pub(super) struct PendingChecks {
|
2023-01-21 03:28:35 +00:00
|
|
|
}
|
2023-01-21 03:48:46 +00:00
|
|
|
|
2023-02-08 22:06:11 +00:00
|
|
|
impl PendingChecks {
|
|
|
|
pub(super) fn new() -> Self {
|
|
|
|
PendingChecks {}
|
|
|
|
}
|
|
|
|
|
|
|
|
pub(super) fn check_channel_announcement<U: Deref>(&self,
|
|
|
|
utxo_lookup: &Option<U>, msg: &msgs::UnsignedChannelAnnouncement,
|
|
|
|
full_msg: Option<&msgs::ChannelAnnouncement>
|
|
|
|
) -> Result<Option<u64>, msgs::LightningError> where U::Target: UtxoLookup {
|
|
|
|
let handle_result = |res| {
|
|
|
|
match res {
|
2023-01-21 03:48:46 +00:00
|
|
|
Ok(TxOut { value, script_pubkey }) => {
|
|
|
|
let expected_script =
|
|
|
|
make_funding_redeemscript_from_slices(msg.bitcoin_key_1.as_slice(), msg.bitcoin_key_2.as_slice()).to_v0_p2wsh();
|
|
|
|
if script_pubkey != expected_script {
|
2023-01-21 03:51:22 +00:00
|
|
|
return Err(LightningError{
|
|
|
|
err: format!("Channel announcement key ({}) didn't match on-chain script ({})",
|
|
|
|
expected_script.to_hex(), script_pubkey.to_hex()),
|
|
|
|
action: ErrorAction::IgnoreError
|
|
|
|
});
|
2023-01-21 03:48:46 +00:00
|
|
|
}
|
2023-01-21 03:51:22 +00:00
|
|
|
Ok(Some(value))
|
2023-01-21 03:48:46 +00:00
|
|
|
},
|
|
|
|
Err(UtxoLookupError::UnknownChain) => {
|
2023-01-21 03:51:22 +00:00
|
|
|
Err(LightningError {
|
|
|
|
err: format!("Channel announced on an unknown chain ({})",
|
|
|
|
msg.chain_hash.encode().to_hex()),
|
|
|
|
action: ErrorAction::IgnoreError
|
|
|
|
})
|
2023-01-21 03:48:46 +00:00
|
|
|
},
|
|
|
|
Err(UtxoLookupError::UnknownTx) => {
|
2023-01-21 03:51:22 +00:00
|
|
|
Err(LightningError {
|
|
|
|
err: "Channel announced without corresponding UTXO entry".to_owned(),
|
|
|
|
action: ErrorAction::IgnoreError
|
|
|
|
})
|
2023-01-21 03:48:46 +00:00
|
|
|
},
|
|
|
|
}
|
2023-02-08 22:06:11 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
match utxo_lookup {
|
|
|
|
&None => {
|
|
|
|
// Tentatively accept, potentially exposing us to DoS attacks
|
|
|
|
Ok(None)
|
|
|
|
},
|
|
|
|
&Some(ref utxo_lookup) => {
|
|
|
|
match utxo_lookup.get_utxo(&msg.chain_hash, msg.short_channel_id) {
|
|
|
|
UtxoResult::Sync(res) => handle_result(res),
|
|
|
|
UtxoResult::Async(future) => {
|
|
|
|
let mut async_messages = future.state.lock().unwrap();
|
|
|
|
if let Some(res) = async_messages.complete.take() {
|
|
|
|
// In the unlikely event the future resolved before we managed to get it,
|
|
|
|
// handle the result in-line.
|
|
|
|
handle_result(res)
|
|
|
|
} else {
|
|
|
|
async_messages.channel_announce = Some(
|
|
|
|
if let Some(msg) = full_msg { ChannelAnnouncement::Full(msg.clone()) }
|
|
|
|
else { ChannelAnnouncement::Unsigned(msg.clone()) });
|
|
|
|
Err(LightningError {
|
|
|
|
err: "Channel being checked async".to_owned(),
|
|
|
|
action: ErrorAction::IgnoreAndLog(Level::Gossip),
|
|
|
|
})
|
|
|
|
}
|
|
|
|
},
|
|
|
|
}
|
|
|
|
}
|
2023-01-21 03:48:46 +00:00
|
|
|
}
|
2023-01-21 03:51:22 +00:00
|
|
|
}
|
2023-01-21 03:48:46 +00:00
|
|
|
}
|