diff --git a/smite-ir/src/mutators/operation_param.rs b/smite-ir/src/mutators/operation_param.rs
index b5152f6c..da56a163 100644
--- a/smite-ir/src/mutators/operation_param.rs
+++ b/smite-ir/src/mutators/operation_param.rs
@@ -122,6 +122,7 @@ fn mutate_operation(op: &mut Operation, rng: &mut impl Rng) -> bool {
| Operation::RecvAcceptChannel
| Operation::RecvFundingSigned
| Operation::RecvChannelReady
+ | Operation::Reconnect
| Operation::BroadcastTransaction
| Operation::LookupShortChannelId => {
unreachable!("is_param_mutable returned true for {op:?}")
diff --git a/smite-ir/src/operation.rs b/smite-ir/src/operation.rs
index 22a17899..e7470237 100644
--- a/smite-ir/src/operation.rs
+++ b/smite-ir/src/operation.rs
@@ -228,6 +228,9 @@ pub enum Operation {
/// point unknown) and its funding transaction has enough confirmations for
/// the target to have sent `channel_ready`.
RecvChannelReady,
+ /// Close the connection and establish a fresh one to the target, re-running
+ /// the Noise handshake and exchanging `init`.
+ Reconnect,
/// Mines the given number of blocks on the Bitcoin network.
MineBlocks(u8),
/// Sign wallet inputs of the transaction and broadcast it via `bitcoin-cli`.
@@ -556,6 +559,7 @@ impl fmt::Display for Operation {
Self::RecvAcceptChannel => write!(f, "RecvAcceptChannel"),
Self::RecvFundingSigned => write!(f, "RecvFundingSigned"),
Self::RecvChannelReady => write!(f, "RecvChannelReady()"),
+ Self::Reconnect => write!(f, "Reconnect()"),
Self::MineBlocks(v) => write!(f, "MineBlocks({v})"),
Self::BroadcastTransaction => write!(f, "BroadcastTransaction"),
Self::LookupShortChannelId => write!(f, "LookupShortChannelId"),
@@ -595,6 +599,7 @@ impl Operation {
Self::SendMessage
| Self::SendChannelReady { .. }
| Self::RecvChannelReady
+ | Self::Reconnect
| Self::MineBlocks(_)
| Self::BroadcastTransaction => None,
Self::SendOpenChannel => Some(VariableType::SentOpenChannel),
@@ -626,6 +631,7 @@ impl Operation {
| Self::LoadTargetPubkeyFromContext
| Self::LoadChainHashFromContext
| Self::RecvChannelReady
+ | Self::Reconnect
| Self::MineBlocks(_) => vec![],
Self::DerivePoint => vec![VariableType::PrivateKey],
@@ -763,6 +769,7 @@ impl Operation {
| Self::SendShutdown
| Self::RecvFundingSigned
| Self::RecvChannelReady
+ | Self::Reconnect
| Self::MineBlocks(_)
| Self::BroadcastTransaction
| Self::LookupShortChannelId => vec![],
@@ -812,6 +819,7 @@ impl Operation {
| Self::RecvAcceptChannel
| Self::RecvFundingSigned
| Self::RecvChannelReady
+ | Self::Reconnect
| Self::MineBlocks(_)
| Self::BroadcastTransaction => true,
}
@@ -858,14 +866,16 @@ impl Operation {
// contents change as transactions are created and broadcast.
// `SendFundingCreated` builds its message from the recorded
// negotiation and channel state. The `Recv` operations read
- // whatever the target sends us. `MineBlocks` also mines whatever
- // the private mempool holds, `BroadcastTransaction` dedups against
- // it, and `LookupShortChannelId` reads chain state.
+ // whatever the target sends us. `Reconnect` re-establishes the
+ // connection and depends on live transport state. `MineBlocks` also
+ // mines whatever the private mempool holds, `BroadcastTransaction`
+ // dedups against it, and `LookupShortChannelId` reads chain state.
Self::CreateFundingTransaction
| Self::SendFundingCreated
| Self::RecvAcceptChannel
| Self::RecvFundingSigned
| Self::RecvChannelReady
+ | Self::Reconnect
| Self::MineBlocks(_)
| Self::BroadcastTransaction
| Self::LookupShortChannelId => false,
@@ -919,6 +929,7 @@ impl Operation {
| Self::RecvAcceptChannel
| Self::RecvFundingSigned
| Self::RecvChannelReady
+ | Self::Reconnect
| Self::BroadcastTransaction
| Self::LookupShortChannelId => false,
}
diff --git a/smite-ir/src/tests.rs b/smite-ir/src/tests.rs
index 53fdd3cf..b6a34b98 100644
--- a/smite-ir/src/tests.rs
+++ b/smite-ir/src/tests.rs
@@ -678,6 +678,18 @@ fn postcard_roundtrip() {
assert_eq!(program, decoded);
}
+#[test]
+fn reconnect_operation() {
+ let op = Operation::Reconnect;
+ assert_eq!(op.input_types(), vec![]);
+ assert_eq!(op.output_type(), None);
+ assert!(!op.is_param_mutable());
+ assert!(op.has_side_effects());
+ assert!(!op.depends_only_on_inputs());
+ assert!(!op.is_pure());
+ assert_eq!(op.to_string(), "Reconnect()");
+}
+
#[test]
fn mine_blocks_operation() {
let op = Operation::MineBlocks(8);
diff --git a/smite-scenarios/src/executor.rs b/smite-scenarios/src/executor.rs
index bf6f6b90..7413354c 100644
--- a/smite-scenarios/src/executor.rs
+++ b/smite-scenarios/src/executor.rs
@@ -9,9 +9,9 @@ use bitcoin::{OutPoint, ScriptBuf, Txid};
use smite::bitcoin::{BitcoinCli, TxBlockPosition, Utxo};
use smite::bolt::{
AcceptChannel, AnnouncementSignatures, ChannelAnnouncement, ChannelId, ChannelReady,
- ChannelReadyTlvs, ChannelUpdate, Features, FromMessage, FundingCreated, FundingSigned, Message,
- MessageType, NodeAnnouncement, OpenChannel, OpenChannelTlvs, Pong, ShortChannelId, Shutdown,
- TemporaryChannelId,
+ ChannelReadyTlvs, ChannelUpdate, Features, FromMessage, FundingCreated, FundingSigned, Init,
+ InitTlvs, Message, MessageType, NodeAnnouncement, OpenChannel, OpenChannelTlvs, Pong,
+ ShortChannelId, Shutdown, TemporaryChannelId,
};
use smite::channel_tx::{
ChannelConfig, ChannelPartyConfig, ChannelState, FundingTransaction, HolderIdentity, Side,
@@ -175,6 +175,14 @@ pub trait Connection {
///
/// Returns an error if the timeout cannot be read.
fn read_timeout(&self) -> Result