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
158 changes: 1 addition & 157 deletions smite-ir/src/operation.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ use std::fmt::Write;
use bitcoin::{opcodes::all as opcodes, script::Builder, script::PushBytes};
use rand::{Rng, RngExt};
use serde::{Deserialize, Serialize};
pub use smite::bolt::ChannelTypeVariant;
use smite::bolt::ShortChannelId;

use super::VariableType;
Expand Down Expand Up @@ -422,163 +423,6 @@ impl fmt::Display for ShutdownScriptVariant {
}
}

/// A specific BOLT 2 `channel_type` feature-bit combination.
///
/// Each variant corresponds to a channel type accepted by at least one target
/// implementation:
///
/// - `option_static_remotekey` (bit 12)
/// - `option_anchors` (bits 22 and 12)
/// - `zero_fee_commitments` (bit 40)
/// - `option_simple_taproot` (bit 80)
/// - `option_simple_taproot_staging` (bit 180)
/// - `option_script_enforced_lease` (bits 2022, 22, 12)
///
/// Additionally, the following bits can be added to any channel type:
/// - `option_scid_alias` (bit 46)
/// - `option_zeroconf` (bit 50)
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub enum ChannelTypeVariant {
/// bit 12
StaticRemoteKey,
/// bits 12, 46
StaticRemoteKeyScidAlias,
/// bits 12, 50
StaticRemoteKeyZeroConf,
/// bits 12, 46, 50
StaticRemoteKeyScidAliasZeroConf,
/// bits 12, 22
Anchors,
/// bits 12, 22, 46
AnchorsScidAlias,
/// bits 12, 22, 50
AnchorsZeroConf,
/// bits 12, 22, 46, 50
AnchorsScidAliasZeroConf,
/// bit 40
ZeroFeeCommitments,
/// bits 40, 46
ZeroFeeCommitmentsScidAlias,
/// bits 40, 50
ZeroFeeCommitmentsZeroConf,
/// bits 40, 46, 50
ZeroFeeCommitmentsScidAliasZeroConf,
/// bit 80
SimpleTaproot,
/// bits 80, 46
SimpleTaprootScidAlias,
/// bits 80, 50
SimpleTaprootZeroConf,
/// bits 80, 46, 50
SimpleTaprootScidAliasZeroConf,
/// bit 180
SimpleTaprootStaging,
/// bits 180, 46
SimpleTaprootStagingScidAlias,
/// bits 180, 50
SimpleTaprootStagingZeroConf,
/// bits 180, 46, 50
SimpleTaprootStagingScidAliasZeroConf,
/// bits 12, 22, 2022
ScriptEnforcedLease,
/// bits 12, 22, 2022, 46
ScriptEnforcedLeaseScidAlias,
/// bits 12, 22, 2022, 50
ScriptEnforcedLeaseZeroConf,
/// bits 12, 22, 2022, 46, 50
ScriptEnforcedLeaseScidAliasZeroConf,
}

impl ChannelTypeVariant {
/// All variants. Keep in sync with the enum definition.
pub const ALL: &[Self] = &[
Self::StaticRemoteKey,
Self::StaticRemoteKeyScidAlias,
Self::StaticRemoteKeyZeroConf,
Self::StaticRemoteKeyScidAliasZeroConf,
Self::Anchors,
Self::AnchorsScidAlias,
Self::AnchorsZeroConf,
Self::AnchorsScidAliasZeroConf,
Self::ZeroFeeCommitments,
Self::ZeroFeeCommitmentsScidAlias,
Self::ZeroFeeCommitmentsZeroConf,
Self::ZeroFeeCommitmentsScidAliasZeroConf,
Self::SimpleTaproot,
Self::SimpleTaprootScidAlias,
Self::SimpleTaprootZeroConf,
Self::SimpleTaprootScidAliasZeroConf,
Self::SimpleTaprootStaging,
Self::SimpleTaprootStagingScidAlias,
Self::SimpleTaprootStagingZeroConf,
Self::SimpleTaprootStagingScidAliasZeroConf,
Self::ScriptEnforcedLease,
Self::ScriptEnforcedLeaseScidAlias,
Self::ScriptEnforcedLeaseZeroConf,
Self::ScriptEnforcedLeaseScidAliasZeroConf,
];

/// The feature bits (even/required) contained in this channel type.
#[must_use]
pub fn bits(self) -> &'static [usize] {
// BOLT 9 feature bits:
// 12 = option_static_remotekey
// 22 = option_anchors
// 40 = zero_fee_commitments
// 46 = option_scid_alias
// 50 = option_zeroconf
// 80 = option_simple_taproot
// 180 = option_simple_taproot_staging
// 2022 = option_script_enforced_lease
match self {
Self::StaticRemoteKey => &[12],
Self::StaticRemoteKeyScidAlias => &[12, 46],
Self::StaticRemoteKeyZeroConf => &[12, 50],
Self::StaticRemoteKeyScidAliasZeroConf => &[12, 46, 50],
Self::Anchors => &[12, 22],
Self::AnchorsScidAlias => &[12, 22, 46],
Self::AnchorsZeroConf => &[12, 22, 50],
Self::AnchorsScidAliasZeroConf => &[12, 22, 46, 50],
Self::ZeroFeeCommitments => &[40],
Self::ZeroFeeCommitmentsScidAlias => &[40, 46],
Self::ZeroFeeCommitmentsZeroConf => &[40, 50],
Self::ZeroFeeCommitmentsScidAliasZeroConf => &[40, 46, 50],
Self::SimpleTaproot => &[80],
Self::SimpleTaprootScidAlias => &[80, 46],
Self::SimpleTaprootZeroConf => &[80, 50],
Self::SimpleTaprootScidAliasZeroConf => &[80, 46, 50],
Self::SimpleTaprootStaging => &[180],
Self::SimpleTaprootStagingScidAlias => &[180, 46],
Self::SimpleTaprootStagingZeroConf => &[180, 50],
Self::SimpleTaprootStagingScidAliasZeroConf => &[180, 46, 50],
Self::ScriptEnforcedLease => &[12, 22, 2022],
Self::ScriptEnforcedLeaseScidAlias => &[12, 22, 2022, 46],
Self::ScriptEnforcedLeaseZeroConf => &[12, 22, 2022, 50],
Self::ScriptEnforcedLeaseScidAliasZeroConf => &[12, 22, 2022, 46, 50],
}
}

/// Encodes the channel type as a BOLT feature bitmap (big-endian bytes).
#[must_use]
#[allow(clippy::missing_panics_doc)] // bits() is always non-empty
pub fn encode(self) -> Vec<u8> {
let bits = self.bits();
let max_bit = *bits.iter().max().expect("non-empty bits");
let num_bytes = max_bit / 8 + 1;
let mut out = vec![0u8; num_bytes];
for &bit in bits {
out[num_bytes - 1 - bit / 8] |= 1 << (bit % 8);
}
out
}
}

impl fmt::Display for ChannelTypeVariant {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{self:?}")
}
}

/// Fields that can be extracted from an `AcceptChannel` compound variable.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub enum AcceptChannelField {
Expand Down
17 changes: 11 additions & 6 deletions smite-scenarios/src/executor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,8 @@ use bitcoin::{OutPoint, ScriptBuf, Txid};
use smite::bitcoin::{BitcoinCli, TxBlockPosition, Utxo};
use smite::bolt::{
AcceptChannel, AnnouncementSignatures, ChannelAnnouncement, ChannelId, ChannelReady,
ChannelReadyTlvs, ChannelUpdate, FundingCreated, FundingSigned, Message, NodeAnnouncement,
OpenChannel, OpenChannelTlvs, Pong, ShortChannelId, Shutdown, msg_type,
ChannelReadyTlvs, ChannelUpdate, Features, FundingCreated, FundingSigned, Message,
NodeAnnouncement, OpenChannel, OpenChannelTlvs, Pong, ShortChannelId, Shutdown, msg_type,
};
use smite::channel_tx::{
ChannelConfig, ChannelPartyConfig, ChannelState, FundingTransaction, HolderIdentity, Side,
Expand Down Expand Up @@ -133,8 +133,8 @@ pub struct ProgramContext {
pub chain_hash: [u8; 32],
/// Current block height at snapshot time.
pub block_height: u32,
/// Target's advertised feature bits from init message.
pub target_features: Vec<u8>,
/// Features negotiated between the target node and Smite.
pub negotiated_features: Features,
}

/// Abstraction over a Noise-encrypted connection, allowing mock implementations
Expand Down Expand Up @@ -481,6 +481,7 @@ impl<C: Connection, B: BitcoinRpc> Executor<C, B> {
AcceptChannelOracle.evaluate(&AcceptChannelContext {
accept_channel: &ac,
negotiation: self.negotiations.get(&ac.temporary_channel_id),
negotiated_features: &self.context.negotiated_features,
})?;
record_recv_accept_channel(&mut self.negotiations, &ac);
Some(Variable::AcceptChannel(ac))
Expand Down Expand Up @@ -932,7 +933,7 @@ fn build_funding_created(
let config = ChannelConfig {
funding_outpoint,
funding_satoshis: open_channel.funding_satoshis,
channel_type: open_channel.tlvs.channel_type.clone().unwrap_or_default(),
channel_type: Features::from(open_channel.tlvs.channel_type.clone().unwrap_or_default()),
opener,
acceptor,
minimum_depth: accept_channel.minimum_depth,
Expand Down Expand Up @@ -1557,7 +1558,11 @@ mod tests {
target_pubkey: sample_pubkey(1),
chain_hash: [0xcc; 32],
block_height: 800_000,
target_features: vec![],
negotiated_features: Features::from_bits(&[
Features::OPTION_STATIC_REMOTEKEY,
Features::OPTION_ANCHORS,
Features::OPTION_CHANNEL_TYPE,
]),
}
}

Expand Down
69 changes: 29 additions & 40 deletions smite-scenarios/src/scenarios/setup.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

use std::time::Duration;

use smite::bolt::{Init, InitTlvs, Message};
use smite::bolt::{FeatureBit, Features, Init, InitTlvs, Message};
use smite::noise::NoiseConnection;
use smite::scenarios::ScenarioError;

Expand Down Expand Up @@ -30,49 +30,34 @@ pub trait SnapshotSetup<T: Target> {
fn setup(target: &T) -> Result<(NoiseConnection, ProgramContext), ScenarioError>;
}

/// Clears a feature bit from a feature vector.
///
/// Feature vectors are encoded as big-endian byte arrays where bit N lives in
/// byte `features[len - 1 - N/8]` at position `N % 8`.
fn clear_feature_bit(features: &mut [u8], bit: usize) {
let byte_index = features.len().checked_sub(1 + bit / 8);
if let Some(i) = byte_index {
features[i] &= !(1 << (bit % 8));
}
}

/// Gossip-related feature bits (BOLT 9): `gossip_queries` (6/7),
/// `gossip_queries_ex` (10/11). Stripped so the target doesn't send
/// `gossip_timestamp_filter` or other gossip noise during execution.
const GOSSIP_FEATURE_BITS: &[usize] = &[6, 7, 10, 11];

/// Feature bits that force a dual-funded flow when both peers support them:
/// `option_dual_fund` (28/29). Eclair in particular will not allow
/// single-funded flows if either of these feature bits is set, so we strip them
/// when fuzzing the single-funded flow.
const DUAL_FUNDING_FEATURE_BITS: &[usize] = &[28, 29];

/// Peer storage feature bits: `option_provide_storage` (42/43). When enabled,
/// peers may send `peer_storage` and `peer_storage_retrieval` messages at
/// arbitrary times. Disabling these bits eliminates peer storage noise.
const PEER_STORAGE_FEATURE_BITS: &[usize] = &[42, 43];
/// Features stripped from our echoed `init` so the target stays on the single
/// funded flow and doesn't emit unrelated noise:
/// - `gossip_queries` (6/7), `gossip_queries_ex` (10/11): Stripped so the
/// target doesn't send `gossip_timestamp_filter` or other gossip noise during
/// execution.
/// - `option_dual_fund` (28/29): Eclair in particular will not allow
/// single-funded flows if either of these feature bits is set.
/// - `option_provide_storage` (42/43): When enabled, peers may send
/// `peer_storage` and `peer_storage_retrieval` messages at arbitrary times.
const STRIPPED_FEATURES: &[FeatureBit] = &[
Features::GOSSIP_QUERIES,
Features::GOSSIP_QUERIES_EX,
Features::OPTION_DUAL_FUND,
Features::OPTION_PROVIDE_STORAGE,
];

/// Creates an `init` that echoes the received features with bits stripped that
/// would steer the target away from the single-funded `open_channel` flow.
fn init_for_single_funded(received: &Init) -> Init {
let mut globalfeatures = received.globalfeatures.clone();
let mut features = received.features.clone();
for &bit in GOSSIP_FEATURE_BITS
.iter()
.chain(DUAL_FUNDING_FEATURE_BITS)
.chain(PEER_STORAGE_FEATURE_BITS)
{
clear_feature_bit(&mut globalfeatures, bit);
clear_feature_bit(&mut features, bit);
let mut globalfeatures = Features::from(received.globalfeatures.clone());
let mut features = Features::from(received.features.clone());
for &bit in STRIPPED_FEATURES {
globalfeatures.clear_feature(bit);
features.clear_feature(bit);
}
Init {
globalfeatures,
features,
globalfeatures: globalfeatures.into_bytes(),
features: features.into_bytes(),
tlvs: InitTlvs::default(),
}
}
Expand All @@ -88,7 +73,7 @@ impl<T: Target> SnapshotSetup<T> for PostInitSetup {
// Echo features but strip the bits that would take us off the
// single-funded `open_channel` path this setup is built for.
let our_init = init_for_single_funded(&target_init);
conn.send_message(&Message::Init(our_init).encode())?;
conn.send_message(&Message::Init(our_init.clone()).encode())?;

// Drain any remaining post-init noise so the snapshot starts with a
// clean connection.
Expand All @@ -101,7 +86,11 @@ impl<T: Target> SnapshotSetup<T> for PostInitSetup {
// this is the floor. Dynamic per-target queries can replace it
// later.
block_height: u32::try_from(INITIAL_BLOCKS).expect("fits in u32"),
target_features: target_init.features,
// Since we echo the same features the target sent, but strip both
// required and optional bits to exercise only the single funded
// flow and avoid unrelated noise, negotiated features are just the
// features we sent in our init.
negotiated_features: Features::from(our_init.features),
};

Ok((conn, context))
Expand Down
10 changes: 6 additions & 4 deletions smite/src/bolt.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ mod closing_complete;
mod closing_sig;
mod commitment_signed;
mod error;
mod features;
mod funding_created;
mod funding_signed;
mod gossip_timestamp_filter;
Expand Down Expand Up @@ -52,6 +53,7 @@ pub use closing_complete::{ClosingComplete, ClosingTlvs};
pub use closing_sig::ClosingSig;
pub use commitment_signed::{CommitmentSigned, CommitmentSignedTlvs};
pub use error::Error;
pub use features::{FeatureBit, Features};
pub use funding_created::FundingCreated;
pub use funding_signed::FundingSigned;
pub use gossip_timestamp_filter::GossipTimestampFilter;
Expand All @@ -62,7 +64,7 @@ pub use open_channel2::{OpenChannel2, OpenChannel2Tlvs};
pub use ping::Ping;
pub use pong::Pong;
pub use revoke_and_ack::RevokeAndAck;
pub use shutdown::Shutdown;
pub use shutdown::{Shutdown, is_acceptable_shutdown_script, is_standard_shutdown_script};
pub use tlv::{TlvRecord, TlvStream};
pub use tx_abort::TxAbort;
pub use tx_ack_rbf::{TxAckRbf, TxAckRbfTlvs};
Expand All @@ -72,9 +74,9 @@ pub use tx_init_rbf::{TxInitRbf, TxInitRbfTlvs};
pub use tx_remove_input::TxRemoveInput;
pub use tx_remove_output::TxRemoveOutput;
pub use types::{
BigSize, CHANNEL_ID_SIZE, COMPACT_SIGNATURE_SIZE, ChannelId, MAX_MESSAGE_SIZE,
PAYMENT_ONION_PACKET_SIZE, PER_COMMITMENT_SECRET_SIZE, PUBLIC_KEY_SIZE, SHA256_HASH_SIZE,
SHORT_CHANNEL_ID_SIZE, ShortChannelId, TXID_SIZE, Tu32, Tu64,
BigSize, CHANNEL_ID_SIZE, COMPACT_SIGNATURE_SIZE, ChannelId, ChannelTypeVariant,
MAX_MESSAGE_SIZE, PAYMENT_ONION_PACKET_SIZE, PER_COMMITMENT_SECRET_SIZE, PUBLIC_KEY_SIZE,
SHA256_HASH_SIZE, SHORT_CHANNEL_ID_SIZE, ShortChannelId, TXID_SIZE, Tu32, Tu64,
};
pub use update_add_htlc::{UpdateAddHtlc, UpdateAddHtlcTlvs};
pub use update_fail_htlc::{UpdateFailHtlc, UpdateFailHtlcTlvs};
Expand Down
Loading