Skip to content
Merged
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
207 changes: 82 additions & 125 deletions smite-scenarios/src/executor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -528,15 +528,7 @@ impl<C: Connection, B: BitcoinRpc, R: TargetRpc> Executor<C, B, R> {
instr.operation.input_types()[0],
);
log::debug!("[{:?}] RecvAcceptChannel: waiting", start.elapsed());
let ac: AcceptChannel = recv_bolt(
&mut self.conn,
&mut self.channel_states,
&mut self.revealed_pubkeys,
&mut self.revealed_scids,
&self.context,
&mut self.bitcoin_cli,
RECV_IDLE_TIMEOUT,
)?;
let ac: AcceptChannel = self.recv_bolt(RECV_IDLE_TIMEOUT)?;
log::debug!("[{:?}] RecvAcceptChannel: received", start.elapsed());
AcceptChannelOracle.evaluate(&AcceptChannelContext {
accept_channel: &ac,
Expand All @@ -559,15 +551,7 @@ impl<C: Connection, B: BitcoinRpc, R: TargetRpc> Executor<C, B, R> {
instr.operation.input_types()[0],
);
log::debug!("[{:?}] RecvFundingSigned: waiting", start.elapsed());
let fs: FundingSigned = recv_bolt(
&mut self.conn,
&mut self.channel_states,
&mut self.revealed_pubkeys,
&mut self.revealed_scids,
&self.context,
&mut self.bitcoin_cli,
RECV_IDLE_TIMEOUT,
)?;
let fs: FundingSigned = self.recv_bolt(RECV_IDLE_TIMEOUT)?;
log::debug!("[{:?}] RecvFundingSigned: received", start.elapsed());
FundingSignedOracle.evaluate(&FundingSignedContext {
funding_signed: &fs,
Expand All @@ -581,16 +565,8 @@ impl<C: Connection, B: BitcoinRpc, R: TargetRpc> Executor<C, B, R> {
if is_channel_ready_expected(&self.channel_states, &mut self.bitcoin_cli) {
log::debug!("[{:?}] RecvChannelReady: waiting", start.elapsed());
// `channel_ready` is handled implicitly inside
// `recv_non_ping`, so no further handling is needed here.
recv_bolt::<ChannelReady>(
&mut self.conn,
&mut self.channel_states,
&mut self.revealed_pubkeys,
&mut self.revealed_scids,
&self.context,
&mut self.bitcoin_cli,
RECV_CHANNEL_READY_TIMEOUT,
)?;
// `recv_bolt`, so no further handling is needed here.
self.recv_bolt::<ChannelReady>(RECV_CHANNEL_READY_TIMEOUT)?;
log::debug!("[{:?}] RecvChannelReady: received", start.elapsed());
}
None
Expand Down Expand Up @@ -660,6 +636,82 @@ impl<C: Connection, B: BitcoinRpc, R: TargetRpc> Executor<C, B, R> {

Ok(())
}

/// Receives and decodes the next message, requiring it to be an `M`.
///
/// Implicitly handled messages are recorded by [`Self::handle_implicit`].
/// Those that are not an `M` are skipped, while receiving continues.
///
/// # Errors
///
/// Returns [`ExecuteError::UnexpectedMessage`] if the received message is
/// neither an `M` nor implicitly handled, or any error from
/// [`recv_non_ping`] or [`Self::handle_implicit`].
fn recv_bolt<M: FromMessage>(&mut self, timeout: Duration) -> Result<M, ExecuteError> {
loop {
let msg = recv_non_ping(&mut self.conn, timeout)?;
let was_implicitly_handled = self.handle_implicit(&msg)?;
let got = msg.msg_type();
if let Some(m) = M::from_message(msg) {
return Ok(m);
}
// A non-`M` message is skipped only if it was implicitly recorded,
// and is otherwise unexpected.
if !was_implicitly_handled {
return Err(ExecuteError::UnexpectedMessage {
expected: M::TYPE,
got,
});
}
}
}

/// Handles `msg` if it is one the executor processes implicitly: evaluates
/// its oracle, then records it into the executor's tracking state. See each
/// message's `record_*` function for the state it records. Returns `true`
/// if `msg` was handled, `false` otherwise.
///
/// This currently includes:
/// - `channel_ready`
///
/// # Errors
///
/// Returns [`ExecuteError::Violation`] if the message's oracle reports one.
fn handle_implicit(&mut self, msg: &Message) -> Result<bool, ExecuteError> {
match msg {
Message::ChannelReady(cr) => {
log::debug!("received channel_ready on {}", cr.channel_id);
let channel = self.channel_states.get(&cr.channel_id);
// TODO: Both lookups below call `getrawtransaction`. Returning
// confirmations alongside the block position would save one RPC.
let funding_outpoint = channel
.filter(|c| c.is_funding_outpoint_valid)
.map(|c| c.config.funding_outpoint);
let funding_confirmations = funding_outpoint.map_or(0, |o| {
self.bitcoin_cli.get_transaction_confirmations(o.txid)
});
let funding_scid = funding_outpoint
.and_then(|o| lookup_short_channel_id(&mut self.bitcoin_cli, o));
ChannelReadyOracle.evaluate(&ChannelReadyContext {
channel_ready: cr,
channel,
negotiated_features: &self.context.negotiated_features,
revealed_pubkeys: &self.revealed_pubkeys,
funding_confirmations,
revealed_scids: &self.revealed_scids,
})?;
record_recv_channel_ready(
&mut self.channel_states,
&mut self.revealed_pubkeys,
&mut self.revealed_scids,
funding_scid,
cr,
);
Ok(true)
}
_ => Ok(false),
}
}
}

// -- Variable resolution --
Expand Down Expand Up @@ -1204,39 +1256,17 @@ fn build_channel_update(variables: &[Option<Variable>], inputs: &[usize]) -> Cha
cu
}

/// Returns `true` if a message of type `msg_type` is handled implicitly by the
/// executor: [`recv_non_ping`] evaluates its oracle, then records it into the
/// executor's tracking state and returns it, and explicit receives skip past
/// it. See each message's `record_*` function for the state it records.
///
/// This currently includes:
/// - `channel_ready`
fn is_implicitly_handled(msg_type: MessageType) -> bool {
matches!(msg_type, MessageType::CHANNEL_READY)
}

/// Receives the next message of interest, auto-responding to pings and silently
/// skipping unknown odd-type messages.
///
/// Implicitly handled messages (see [`is_implicitly_handled`]) are recorded
/// before being returned.
///
/// The read is bounded by `timeout`.
///
/// # Errors
///
/// Returns [`ExecuteError::PeerError`] on a received `error`, or any error from
/// receiving, decoding, or recording the message.
/// receiving or decoding the message.
#[allow(clippy::similar_names)] // ping and pong are canonical names
fn recv_non_ping(
conn: &mut impl Connection,
channel_states: &mut HashMap<ChannelId, ChannelState>,
revealed_pubkeys: &mut HashMap<PublicKey, KeyOrigin>,
revealed_scids: &mut HashMap<ShortChannelId, ChannelId>,
context: &ProgramContext,
bitcoin_cli: &mut impl BitcoinRpc,
timeout: Duration,
) -> Result<Message, ExecuteError> {
fn recv_non_ping(conn: &mut impl Connection, timeout: Duration) -> Result<Message, ExecuteError> {
let previous = conn.read_timeout()?;
conn.set_read_timeout(Some(timeout))?;

Expand All @@ -1248,35 +1278,6 @@ fn recv_non_ping(
let pong = Message::Pong(Pong::respond_to(&ping)).encode();
conn.send_message(&pong)?;
}
Message::ChannelReady(ref cr) => {
log::debug!("received channel_ready on {}", cr.channel_id);
let channel = channel_states.get(&cr.channel_id);
// TODO: Both lookups below call `getrawtransaction`. Returning
// confirmations alongside the block position would save one RPC.
let funding_outpoint = channel
.filter(|c| c.is_funding_outpoint_valid)
.map(|c| c.config.funding_outpoint);
let funding_confirmations = funding_outpoint
.map_or(0, |o| bitcoin_cli.get_transaction_confirmations(o.txid));
let funding_scid =
funding_outpoint.and_then(|o| lookup_short_channel_id(bitcoin_cli, o));
ChannelReadyOracle.evaluate(&ChannelReadyContext {
channel_ready: cr,
channel,
negotiated_features: &context.negotiated_features,
revealed_pubkeys,
funding_confirmations,
revealed_scids,
})?;
record_recv_channel_ready(
channel_states,
revealed_pubkeys,
revealed_scids,
funding_scid,
cr,
);
return Ok(msg);
}
Message::Unknown { .. } => {
log::debug!("skipping message {msg}");
}
Expand Down Expand Up @@ -1321,50 +1322,6 @@ fn recv_non_ping(
result
}

/// Receives and decodes the next message, requiring it to be an `M`.
///
/// Implicitly handled messages (see [`is_implicitly_handled`]) are recorded by
/// [`recv_non_ping`]. Those that are not an `M` are skipped, while receiving
/// continues.
///
/// # Errors
///
/// Returns [`ExecuteError::UnexpectedMessage`] if the received message is
/// neither an `M` nor implicitly handled, or any error from [`recv_non_ping`].
fn recv_bolt<M: FromMessage>(
conn: &mut impl Connection,
channel_states: &mut HashMap<ChannelId, ChannelState>,
revealed_pubkeys: &mut HashMap<PublicKey, KeyOrigin>,
revealed_scids: &mut HashMap<ShortChannelId, ChannelId>,
context: &ProgramContext,
bitcoin_cli: &mut impl BitcoinRpc,
timeout: Duration,
) -> Result<M, ExecuteError> {
loop {
let msg = recv_non_ping(
conn,
channel_states,
revealed_pubkeys,
revealed_scids,
context,
bitcoin_cli,
timeout,
)?;
let got = msg.msg_type();
if let Some(m) = M::from_message(msg) {
return Ok(m);
}
// A non-`M` message is skipped only if it was implicitly recorded,
// and is otherwise unexpected.
if !is_implicitly_handled(got) {
return Err(ExecuteError::UnexpectedMessage {
expected: M::TYPE,
got,
});
}
}
}

/// Returns `true` if the target owes us a `channel_ready` message.
///
/// A `channel_ready` is expected when a tracked channel's counterparty
Expand Down
Loading