Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
147 changes: 17 additions & 130 deletions smite-scenarios/src/executor/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1469,50 +1469,12 @@ fn execute_recv_channel_ready_invalid_signature_is_noop() {

#[test]
fn execute_recv_channel_ready_resend_with_new_alias() {
// We will carry forward two funding flows with different temporary channel
// IDs and funding amounts and hence different funded channel IDs.
let first_negotiation = sample_funding_negotiation();
let target_pcp = sample_pubkey(9);
let second_id = TemporaryChannelId::new([0xcc; 32]);
// A second UTXO so the program can build a second funding transaction.
let mut second_utxo = sample_utxo();
second_utxo.amount = Amount::from_sat(20_010_000);
second_utxo.outpoint.vout = 1;
// A second negotiation with a different temporary channel ID and funding
// amount, resulting in a different channel ID and allowing the second UTXO
// to fund it.
let mut second_negotiation = sample_funding_negotiation();
second_negotiation.open_channel.temporary_channel_id = second_id;
second_negotiation.open_channel.funding_satoshis = 20_000_000;
second_negotiation
.accept_channel
.as_mut()
.unwrap()
.temporary_channel_id = second_id;

// Create the funding transactions and send funding_created for both
// negotiations. Do not wait for funding_signed, since that is not the goal
// of this test. Once the funding transactions have enough confirmations,
// receive channel_ready for both negotiations. The target, instead of
// sending channel_ready for the second funding flow, sends a duplicate
// channel_ready for the first funding flow with a different alias but
// otherwise identical contents.
let mut b = ProgramBuilder::new();
send_funding_created(&mut b);
let tx = create_funding_tx_with(&mut b, 20_000_000, 15_000);
let second_temp_chan_id = b.append(Operation::LoadChannelId(second_id.0), &[]);
b.append(
Operation::SendFundingCreated,
&[tx.tx, tx.opener_privkey, second_temp_chan_id],
);
b.append(Operation::MineBlocks(6), &[]);
b.append(Operation::RecvChannelReady, &[]);
b.append(Operation::RecvChannelReady, &[]);

let mut fx = Fixture::new()
.with_utxos(vec![sample_utxo(), second_utxo])
.with_negotiation(first_negotiation)
.with_negotiation(second_negotiation)
// Instead of a `channel_ready` for the second channel, the target resends
// the first channel's with a different alias but otherwise identical
// contents.
let mut fx = two_funding_negotiations_fixture()
.queue(&channel_ready_reply(target_pcp))
.queue(&Message::ChannelReady(ChannelReady {
channel_id: funding_channel_id(),
Expand All @@ -1521,7 +1483,7 @@ fn execute_recv_channel_ready_resend_with_new_alias() {
short_channel_id: Some(ShortChannelId::from_u64(0)),
},
}));
fx.run(&b.build());
fx.run(&recv_two_channel_ready_program());

let state = fx.channel_state(&funding_channel_id());
assert_eq!(
Expand All @@ -1534,48 +1496,10 @@ fn execute_recv_channel_ready_resend_with_new_alias() {

#[test]
fn execute_recv_channel_ready_rejects_alias_reused_by_another_channel() {
// A second negotiation with a different temporary channel ID and funding
// amount, funded by a second UTXO, resulting in a different channel ID.
let second_id = TemporaryChannelId::new([0xcc; 32]);
let mut second_utxo = sample_utxo();
second_utxo.amount = Amount::from_sat(20_010_000);
second_utxo.outpoint.vout = 1;
let mut second_negotiation = sample_funding_negotiation();
second_negotiation.open_channel.temporary_channel_id = second_id;
second_negotiation.open_channel.funding_satoshis = 20_000_000;
second_negotiation
.accept_channel
.as_mut()
.unwrap()
.temporary_channel_id = second_id;
let second_channel_id = ChannelId::v1_from_funding_outpoint(OutPoint {
txid: "cb6f11974ce6265f81037dc7c0e3f054462f645361bc1e10c208f132f4acbe8d"
.parse()
.expect("valid txid"),
vout: 0,
});

// Fund both channels and receive a `channel_ready` for each once the
// funding transactions reach `minimum_depth`. The second channel's
// `channel_ready` advertises the alias the first channel already
// advertised.
let mut b = ProgramBuilder::new();
send_funding_created(&mut b);
let tx = create_funding_tx_with(&mut b, 20_000_000, 15_000);
let second_temp_chan_id = b.append(Operation::LoadChannelId(second_id.0), &[]);
b.append(
Operation::SendFundingCreated,
&[tx.tx, tx.opener_privkey, second_temp_chan_id],
);
b.append(Operation::MineBlocks(6), &[]);
b.append(Operation::RecvChannelReady, &[]);
b.append(Operation::RecvChannelReady, &[]);

// The second channel's `channel_ready` advertises the alias the first
// channel already advertised.
let alias = ShortChannelId::from_u64(42);
let err = Fixture::new()
.with_utxos(vec![sample_utxo(), second_utxo])
.with_negotiation(sample_funding_negotiation())
.with_negotiation(second_negotiation)
let err = two_funding_negotiations_fixture()
.queue(&Message::ChannelReady(ChannelReady {
channel_id: funding_channel_id(),
second_per_commitment_point: sample_pubkey(7),
Expand All @@ -1584,18 +1508,18 @@ fn execute_recv_channel_ready_rejects_alias_reused_by_another_channel() {
},
}))
.queue(&Message::ChannelReady(ChannelReady {
channel_id: second_channel_id,
channel_id: second_funding_channel_id(),
second_per_commitment_point: sample_pubkey(8),
tlvs: ChannelReadyTlvs {
short_channel_id: Some(alias),
},
}))
.run_err(&b.build());
.run_err(&recv_two_channel_ready_program());

let ExecuteError::Violation(Violation::InvalidChannelReady(id, reason)) = &err else {
panic!("unexpected error: {err:?}");
};
assert_eq!(*id, second_channel_id);
assert_eq!(*id, second_funding_channel_id());
assert!(reason.contains(&format!(
"short_channel_id alias {alias} reuses the short_channel_id of channel {}",
funding_channel_id(),
Expand All @@ -1604,65 +1528,28 @@ fn execute_recv_channel_ready_rejects_alias_reused_by_another_channel() {

#[test]
fn execute_recv_channel_ready_rejects_alias_matching_another_channels_scid() {
// A second negotiation with a different temporary channel ID and funding
// amount, funded by a second UTXO, resulting in a different channel ID.
let second_id = TemporaryChannelId::new([0xcc; 32]);
let mut second_utxo = sample_utxo();
second_utxo.amount = Amount::from_sat(20_010_000);
second_utxo.outpoint.vout = 1;
let mut second_negotiation = sample_funding_negotiation();
second_negotiation.open_channel.temporary_channel_id = second_id;
second_negotiation.open_channel.funding_satoshis = 20_000_000;
second_negotiation
.accept_channel
.as_mut()
.unwrap()
.temporary_channel_id = second_id;
let second_channel_id = ChannelId::v1_from_funding_outpoint(OutPoint {
txid: "cb6f11974ce6265f81037dc7c0e3f054462f645361bc1e10c208f132f4acbe8d"
.parse()
.expect("valid txid"),
vout: 0,
});

// Fund both channels and receive a `channel_ready` for each once the
// funding transactions reach `minimum_depth`. The second channel advertises
// an alias matching the first channel's real short channel ID.
let mut b = ProgramBuilder::new();
send_funding_created(&mut b);
let tx = create_funding_tx_with(&mut b, 20_000_000, 15_000);
let second_temp_chan_id = b.append(Operation::LoadChannelId(second_id.0), &[]);
b.append(
Operation::SendFundingCreated,
&[tx.tx, tx.opener_privkey, second_temp_chan_id],
);
b.append(Operation::MineBlocks(6), &[]);
b.append(Operation::RecvChannelReady, &[]);
b.append(Operation::RecvChannelReady, &[]);

// The second channel advertises an alias matching the first channel's real
// short channel ID.
let alias = ShortChannelId::new(800_042, 7, 0);
let err = Fixture::new()
.with_utxos(vec![sample_utxo(), second_utxo])
.with_negotiation(sample_funding_negotiation())
.with_negotiation(second_negotiation)
let err = two_funding_negotiations_fixture()
.queue(&Message::ChannelReady(ChannelReady {
channel_id: funding_channel_id(),
second_per_commitment_point: sample_pubkey(7),
tlvs: ChannelReadyTlvs::default(),
}))
.queue(&Message::ChannelReady(ChannelReady {
channel_id: second_channel_id,
channel_id: second_funding_channel_id(),
second_per_commitment_point: sample_pubkey(8),
tlvs: ChannelReadyTlvs {
short_channel_id: Some(alias),
},
}))
.run_err(&b.build());
.run_err(&recv_two_channel_ready_program());

let ExecuteError::Violation(Violation::InvalidChannelReady(id, reason)) = &err else {
panic!("unexpected error: {err:?}");
};
assert_eq!(*id, second_channel_id);
assert_eq!(*id, second_funding_channel_id());
assert!(reason.contains(&format!(
"short_channel_id alias {alias} reuses the short_channel_id of channel {}",
funding_channel_id(),
Expand Down
40 changes: 40 additions & 0 deletions smite-scenarios/src/executor/tests/harness.rs
Original file line number Diff line number Diff line change
Expand Up @@ -507,3 +507,43 @@ pub fn sample_funding_negotiation() -> PendingChannel {
funding_built: false,
}
}

/// A [`sample_funding_negotiation`] with a different `temporary_channel_id` and
/// funding amount, so that it funds a different channel.
pub fn second_funding_negotiation() -> PendingChannel {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

nit: If we directly port this to run via instructions in the future instead of injecting the negotiation, this will start to fail AcceptChannelOracle, e.g: here open_channel.funding_satoshis > 16777215 or accept_channel reuses the PCP from the first negotiation.

It doesn't really matter much here, since sample_funding_negotiation itself also violates some oracles, so I'm fine with grouping the fix with that in the future if you prefer

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

This is addressed later in #291 and #294.

let second_id = TemporaryChannelId::new([0xcc; 32]);
let mut negotiation = sample_funding_negotiation();
negotiation.open_channel.temporary_channel_id = second_id;
negotiation.open_channel.funding_satoshis = 20_000_000;
negotiation
.accept_channel
.as_mut()
.expect("accept_channel present")
.temporary_channel_id = second_id;

negotiation
}

/// The channel id [`second_funding_negotiation`]'s funding transaction
/// produces.
pub fn second_funding_channel_id() -> ChannelId {
ChannelId::v1_from_funding_outpoint(OutPoint {
txid: "cb6f11974ce6265f81037dc7c0e3f054462f645361bc1e10c208f132f4acbe8d"
.parse()
.expect("valid txid"),
vout: 0,
})
}

/// A fixture with both [`sample_funding_negotiation`] and
/// [`second_funding_negotiation`] seeded, and a UTXO to fund each.
pub fn two_funding_negotiations_fixture() -> Fixture {
let mut second_utxo = sample_utxo();
second_utxo.amount = Amount::from_sat(20_010_000);
second_utxo.outpoint.vout = 1;

Fixture::new()
.with_utxos(vec![sample_utxo(), second_utxo])
.with_negotiation(sample_funding_negotiation())
.with_negotiation(second_funding_negotiation())
}
27 changes: 26 additions & 1 deletion smite-scenarios/src/executor/tests/programs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,10 @@
//! `raw_program` is an exception and doesn't use [`ProgramBuilder`] since its
//! purpose is to create malformed program.

use super::harness::{PointSource, SampleOpenChannel, acceptor_funding_sk, opener_funding_sk};
use super::harness::{
PointSource, SampleOpenChannel, acceptor_funding_sk, opener_funding_sk,
second_funding_negotiation,
};
use crate::executor::*;
use smite_ir::Instruction;
use smite_ir::builder::ProgramBuilder;
Expand Down Expand Up @@ -317,6 +320,28 @@ pub fn recv_channel_ready_program(confirmations: u8) -> Program {
b.build()
}

/// A program that sends `funding_created` for both negotiations of
/// `two_funding_negotiations_fixture` without awaiting `funding_signed`, mines
/// to their `minimum_depth`, and receives a `channel_ready` for each.
pub fn recv_two_channel_ready_program() -> Program {
let second = second_funding_negotiation().open_channel;

let mut b = ProgramBuilder::new();
send_funding_created(&mut b);
let tx = create_funding_tx_with(&mut b, second.funding_satoshis, FUNDING_FEERATE_PER_KW);
let temporary_channel_id =
b.append(Operation::LoadChannelId(second.temporary_channel_id.0), &[]);
b.append(
Operation::SendFundingCreated,

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I know this is just a refactor of the existing instructions, but this will start to fail once we add an oracle to channel_ready that checks whether funding_signed was sent

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Yes, we will need to change this for the new oracle check. I didn't want to change it during the refactor though.

&[tx.tx, tx.opener_privkey, temporary_channel_id],
);
b.append(Operation::MineBlocks(6), &[]);
b.append(Operation::RecvChannelReady, &[]);
b.append(Operation::RecvChannelReady, &[]);

b.build()
}

// -- Gossip --

/// Emits a `channel_announcement` carrying the `ShortChannelId` variable
Expand Down
Loading