Skip to content
Draft
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
1 change: 1 addition & 0 deletions smite-ir/src/mutators/operation_param.rs
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,7 @@ fn mutate_operation(op: &mut Operation, rng: &mut impl Rng) -> bool {
| Operation::RecvAcceptChannel
| Operation::RecvFundingSigned
| Operation::RecvChannelReady
| Operation::RecvShutdown
| Operation::BroadcastTransaction
| Operation::LookupShortChannelId => {
unreachable!("is_param_mutable returned true for {op:?}")
Expand Down
12 changes: 11 additions & 1 deletion smite-ir/src/operation.rs
Original file line number Diff line number Diff line change
Expand Up @@ -228,6 +228,8 @@ pub enum Operation {
/// point unknown) and its funding transaction has enough confirmations for
/// the target to have sent `channel_ready`.
RecvChannelReady,
/// Receive and parse a `shutdown` message.
RecvShutdown,
/// Mines the given number of blocks on the Bitcoin network.
MineBlocks(u8),
/// Sign wallet inputs of the transaction and broadcast it via `bitcoin-cli`.
Expand Down Expand Up @@ -750,6 +752,7 @@ impl fmt::Display for Operation {
Self::RecvAcceptChannel => write!(f, "RecvAcceptChannel"),
Self::RecvFundingSigned => write!(f, "RecvFundingSigned"),
Self::RecvChannelReady => write!(f, "RecvChannelReady()"),
Self::RecvShutdown => write!(f, "RecvShutdown"),
Self::MineBlocks(v) => write!(f, "MineBlocks({v})"),
Self::BroadcastTransaction => write!(f, "BroadcastTransaction"),
Self::LookupShortChannelId => write!(f, "LookupShortChannelId"),
Expand All @@ -773,7 +776,9 @@ impl Operation {
Self::LoadForwardingFee(_) => Some(VariableType::ForwardingFee),
Self::LoadU16(_) => Some(VariableType::U16),
Self::LoadU8(_) => Some(VariableType::U8),
Self::LoadBytes(_) | Self::LoadShutdownScript(_) => Some(VariableType::Bytes),
Self::LoadBytes(_) | Self::LoadShutdownScript(_) | Self::RecvShutdown => {
Some(VariableType::Bytes)
}
Self::LoadFeatures(_) | Self::LoadChannelType(_) => Some(VariableType::Features),
Self::LoadPrivateKey(_) => Some(VariableType::PrivateKey),
Self::LoadChannelId(_) | Self::RecvFundingSigned => Some(VariableType::ChannelId),
Expand Down Expand Up @@ -912,6 +917,7 @@ impl Operation {
],
Self::RecvAcceptChannel => vec![VariableType::SentOpenChannel],
Self::RecvFundingSigned => vec![VariableType::SentFundingCreated],
Self::RecvShutdown => vec![VariableType::SentShutdown],
Self::BroadcastTransaction | Self::LookupShortChannelId => {
vec![VariableType::FundingTransaction]
}
Expand Down Expand Up @@ -957,6 +963,7 @@ impl Operation {
| Self::SendShutdown
| Self::RecvFundingSigned
| Self::RecvChannelReady
| Self::RecvShutdown
| Self::MineBlocks(_)
| Self::BroadcastTransaction
| Self::LookupShortChannelId => vec![],
Expand Down Expand Up @@ -1006,6 +1013,7 @@ impl Operation {
| Self::RecvAcceptChannel
| Self::RecvFundingSigned
| Self::RecvChannelReady
| Self::RecvShutdown
| Self::MineBlocks(_)
| Self::BroadcastTransaction => true,
}
Expand Down Expand Up @@ -1060,6 +1068,7 @@ impl Operation {
| Self::RecvAcceptChannel
| Self::RecvFundingSigned
| Self::RecvChannelReady
| Self::RecvShutdown
| Self::MineBlocks(_)
| Self::BroadcastTransaction
| Self::LookupShortChannelId => false,
Expand Down Expand Up @@ -1113,6 +1122,7 @@ impl Operation {
| Self::RecvAcceptChannel
| Self::RecvFundingSigned
| Self::RecvChannelReady
| Self::RecvShutdown
| Self::BroadcastTransaction
| Self::LookupShortChannelId => false,
}
Expand Down
7 changes: 6 additions & 1 deletion smite-ir/src/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -569,7 +569,7 @@ fn display_send_and_recv_channel_ready_program() {
}

#[test]
fn display_send_shutdown_program() {
fn display_send_and_recv_shutdown_program() {
let instructions = vec![
Instruction {
operation: Operation::LoadChannelId([0xcd; 32]),
Expand All @@ -583,6 +583,10 @@ fn display_send_shutdown_program() {
operation: Operation::SendShutdown,
inputs: vec![0, 1],
},
Instruction {
operation: Operation::RecvShutdown,
inputs: vec![2],
},
];

let program = Program { instructions };
Expand All @@ -595,6 +599,7 @@ fn display_send_shutdown_program() {
format!("v0 = LoadChannelId(0x{cid_hex})"),
format!("v1 = LoadShutdownScript(P2wpkh(0x{spk_hex}))"),
"v2 = SendShutdown(v0, v1)".into(),
"v3 = RecvShutdown(v2)".into(),
];
assert_eq!(lines.len(), expected.len(), "line count mismatch");
for (i, (got, want)) in lines.iter().zip(expected.iter()).enumerate() {
Expand Down
4 changes: 2 additions & 2 deletions smite-ir/src/variable.rs
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ pub enum Variable {
SentFundingCreated,
/// `shutdown` has been sent, so the counterparty's `shutdown` may now be
/// received.
SentShutdown,
SentShutdown(ChannelId),
}

impl Variable {
Expand Down Expand Up @@ -88,7 +88,7 @@ impl Variable {
Self::FundingTransaction(_) => VariableType::FundingTransaction,
Self::SentOpenChannel => VariableType::SentOpenChannel,
Self::SentFundingCreated => VariableType::SentFundingCreated,
Self::SentShutdown => VariableType::SentShutdown,
Self::SentShutdown(_) => VariableType::SentShutdown,
}
}
}
Expand Down
69 changes: 67 additions & 2 deletions smite-scenarios/src/executor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,9 @@ use smite::channel_tx::{
build_funding_transaction,
};
use smite::noise::{ConnectionError, NoiseConnection};
use smite::oracles::{AcceptChannelContext, AcceptChannelOracle, Oracle};
use smite::oracles::{
AcceptChannelContext, AcceptChannelOracle, Oracle, ShutdownContext, ShutdownOracle,
};
use smite::pending_channel::PendingChannel;
use smite::violation::Violation;
use smite_ir::operation::AcceptChannelField;
Expand Down Expand Up @@ -472,14 +474,15 @@ impl<C: Connection, B: BitcoinRpc> Executor<C, B> {

Operation::SendShutdown => {
let sd = build_shutdown(&variables, &instr.inputs);
let channel_id = sd.channel_id;
let encoded = Message::Shutdown(sd).encode();
log::debug!(
"[{:?}] SendShutdown: {} bytes",
start.elapsed(),
encoded.len()
);
self.conn.send_message(&encoded)?;
Some(Variable::SentShutdown)
Some(Variable::SentShutdown(channel_id))
}

Operation::RecvAcceptChannel => {
Expand Down Expand Up @@ -513,6 +516,31 @@ impl<C: Connection, B: BitcoinRpc> Executor<C, B> {
None
}

Operation::RecvShutdown => {
let channel_id = consume_sent_shutdown(&mut variables, instr.inputs[0]);
// TODO(htlc): we only expect `shutdown` when all HTLCs are resolved, else this
// is a no-op.
if is_shutdown_expected(&self.channel_states, channel_id) {
log::debug!("[{:?}] RecvShutdown: waiting", start.elapsed());
let sd = recv_shutdown(&mut self.conn)?;
log::debug!("[{:?}] RecvShutdown: received", start.elapsed());
let negotiated_features =
Features::from(self.context.target_features.as_slice());
ShutdownOracle.evaluate(&ShutdownContext {
shutdown: &sd,
channel_states: &self.channel_states,
negotiated_features: &negotiated_features,
})?;
self.channel_states
.get_mut(&channel_id)
.expect("is_shutdown_expected guarantees a tracked channel")
.peer_shutdown_received = true;
Some(Variable::Bytes(sd.scriptpubkey))
} else {
Some(Variable::Bytes(Vec::new()))
}
}

Operation::MineBlocks(v) => {
// Clear the private mempool and mine the requested blocks,
// adding those transactions to the first block.
Expand Down Expand Up @@ -793,6 +821,21 @@ fn consume_sent_funding_created(variables: &mut [Option<Variable>], index: usize
}
}

fn consume_sent_shutdown(variables: &mut [Option<Variable>], index: usize) -> ChannelId {
match resolve(variables, index) {
Variable::SentShutdown(channel_id) => {
let channel_id = *channel_id;
// Consume the affine `SentShutdown`.
variables[index] = None;
channel_id
}
other => panic!(
"variable {index}: expected SentShutdown, got {:?}",
other.var_type(),
),
}
}

// -- Operation handlers --

/// Create a funding transaction by querying the bitcoind for UTXOs and a
Expand Down Expand Up @@ -984,6 +1027,7 @@ fn build_funding_created(
state,
is_funding_outpoint_valid,
mined_txids.contains(&funding_outpoint.txid),
accept_channel.tlvs.upfront_shutdown_script.clone(),
)
});
}
Expand Down Expand Up @@ -1309,6 +1353,17 @@ fn recv_channel_ready(
Ok(())
}

/// Receives and decodes a `shutdown` message.
fn recv_shutdown(conn: &mut impl Connection) -> Result<Shutdown, ExecuteError> {
match recv_non_ping(conn, RECV_IDLE_TIMEOUT)? {
Message::Shutdown(sd) => Ok(sd),
other => Err(ExecuteError::UnexpectedMessage {
expected: MessageType::SHUTDOWN,
got: other.msg_type(),
}),
}
}

/// Returns `true` if the target owes us a `channel_ready` message.
///
/// A `channel_ready` is expected when a tracked channel is still at commitment
Expand All @@ -1331,6 +1386,16 @@ fn is_channel_ready_expected(
})
}

/// Returns `true` if the target still owes us a `shutdown` response on the given channel.
fn is_shutdown_expected(
channel_states: &HashMap<ChannelId, ChannelState>,
channel_id: ChannelId,
) -> bool {
channel_states
.get(&channel_id)
.is_some_and(|state| !state.peer_shutdown_received)
}

/// Verifies the counterparty's signature from a `funding_signed` message using
/// the channel state associated with the message's `channel_id`.
///
Expand Down
Loading