Skip to content
Open
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
3 changes: 3 additions & 0 deletions smite-ir/src/builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -168,6 +168,9 @@ impl ProgramBuilder {
VariableType::AcceptChannel => {
panic!("cannot generate fresh AcceptChannel: requires protocol interaction")
}
VariableType::FundingTransaction => {
panic!("cannot generate fresh FundingTransaction: requires composed inputs")
}
}
}

Expand Down
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 @@ -82,6 +82,7 @@ fn mutate_operation(op: &mut Operation, rng: &mut impl Rng) -> bool {
// Non-mutable variants. Reaching here means `is_param_mutable` and this
// match have drifted out of sync.
Operation::DerivePoint
| Operation::CreateFundingTransaction
| Operation::LoadTargetPubkeyFromContext
| Operation::LoadChainHashFromContext
| Operation::BuildOpenChannel
Expand Down
22 changes: 20 additions & 2 deletions smite-ir/src/operation.rs
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,14 @@ pub enum Operation {
/// Extract a field from a parsed `accept_channel` response.
/// Input: `AcceptChannel`.
ExtractAcceptChannel(AcceptChannelField),
/// Create a BOLT 3 funding transaction for the channel funding flow.
///
/// Inputs (4):
/// 0: `opener_funding_pubkey` (`Point`)
/// 1: `acceptor_funding_pubkey` (`Point`)
/// 2: `funding_satoshis` (`Amount`)
/// 3: `feerate_per_kw` (`FeeratePerKw`)
CreateFundingTransaction,
Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: should we call it BuildFundingTransaction for consistency with similar operations?


// -- Build: construct a BOLT message from inputs --
/// Build an `open_channel` message (BOLT 2, type 32).
Expand Down Expand Up @@ -532,6 +540,7 @@ impl fmt::Display for Operation {
// Operations with inputs: parens added by Program::Display.
Self::DerivePoint => write!(f, "DerivePoint"),
Self::ExtractAcceptChannel(field) => write!(f, "Extract{field}"),
Self::CreateFundingTransaction => write!(f, "CreateFundingTransaction"),
Self::BuildOpenChannel => write!(f, "BuildOpenChannel"),
Self::SendMessage => write!(f, "SendMessage"),
}
Expand All @@ -557,6 +566,7 @@ impl Operation {
Self::LoadTargetPubkeyFromContext | Self::DerivePoint => Some(VariableType::Point),
Self::LoadChainHashFromContext => Some(VariableType::ChainHash),
Self::ExtractAcceptChannel(field) => Some(field.output_type()),
Self::CreateFundingTransaction => Some(VariableType::FundingTransaction),
Self::BuildOpenChannel => Some(VariableType::Message),
Self::SendMessage | Self::MineBlocks(_) => None,
Self::RecvAcceptChannel => Some(VariableType::AcceptChannel),
Expand Down Expand Up @@ -587,6 +597,12 @@ impl Operation {
Self::DerivePoint => vec![VariableType::PrivateKey],
Self::ExtractAcceptChannel(_) => vec![VariableType::AcceptChannel],
Self::SendMessage => vec![VariableType::Message],
Self::CreateFundingTransaction => vec![
Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: the ordering is inconsistent in some places -- in general CreateFundingTransaction comes right after ExtractAcceptChannel, but not here

VariableType::Point, // opener_funding_pubkey
VariableType::Point, // acceptor_funding_pubkey
VariableType::Amount, // funding_satoshis
VariableType::FeeratePerKw, // feerate_per_kw
],

Self::BuildOpenChannel => vec![
VariableType::ChainHash, // chain_hash
Expand Down Expand Up @@ -639,7 +655,8 @@ impl Operation {
| Self::ExtractAcceptChannel(_)
| Self::BuildOpenChannel
| Self::SendMessage
| Self::MineBlocks(_) => vec![],
| Self::MineBlocks(_)
| Self::CreateFundingTransaction => vec![],

Self::RecvAcceptChannel => AcceptChannelField::ALL
.iter()
Expand Down Expand Up @@ -673,7 +690,8 @@ impl Operation {
| Self::DerivePoint
| Self::BuildOpenChannel
| Self::SendMessage
| Self::RecvAcceptChannel => false,
| Self::RecvAcceptChannel
| Self::CreateFundingTransaction => false,
}
}
}
202 changes: 202 additions & 0 deletions smite-ir/src/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -234,6 +234,22 @@ fn postcard_roundtrip() {
operation: Operation::MineBlocks(6),
inputs: vec![],
},
Instruction {
operation: Operation::LoadPrivateKey(key(2)),
inputs: vec![],
},
Instruction {
operation: Operation::DerivePoint,
inputs: vec![7],
},
Instruction {
operation: Operation::LoadFeeratePerKw(15_000),
inputs: vec![],
},
Instruction {
operation: Operation::CreateFundingTransaction,
inputs: vec![1, 8, 4, 9],
},
],
};

Expand Down Expand Up @@ -298,6 +314,184 @@ fn validate_rejects_mine_blocks_with_wrong_input() {
);
}

#[test]
fn create_funding_transaction_operation() {
let op = Operation::CreateFundingTransaction;
assert_eq!(
op.input_types(),
vec![
VariableType::Point,
VariableType::Point,
VariableType::Amount,
VariableType::FeeratePerKw,
],
);
assert_eq!(op.output_type(), Some(VariableType::FundingTransaction));
assert!(!op.is_param_mutable());
}

fn create_funding_transaction_instructions() -> Vec<Instruction> {
vec![
Instruction {
operation: Operation::LoadPrivateKey(key(1)),
inputs: vec![],
},
Instruction {
operation: Operation::DerivePoint,
inputs: vec![0],
},
Instruction {
operation: Operation::LoadPrivateKey(key(2)),
inputs: vec![],
},
Instruction {
operation: Operation::DerivePoint,
inputs: vec![2],
},
Instruction {
operation: Operation::LoadAmount(10_000_000),
inputs: vec![],
},
Instruction {
operation: Operation::LoadFeeratePerKw(15_000),
inputs: vec![],
},
Instruction {
operation: Operation::CreateFundingTransaction,
inputs: vec![1, 3, 4, 5],
},
]
}

#[test]
fn displays_create_funding_transaction_program() {
let program = Program {
instructions: create_funding_transaction_instructions(),
};
let text = program.to_string();
let lines: Vec<&str> = text.lines().collect();

let z31 = "00".repeat(31);

let expected = vec![
format!("v0 = LoadPrivateKey(0x{z31}01)"),
"v1 = DerivePoint(v0)".into(),
format!("v2 = LoadPrivateKey(0x{z31}02)"),
"v3 = DerivePoint(v2)".into(),
"v4 = LoadAmount(10000000)".into(),
"v5 = LoadFeeratePerKw(15000)".into(),
"v6 = CreateFundingTransaction(v1, v3, v4, v5)".into(),
];
assert_eq!(lines, expected);
}

#[test]
fn validate_accepts_create_funding_transaction() {
let program = Program {
instructions: create_funding_transaction_instructions(),
};
program
.validate()
.expect("CreateFundingTransaction should validate");
}

#[test]
fn validate_rejects_create_funding_transaction_with_wrong_input_count() {
let program = Program {
instructions: vec![Instruction {
operation: Operation::CreateFundingTransaction,
inputs: vec![],
}],
};
assert_eq!(
program.validate(),
Err(ValidateError::WrongInputCount {
instr: 0,
expected: 4,
got: 0,
}),
);
}

#[test]
fn validate_rejects_create_funding_transaction_with_wrong_input_type() {
Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: this test is hard to follow. I think it would be easier to understand if it were split into 4 tests with a helper function

fn program_with_wrong_input(pos: usize, wrong: Operation) -> Program {
let mut inputs = vec![2, 2, 3, 4];
inputs[pos] = 0;

Program {
instructions: vec![
Instruction {
operation: wrong, // wrong-typed value
inputs: vec![],
},
Instruction {
operation: Operation::LoadPrivateKey(key(1)),
inputs: vec![],
},
Instruction {
operation: Operation::DerivePoint,
inputs: vec![1],
},
Instruction {
operation: Operation::LoadAmount(100_000),
inputs: vec![],
},
Instruction {
operation: Operation::LoadFeeratePerKw(1_500),
inputs: vec![],
},
Instruction {
operation: Operation::CreateFundingTransaction,
inputs,
},
],
}
}

// input pos, wrong, expected, got
let cases = [
(
0,
Operation::LoadAmount(100_000),
VariableType::Point,
VariableType::Amount,
),
(
1,
Operation::LoadFeeratePerKw(1_500),
VariableType::Point,
VariableType::FeeratePerKw,
),
(
2,
Operation::LoadPrivateKey(key(1)),
VariableType::Amount,
VariableType::PrivateKey,
),
(
3,
Operation::LoadAmount(100_000),
VariableType::FeeratePerKw,
VariableType::Amount,
),
];

for (input, wrong, expected, got) in cases {
let result = program_with_wrong_input(input, wrong).validate();

assert_eq!(
result,
Err(ValidateError::TypeMismatch {
instr: 5,
input,
expected,
got,
}),
);
}
}

// Ensure AcceptChannelField and AcceptChannelField::ALL stay in sync. The
// exhaustive match in this test will fail to compile if a variant is added
// without updating it, and the assertion will fail if the match is updated
Expand Down Expand Up @@ -685,6 +879,14 @@ fn generate_fresh_accept_channel_panics() {
builder.generate_fresh(VariableType::AcceptChannel, &mut rng);
}

#[test]
#[should_panic(expected = "cannot generate fresh FundingTransaction")]
fn generate_fresh_funding_transaction_panics() {
let mut rng = SmallRng::seed_from_u64(0);
let mut builder = ProgramBuilder::new();
builder.generate_fresh(VariableType::FundingTransaction, &mut rng);
}

#[test]
#[should_panic(expected = "expected 1 inputs, got 0")]
fn append_wrong_input_count_panics() {
Expand Down
6 changes: 5 additions & 1 deletion smite-ir/src/variable.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
//! The serialized program stores data only in [`Operation`] literals.

use bitcoin::secp256k1::PublicKey;
use smite::bolt::{AcceptChannel, ChannelId};
use smite::bolt::{AcceptChannel, ChannelId, FundingTransaction};

const CHAIN_HASH_SIZE: usize = 32;
const PRIVATE_KEY_SIZE: usize = 32;
Expand Down Expand Up @@ -42,6 +42,8 @@ pub enum Variable {
Message(Vec<u8>),
/// Parsed `accept_channel` response.
AcceptChannel(AcceptChannel),
/// Constructed funding transaction with funding output index.
FundingTransaction(FundingTransaction),
}

impl Variable {
Expand All @@ -63,6 +65,7 @@ impl Variable {
Self::Features(_) => VariableType::Features,
Self::Message(_) => VariableType::Message,
Self::AcceptChannel(_) => VariableType::AcceptChannel,
Self::FundingTransaction(_) => VariableType::FundingTransaction,
}
}
}
Expand All @@ -85,4 +88,5 @@ pub enum VariableType {
Features,
Message,
AcceptChannel,
FundingTransaction,
}
Loading