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

allow functional tests to be used externally with a dynamic signer factory #3016

Open
wants to merge 12 commits into
base: main
Choose a base branch
from
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -12,5 +12,6 @@ lightning/net_graph-*.bin
lightning-rapid-gossip-sync/res/full_graph.lngossip
lightning-custom-message/target
lightning-transaction-sync/target
ext-functional-test-demo/target
no-std-check/target
msrv-no-dev-deps-check/target
2 changes: 2 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,11 @@ members = [
"lightning-custom-message",
"lightning-transaction-sync",
"possiblyrandom",
"ext-test-macro",
]

exclude = [
"ext-functional-test-demo",
"no-std-check",
"msrv-no-dev-deps-check",
"bench",
Expand Down
8 changes: 6 additions & 2 deletions ci/check-cfg-flags.py
Original file line number Diff line number Diff line change
Expand Up @@ -158,7 +158,11 @@ def check_cfg_args(cfg):
line = file.readline()
if not line:
break
if "#[cfg(" in line:
if "#[cfg(" in line and not "quote!" in line:
if not line.strip().startswith("//"):
cfg_part = cfg_regex.match(line.strip()).group(1)
match = cfg_regex.match(line.strip())
if match is None:
print("Bad cfg line: " + line.strip())
continue
cfg_part = match.group(1)
check_cfg_args(cfg_part)
6 changes: 6 additions & 0 deletions ci/ci-tests.sh
Original file line number Diff line number Diff line change
Expand Up @@ -157,6 +157,12 @@ cargo check --verbose --color always --features lightning-transaction-sync
[ "$CI_MINIMIZE_DISK_USAGE" != "" ] && cargo clean
popd

echo -e "\n\Running functional tests from outside the workspace"
pushd ext-functional-test-demo
cargo test --color always
[ "$CI_MINIMIZE_DISK_USAGE" != "" ] && cargo clean
popd

# Test that we can build downstream code with only the "release pins".
pushd msrv-no-dev-deps-check
PIN_RELEASE_DEPS
Expand Down
7 changes: 7 additions & 0 deletions ext-functional-test-demo/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
[package]
name = "ext-functional-tester"
version = "0.1.0"
edition = "2021"

[dependencies]
lightning = { path = "../lightning", features = ["_test_utils"] }
31 changes: 31 additions & 0 deletions ext-functional-test-demo/src/main.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
fn main() {
println!("Hello, world!");
}

#[cfg(test)]
mod tests {
use lightning::util::dyn_signer::{DynKeysInterfaceTrait, DynSigner};
use lightning::util::test_utils::{TestSignerFactory, SIGNER_FACTORY};
use std::panic::catch_unwind;
use std::sync::Arc;
use std::time::Duration;

struct BrokenSignerFactory();

impl TestSignerFactory for BrokenSignerFactory {
fn make_signer(
&self, _seed: &[u8; 32], _now: Duration,
) -> Box<dyn DynKeysInterfaceTrait<EcdsaSigner = DynSigner>> {
panic!()
}
}

#[test]
fn test_functional() {
lightning::ln::functional_tests::test_insane_channel_opens();
lightning::ln::functional_tests::fake_network_test();

SIGNER_FACTORY.set(Arc::new(BrokenSignerFactory()));
catch_unwind(|| lightning::ln::functional_tests::fake_network_test()).unwrap_err();
}
}
14 changes: 14 additions & 0 deletions ext-test-macro/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
[package]
name = "ext-test-macro"
Copy link
Contributor

@tnull tnull Apr 24, 2024

Choose a reason for hiding this comment

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

I wonder if we should call this more generally ldk-macros? Currently, we use bdk-macros to provide a dual blocking/async interface in lightning-transaction-sync. However, the bdk-macros crate is basically going away with BDK 1.0, so we might want to include the corresponding functionality here?

(cc @TheBlueMatt)

version = "0.1.0"
edition = "2021"

[lib]
proc-macro = true

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[dependencies]
syn = { version = "1.0", features = ["full"] }
quote = "1.0"
proc-macro2 = "1.0"
73 changes: 73 additions & 0 deletions ext-test-macro/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
use proc_macro::TokenStream;
use proc_macro2::TokenStream as TokenStream2;
use quote::quote;
use syn::{parse2, ItemFn, ItemMod};

/// An exposed test. This is a test that will run locally and also be
/// made available to other crates that want to run it in their own context.
///
/// For example:
/// ```rust
/// use ext_test_macro::xtest;
///
/// fn f1() {}
///
/// #[xtest(feature = "_test_utils")]
/// pub fn test_f1() {
/// f1();
/// }
/// ```
///
/// May also be applied to modules, like so:
///
/// ```rust
/// use ext_test_macro::xtest;
///
/// #[xtest(feature = "_test_utils")]
/// pub mod tests {
/// use super::*;
///
/// fn f1() {}
///
/// #[xtest]
/// pub fn test_f1() {
/// f1();
/// }
/// }
/// ```
///
/// Which will include the module if we are testing or the `externalize-the-tests` feature
/// is on.
#[proc_macro_attribute]
pub fn xtest(attrs: TokenStream, item: TokenStream) -> TokenStream {
let input = syn::parse_macro_input!(item as TokenStream2);
let attrs = syn::parse_macro_input!(attrs as TokenStream2);

let expanded = if is_module_definition(input.clone()) {
let cfg = if attrs.is_empty() {
quote! { #[cfg(test)] }
} else {
quote! { #[cfg(any(test, #attrs))] }
};
quote! {
#cfg
#input
}
} else if is_function_definition(input.clone()) {
quote! {
#[cfg_attr(test, ::core::prelude::v1::test)]
#input
}
} else {
panic!("xtest can only be applied to functions or modules");
};
expanded.into()
}

fn is_module_definition(input: TokenStream2) -> bool {
parse2::<ItemMod>(input).is_ok()
}

fn is_function_definition(input: TokenStream2) -> bool {
parse2::<ItemFn>(input).is_ok()
}
3 changes: 3 additions & 0 deletions fuzz/src/chanmon_consistency.rs
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@ use std::sync::{Arc,Mutex};
use std::sync::atomic;
use std::io::Cursor;
use bitcoin::bech32::u5;
use lightning::util::dyn_signer::DynSigner;

const MAX_FEE: u32 = 10_000;
struct FuzzEstimator {
Expand Down Expand Up @@ -276,6 +277,7 @@ impl SignerProvider for KeyProvider {
channel_keys_id,
);
let revoked_commitment = self.make_enforcement_state_cell(keys.commitment_seed);
let keys = DynSigner::new(keys);
TestChannelSigner::new_with_revoked(keys, revoked_commitment, false)
}

Expand All @@ -284,6 +286,7 @@ impl SignerProvider for KeyProvider {

let inner: InMemorySigner = ReadableArgs::read(&mut reader, self)?;
let state = self.make_enforcement_state_cell(inner.commitment_seed);
let inner = DynSigner::new(inner);

Ok(TestChannelSigner {
inner,
Expand Down
8 changes: 6 additions & 2 deletions fuzz/src/full_stack.rs
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@ use std::cmp;
use std::sync::{Arc, Mutex};
use std::sync::atomic::{AtomicU64,AtomicUsize,Ordering};
use bitcoin::bech32::u5;
use lightning::util::dyn_signer::DynSigner;

#[inline]
pub fn slice_to_be16(v: &[u8]) -> u16 {
Expand Down Expand Up @@ -370,7 +371,7 @@ impl SignerProvider for KeyProvider {
let secp_ctx = Secp256k1::signing_only();
let ctr = channel_keys_id[0];
let (inbound, state) = self.signer_state.borrow().get(&ctr).unwrap().clone();
TestChannelSigner::new_with_revoked(if inbound {
let inner = if inbound {
InMemorySigner::new(
&secp_ctx,
SecretKey::from_slice(&[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, ctr]).unwrap(),
Expand All @@ -396,12 +397,15 @@ impl SignerProvider for KeyProvider {
channel_keys_id,
channel_keys_id,
)
}, state, false)
};
let inner = DynSigner::new(inner);
TestChannelSigner::new_with_revoked(inner, state, false)
}

fn read_chan_signer(&self, mut data: &[u8]) -> Result<TestChannelSigner, DecodeError> {
let inner: InMemorySigner = ReadableArgs::read(&mut data, self)?;
let state = Arc::new(Mutex::new(EnforcementState::new()));
let inner = DynSigner::new(inner);

Ok(TestChannelSigner::new_with_revoked(
inner,
Expand Down
4 changes: 4 additions & 0 deletions lightning-background-processor/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1918,6 +1918,8 @@ mod tests {
failure: PathFailure::OnPath { network_update: None },
path: path.clone(),
short_channel_id: Some(scored_scid),
error_code: None,
error_data: None,
});
let event = $receive.expect("PaymentPathFailed not handled within deadline");
match event {
Expand All @@ -1935,6 +1937,8 @@ mod tests {
failure: PathFailure::OnPath { network_update: None },
path: path.clone(),
short_channel_id: None,
error_code: None,
error_data: None,
});
let event = $receive.expect("PaymentPathFailed not handled within deadline");
match event {
Expand Down
19 changes: 10 additions & 9 deletions lightning-custom-message/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -167,6 +167,16 @@
//! # }
//! }
//!
//! #[macro_export]
//! macro_rules! foo_bar_type_ids {
//! () => { foo_type_id!() | bar_type_id!() }
//! }
//!
//! #[macro_export]
//! macro_rules! foo_bar_baz_type_ids {
//! () => { foo_bar_type_ids!() | baz_type_id!() }
//! }
//!
//! # fn main() {
//! // The first crate may define a handler composing `FooHandler` and `BarHandler` and export the
//! // corresponding message type ids as a macro to use in further composition.
Expand All @@ -183,11 +193,6 @@
//! }
//! );
//!
//! #[macro_export]
//! macro_rules! foo_bar_type_ids {
//! () => { foo_type_id!() | bar_type_id!() }
//! }
//!
//! // Another crate can then define a handler further composing `FooBarHandler` with `BazHandler`
//! // and similarly export the composition of message type ids as a macro.
//!
Expand All @@ -203,10 +208,6 @@
//! }
//! );
//!
//! #[macro_export]
//! macro_rules! foo_bar_baz_type_ids {
//! () => { foo_bar_type_ids!() | baz_type_id!() }
//! }
//! # }
//!```
//!
Expand Down
12 changes: 7 additions & 5 deletions lightning-invoice/src/payment.rs
Original file line number Diff line number Diff line change
Expand Up @@ -173,6 +173,9 @@ mod tests {
use lightning::ln::channelmanager::{Retry, PaymentId};
use lightning::ln::msgs::ChannelMessageHandler;
use lightning::ln::functional_test_utils::*;
use lightning::sign::{NodeSigner, Recipient};
use bech32::ToBase32;

// Test that a payment metadata read from an invoice passed to `pay_invoice` makes it all
// the way out through the `PaymentClaimable` event.
let chanmon_cfgs = create_chanmon_cfgs(2);
Expand All @@ -194,11 +197,10 @@ mod tests {
.min_final_cltv_expiry_delta(144)
.amount_milli_satoshis(50_000)
.payment_metadata(payment_metadata.clone())
.build_signed(|hash| {
Secp256k1::new().sign_ecdsa_recoverable(hash,
&nodes[1].keys_manager.backing.get_node_secret_key())
})
.unwrap();
.build_raw().unwrap();
let sig = nodes[1].keys_manager.backing.sign_invoice(invoice.hrp.to_string().as_bytes(), &invoice.data.to_base32(), Recipient::Node).unwrap();
let invoice = invoice.sign::<_, ()>(|_| Ok(sig)).unwrap();
let invoice = Bolt11Invoice::from_signed(invoice).unwrap();

let (hash, onion, params) = payment_parameters_from_invoice(&invoice).unwrap();
nodes[0].node.send_payment(hash, onion, PaymentId(hash.0), params, Retry::Attempts(0)).unwrap();
Expand Down