Skip to content

Commit bf52f5f

Browse files
authored
Merge pull request #128 from NishantBansal2003/build-send-channel-ready
smite-ir: implement BuildChannelReady operation
2 parents ec29472 + 790eab5 commit bf52f5f

6 files changed

Lines changed: 303 additions & 15 deletions

File tree

smite-ir/src/mutators/operation_param.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,12 @@ fn mutate_operation(op: &mut Operation, rng: &mut impl Rng) -> bool {
8383
true
8484
}
8585
Operation::ExtractAcceptChannel(field) => mutate_extract_field(field, rng),
86+
Operation::BuildChannelReady { include_alias } => {
87+
// Toggle the SCID alias TLV. Flipping always changes the value;
88+
// a random bool could repeat it and waste the mutation.
89+
*include_alias = !*include_alias;
90+
true
91+
}
8692
Operation::BuildNodeAnnouncement { rgb_color, alias } => {
8793
// Randomly mutate rgb_color or alias bytes in place; never change
8894
// their lengths (array types prevent it).

smite-ir/src/operation.rs

Lines changed: 32 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -131,6 +131,24 @@ pub enum Operation {
131131
/// 18: `opener_per_commitment_point` (`Point`)
132132
/// 19: `acceptor_per_commitment_point` (`Point`)
133133
BuildFundingCreated,
134+
/// Build a `channel_ready` message (BOLT 2, type 36).
135+
///
136+
/// The alias TLV is optional in `channel_ready`. Since every `u64` is a
137+
/// valid `ShortChannelId`, presence is controlled by `include_alias`
138+
/// rather than a sentinel value. When `false`, the alias TLV is omitted
139+
/// and input 2 is ignored. The `ShortChannelId` is used directly by
140+
/// `channel_update`, so the alias type must match it in order to exercise
141+
/// both valid and invalid alias SCID cases.
142+
///
143+
/// Inputs (3):
144+
/// 0: `channel_id` (`ChannelId`)
145+
/// 1: `second_per_commitment_point` (`Point`)
146+
/// 2: `short_channel_id` (`ShortChannelId`) -- the alias SCID
147+
BuildChannelReady {
148+
/// Whether to include the alias `short_channel_id` TLV from input 2.
149+
/// If `false`, the TLV is omitted and input 2 is ignored.
150+
include_alias: bool,
151+
},
134152
/// Build a `channel_announcement` message (BOLT 7, type 256).
135153
///
136154
/// All four `PrivateKey` inputs are used to sign the body.
@@ -657,6 +675,9 @@ impl fmt::Display for Operation {
657675
Self::CreateFundingTransaction => write!(f, "CreateFundingTransaction"),
658676
Self::BuildOpenChannel => write!(f, "BuildOpenChannel"),
659677
Self::BuildFundingCreated => write!(f, "BuildFundingCreated"),
678+
Self::BuildChannelReady { include_alias } => {
679+
write!(f, "BuildChannelReady{{include_alias={include_alias}}}")
680+
}
660681
Self::BuildChannelAnnouncement => write!(f, "BuildChannelAnnouncement"),
661682
Self::BuildNodeAnnouncement { rgb_color, alias } => write!(
662683
f,
@@ -700,7 +721,8 @@ impl Operation {
700721
Self::CreateFundingTransaction => Some(VariableType::FundingTransaction),
701722
Self::BuildOpenChannel => Some(VariableType::OpenChannelMessage),
702723
Self::BuildFundingCreated => Some(VariableType::FundingCreatedMessage),
703-
Self::BuildChannelAnnouncement
724+
Self::BuildChannelReady { .. }
725+
| Self::BuildChannelAnnouncement
704726
| Self::BuildNodeAnnouncement { .. }
705727
| Self::BuildChannelUpdate
706728
| Self::BuildAnnouncementSignatures => Some(VariableType::Message),
@@ -795,6 +817,12 @@ impl Operation {
795817
VariableType::Point, // acceptor_per_commitment_point
796818
],
797819

820+
Self::BuildChannelReady { .. } => vec![
821+
VariableType::ChannelId, // channel_id
822+
VariableType::Point, // second_per_commitment_point
823+
VariableType::ShortChannelId, // short_channel_id (alias)
824+
],
825+
798826
Self::BuildChannelAnnouncement => vec![
799827
VariableType::Features, // features
800828
VariableType::ChainHash, // chain_hash
@@ -868,6 +896,7 @@ impl Operation {
868896
| Self::CreateFundingTransaction
869897
| Self::BuildOpenChannel
870898
| Self::BuildFundingCreated
899+
| Self::BuildChannelReady { .. }
871900
| Self::BuildChannelAnnouncement
872901
| Self::BuildNodeAnnouncement { .. }
873902
| Self::BuildChannelUpdate
@@ -920,6 +949,7 @@ impl Operation {
920949
| Self::ExtractAcceptChannel(_)
921950
| Self::BuildOpenChannel
922951
| Self::BuildFundingCreated
952+
| Self::BuildChannelReady { .. }
923953
| Self::BuildNodeAnnouncement { .. }
924954
| Self::BuildChannelUpdate
925955
| Self::BuildAnnouncementSignatures => false,
@@ -946,6 +976,7 @@ impl Operation {
946976
| Self::LoadShutdownScript(_)
947977
| Self::LoadChannelType(_)
948978
| Self::ExtractAcceptChannel(_)
979+
| Self::BuildChannelReady { .. }
949980
| Self::BuildNodeAnnouncement { .. }
950981
| Self::MineBlocks(_) => true,
951982

smite-ir/src/tests.rs

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -500,6 +500,58 @@ fn display_build_announcement_signatures_program() {
500500
assert_eq!(program, decoded);
501501
}
502502

503+
#[test]
504+
fn display_build_channel_ready_program() {
505+
let scid = ShortChannelId::new(936_450, 1_346, 5);
506+
let instructions = vec![
507+
Instruction {
508+
operation: Operation::LoadChannelId([0xcd; 32]),
509+
inputs: vec![],
510+
},
511+
Instruction {
512+
operation: Operation::LoadPrivateKey(key(1)),
513+
inputs: vec![],
514+
},
515+
Instruction {
516+
operation: Operation::DerivePoint,
517+
inputs: vec![1],
518+
},
519+
Instruction {
520+
operation: Operation::LoadShortChannelId(scid.as_u64()),
521+
inputs: vec![],
522+
},
523+
Instruction {
524+
operation: Operation::BuildChannelReady {
525+
include_alias: true,
526+
},
527+
inputs: vec![0, 2, 3],
528+
},
529+
Instruction {
530+
operation: Operation::SendMessage,
531+
inputs: vec![4],
532+
},
533+
];
534+
535+
let program = Program { instructions };
536+
let text = program.to_string();
537+
let lines: Vec<&str> = text.lines().collect();
538+
539+
let cid_hex = "cd".repeat(32);
540+
let z31 = "00".repeat(31);
541+
let expected: Vec<String> = vec![
542+
format!("v0 = LoadChannelId(0x{cid_hex})"),
543+
format!("v1 = LoadPrivateKey(0x{z31}01)"),
544+
"v2 = DerivePoint(v1)".into(),
545+
format!("v3 = LoadShortChannelId({scid})"),
546+
"v4 = BuildChannelReady{include_alias=true}(v0, v2, v3)".into(),
547+
"SendMessage(v4)".into(),
548+
];
549+
assert_eq!(lines.len(), expected.len(), "line count mismatch");
550+
for (i, (got, want)) in lines.iter().zip(expected.iter()).enumerate() {
551+
assert_eq!(got, want, "line {i} mismatch");
552+
}
553+
}
554+
503555
#[test]
504556
fn postcard_roundtrip() {
505557
let program = Program {

smite-scenarios/src/executor.rs

Lines changed: 150 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,9 @@ use bitcoin::secp256k1::{PublicKey, Secp256k1, SecretKey};
88
use bitcoin::{OutPoint, ScriptBuf};
99
use smite::bitcoin::{BitcoinCli, Utxo};
1010
use smite::bolt::{
11-
AcceptChannel, AnnouncementSignatures, ChannelAnnouncement, ChannelId, ChannelUpdate,
12-
FundingCreated, FundingSigned, Message, NodeAnnouncement, OpenChannel, OpenChannelTlvs, Pong,
13-
ShortChannelId, msg_type,
11+
AcceptChannel, AnnouncementSignatures, ChannelAnnouncement, ChannelId, ChannelReady,
12+
ChannelReadyTlvs, ChannelUpdate, FundingCreated, FundingSigned, Message, NodeAnnouncement,
13+
OpenChannel, OpenChannelTlvs, Pong, ShortChannelId, msg_type,
1414
};
1515
use smite::channel_tx::{
1616
ChannelConfig, ChannelPartyConfig, ChannelState, FundingTransaction, HolderIdentity, Side,
@@ -276,6 +276,17 @@ impl<C: Connection, B: BitcoinRpc> Executor<C, B> {
276276
Some(Variable::FundingCreatedMessage(encoded))
277277
}
278278

279+
Operation::BuildChannelReady { include_alias } => {
280+
let cr = build_channel_ready(
281+
&variables,
282+
&instr.inputs,
283+
*include_alias,
284+
&mut self.channel_states,
285+
);
286+
let encoded = Message::ChannelReady(cr).encode();
287+
Some(Variable::Message(encoded))
288+
}
289+
279290
Operation::BuildChannelAnnouncement => {
280291
let ca = build_channel_announcement(&variables, &instr.inputs);
281292
let encoded = Message::ChannelAnnouncement(ca).encode();
@@ -728,11 +739,7 @@ fn build_funding_created(
728739
// has already been established (and possibly advanced).
729740
channel_states
730741
.entry(channel_id)
731-
.or_insert_with(|| ChannelState {
732-
config,
733-
holder,
734-
commitment: state,
735-
});
742+
.or_insert_with(|| ChannelState::new(config, holder, state));
736743

737744
Ok(FundingCreated {
738745
temporary_channel_id,
@@ -742,6 +749,39 @@ fn build_funding_created(
742749
})
743750
}
744751

752+
/// Builds a `ChannelReady` from 3 input variables (wire order).
753+
fn build_channel_ready(
754+
variables: &[Option<Variable>],
755+
inputs: &[usize],
756+
include_alias: bool,
757+
channel_states: &mut HashMap<ChannelId, ChannelState>,
758+
) -> ChannelReady {
759+
let channel_id = resolve_channel_id(variables, inputs[0]);
760+
let second_per_commitment_point = resolve_pubkey(variables, inputs[1]);
761+
let short_channel_id = include_alias.then(|| resolve_short_channel_id(variables, inputs[2]));
762+
763+
// Record the holder's next per-commitment point from the first locally-sent
764+
// `channel_ready`'s `second_per_commitment_point`. We only do so when the
765+
// channel is tracked, the commitment number is still 0, and the point is not
766+
// yet recorded: `channel_ready` may be resent, but BOLT peers ignore
767+
// redundant ones, so recording a resend would leave us with the wrong point
768+
// and make us reject a valid received commitment signature as invalid.
769+
if let Some(state) = channel_states.get_mut(&channel_id)
770+
&& state.commitment.commitment_number == 0
771+
{
772+
let next_point = state.next_holder_per_commitment_point_mut();
773+
if next_point.is_none() {
774+
*next_point = Some(second_per_commitment_point);
775+
}
776+
}
777+
778+
ChannelReady {
779+
channel_id,
780+
second_per_commitment_point,
781+
tlvs: ChannelReadyTlvs { short_channel_id },
782+
}
783+
}
784+
745785
/// Builds a signed `ChannelAnnouncement` from 7 input variables.
746786
fn build_channel_announcement(
747787
variables: &[Option<Variable>],
@@ -2553,6 +2593,108 @@ mod tests {
25532593
));
25542594
}
25552595

2596+
#[test]
2597+
fn execute_build_channel_ready() {
2598+
let channel_id = ChannelId::v1_from_funding_outpoint(OutPoint {
2599+
txid: "09b0549b35f14ee862f63bd75811c6c27963c4dea6766ec6836952ec78df1e7e"
2600+
.parse()
2601+
.unwrap(),
2602+
vout: 0,
2603+
});
2604+
let alias = ShortChannelId::new(538_532, 845, 1);
2605+
let mock_cli = MockBitcoinCli {
2606+
utxos: vec![sample_utxo()],
2607+
change_spk: sample_change_spk(),
2608+
..Default::default()
2609+
};
2610+
2611+
let mut instrs = send_funding_created_and_recv_funding_signed_instructions();
2612+
instrs.extend([
2613+
Instruction {
2614+
operation: Operation::LoadShortChannelId(alias.as_u64()),
2615+
inputs: vec![],
2616+
},
2617+
Instruction {
2618+
operation: Operation::BuildChannelReady {
2619+
include_alias: false,
2620+
},
2621+
inputs: vec![17, 1, 18],
2622+
},
2623+
Instruction {
2624+
operation: Operation::SendMessage,
2625+
inputs: vec![19],
2626+
},
2627+
Instruction {
2628+
operation: Operation::BuildChannelReady {
2629+
include_alias: true,
2630+
},
2631+
inputs: vec![17, 3, 18],
2632+
},
2633+
Instruction {
2634+
operation: Operation::SendMessage,
2635+
inputs: vec![21],
2636+
},
2637+
]);
2638+
2639+
let program = Program {
2640+
instructions: instrs,
2641+
};
2642+
2643+
// We also need to send this `funding_signed`, since the instructions reused
2644+
// by this test expect one to be present in the executor's receive queue.
2645+
// The expected signature here was computed using LDK as the source of
2646+
// truth.
2647+
let fs_bytes = Message::FundingSigned(FundingSigned {
2648+
channel_id,
2649+
signature: "304402203dbf3dbf337b042a72576488c1fb019086089d8d790a47f652346cff2511b6e70220395fdf700cb82b0abfcfe8e0b7c822181f2ee72409c82c3ff8e04e36593662c7".parse().unwrap(),
2650+
})
2651+
.encode();
2652+
let mut executor = Executor::new(MockConnection::new(), mock_cli, sample_context());
2653+
executor.conn.queue_recv(fs_bytes);
2654+
executor
2655+
.execute(&program, std::time::Instant::now())
2656+
.unwrap();
2657+
2658+
// The instructions send 1 `funding_created` and 2 `channel_ready` messages.
2659+
assert_eq!(executor.conn.sent.len(), 3);
2660+
2661+
// The first channel_ready was built with include_alias = false, so it must
2662+
// not carry the short_channel_id TLV.
2663+
let cr1 = match Message::decode(&executor.conn.sent[1]).expect("valid message") {
2664+
Message::ChannelReady(cr) => cr,
2665+
other => panic!("expected ChannelReady, got type {}", other.msg_type()),
2666+
};
2667+
let expected_pcp1 = PublicKey::from_str(
2668+
"023da092f6980e58d2c037173180e9a465476026ee50f96695963e8efe436f54eb",
2669+
)
2670+
.unwrap();
2671+
assert_eq!(cr1.channel_id, channel_id);
2672+
assert_eq!(cr1.second_per_commitment_point, expected_pcp1);
2673+
assert!(cr1.tlvs.short_channel_id.is_none());
2674+
2675+
// The second channel_ready was built with include_alias = true, so it must
2676+
// carry the alias SCID we loaded in its short_channel_id TLV.
2677+
let cr2 = match Message::decode(&executor.conn.sent[2]).expect("valid message") {
2678+
Message::ChannelReady(cr) => cr,
2679+
other => panic!("expected ChannelReady, got type {}", other.msg_type()),
2680+
};
2681+
let expected_pcp2 = PublicKey::from_str(
2682+
"030e9f7b623d2ccc7c9bd44d66d5ce21ce504c0acf6385a132cec6d3c39fa711c1",
2683+
)
2684+
.unwrap();
2685+
assert_eq!(cr2.channel_id, channel_id);
2686+
assert_eq!(cr2.second_per_commitment_point, expected_pcp2);
2687+
assert_eq!(cr2.tlvs.short_channel_id, Some(alias));
2688+
2689+
// The holder's next per-commitment point must hold the first
2690+
// `channel_ready`'s point, not any subsequent one.
2691+
let state = executor.channel_states.get_mut(&channel_id).unwrap();
2692+
assert_eq!(
2693+
*state.next_holder_per_commitment_point(),
2694+
Some(expected_pcp1)
2695+
);
2696+
}
2697+
25562698
// -- extract_field tests --
25572699

25582700
// TODO: Once we can actually construct and send accept_channel messages, it

0 commit comments

Comments
 (0)