extern crate scopeguard;
use core::cmp::{max, min};
use bitcoin::secp256k1::ecdsa::Signature;
use bitcoin::secp256k1::{PublicKey, Secp256k1, SecretKey};
use bitcoin::{
self, BlockHeader, EcdsaSighashType, FilterHeader, Network, OutPoint, Script, Sighash,
Transaction,
};
use core::time::Duration;
use lightning::chain::keysinterface::InMemorySigner;
use lightning::ln::chan_utils::{ClosingTransaction, HTLCOutputInCommitment, TxCreationKeys};
use lightning::ln::PaymentHash;
use log::{debug, error};
use serde_derive::{Deserialize, Serialize};
use txoo::proof::{TxoProof, VerifyError};
use crate::channel::{ChannelBalance, ChannelId, ChannelSetup, ChannelSlot};
use crate::invoice::{Invoice, InvoiceAttributes};
use crate::policy::{Policy, MAX_CLOCK_SKEW, MIN_INVOICE_EXPIRY};
use crate::prelude::*;
use crate::sync::Arc;
use crate::tx::tx::{CommitmentInfo, CommitmentInfo2, HTLCInfo2, PreimageMap};
use crate::wallet::Wallet;
use super::error::ValidationError;
pub trait Validator {
fn validate_ready_channel(
&self,
wallet: &Wallet,
setup: &ChannelSetup,
holder_shutdown_key_path: &[u32],
) -> Result<(), ValidationError>;
fn validate_channel_value(&self, setup: &ChannelSetup) -> Result<(), ValidationError>;
fn validate_onchain_tx(
&self,
wallet: &Wallet,
channels: Vec<Option<Arc<Mutex<ChannelSlot>>>>,
tx: &Transaction,
input_txs: &[&Transaction],
values_sat: &[u64],
opaths: &[Vec<u32>],
weight_lower_bound: usize,
) -> Result<u64, ValidationError>;
fn decode_commitment_tx(
&self,
keys: &InMemorySigner,
setup: &ChannelSetup,
is_counterparty: bool,
tx: &Transaction,
output_witscripts: &[Vec<u8>],
) -> Result<CommitmentInfo, ValidationError>;
fn validate_counterparty_commitment_tx(
&self,
estate: &EnforcementState,
commit_num: u64,
commitment_point: &PublicKey,
setup: &ChannelSetup,
cstate: &ChainState,
info2: &CommitmentInfo2,
) -> Result<(), ValidationError>;
fn validate_holder_commitment_tx(
&self,
estate: &EnforcementState,
commit_num: u64,
commitment_point: &PublicKey,
setup: &ChannelSetup,
cstate: &ChainState,
info2: &CommitmentInfo2,
) -> Result<(), ValidationError>;
fn validate_counterparty_revocation(
&self,
state: &EnforcementState,
revoke_num: u64,
commitment_secret: &SecretKey,
) -> Result<(), ValidationError>;
fn decode_and_validate_htlc_tx(
&self,
is_counterparty: bool,
setup: &ChannelSetup,
txkeys: &TxCreationKeys,
tx: &Transaction,
redeemscript: &Script,
htlc_amount_sat: u64,
output_witscript: &Script,
) -> Result<(u32, HTLCOutputInCommitment, Sighash, EcdsaSighashType), ValidationError>;
fn validate_htlc_tx(
&self,
setup: &ChannelSetup,
cstate: &ChainState,
is_counterparty: bool,
htlc: &HTLCOutputInCommitment,
feerate_per_kw: u32,
) -> Result<(), ValidationError>;
fn decode_and_validate_mutual_close_tx(
&self,
wallet: &Wallet,
setup: &ChannelSetup,
state: &EnforcementState,
tx: &Transaction,
opaths: &[Vec<u32>],
) -> Result<ClosingTransaction, ValidationError>;
fn validate_mutual_close_tx(
&self,
wallet: &Wallet,
setup: &ChannelSetup,
state: &EnforcementState,
to_holder_value_sat: u64,
to_counterparty_value_sat: u64,
holder_shutdown_script: &Option<Script>,
counterparty_shutdown_script: &Option<Script>,
holder_wallet_path_hint: &[u32],
) -> Result<(), ValidationError>;
fn validate_delayed_sweep(
&self,
wallet: &Wallet,
setup: &ChannelSetup,
cstate: &ChainState,
tx: &Transaction,
input: usize,
amount_sat: u64,
key_path: &[u32],
) -> Result<(), ValidationError>;
fn validate_counterparty_htlc_sweep(
&self,
wallet: &Wallet,
setup: &ChannelSetup,
cstate: &ChainState,
tx: &Transaction,
redeemscript: &Script,
input: usize,
amount_sat: u64,
key_path: &[u32],
) -> Result<(), ValidationError>;
fn validate_justice_sweep(
&self,
wallet: &Wallet,
setup: &ChannelSetup,
cstate: &ChainState,
tx: &Transaction,
input: usize,
amount_sat: u64,
key_path: &[u32],
) -> Result<(), ValidationError>;
fn validate_payment_balance(
&self,
incoming_msat: u64,
outgoing_msat: u64,
invoiced_amount_msat: Option<u64>,
) -> Result<(), ValidationError>;
fn enforce_balance(&self) -> bool {
false
}
fn minimum_initial_balance(&self, holder_value_msat: u64) -> u64;
fn policy(&self) -> Box<&dyn Policy>;
fn set_next_holder_commit_num(
&self,
estate: &mut EnforcementState,
num: u64,
current_commitment_info: CommitmentInfo2,
counterparty_signatures: CommitmentSignatures,
) -> Result<(), ValidationError> {
let current = estate.next_holder_commit_num;
if num != current && num != current + 1 {
policy_err!(
self,
"policy-revoke-new-commitment-signed",
"invalid progression: {} to {}",
current,
num
);
}
estate.set_next_holder_commit_num(num, current_commitment_info, counterparty_signatures);
Ok(())
}
fn get_current_holder_commitment_info(
&self,
estate: &mut EnforcementState,
commitment_number: u64,
) -> Result<CommitmentInfo2, ValidationError> {
if commitment_number + 1 != estate.next_holder_commit_num {
policy_err!(
self,
"policy-other",
"invalid next holder commitment number: {} != {}",
commitment_number + 1,
estate.next_holder_commit_num
);
}
Ok(estate.get_current_holder_commitment_info())
}
fn set_next_counterparty_commit_num(
&self,
estate: &mut EnforcementState,
num: u64,
current_point: PublicKey,
current_commitment_info: CommitmentInfo2,
) -> Result<(), ValidationError> {
if num == 0 {
policy_err!(self, "policy-commitment-previous-revoked", "can't set next to 0");
}
let delta = if num == 1 { 1 } else { 2 };
if num < estate.next_counterparty_revoke_num + delta {
policy_err!(
self,
"policy-commitment-previous-revoked",
"{} too small relative to next_counterparty_revoke_num {}",
num,
estate.next_counterparty_revoke_num
);
}
if num > estate.next_counterparty_revoke_num + 2 {
policy_err!(
self,
"policy-commitment-previous-revoked",
"{} too large relative to next_counterparty_revoke_num {}",
num,
estate.next_counterparty_revoke_num
);
}
let current = estate.next_counterparty_commit_num;
if num == current {
assert!(
estate.current_counterparty_point.is_some(),
"retry {}: current_counterparty_point not set, this shouldn't be possible",
num
);
if current_point != estate.current_counterparty_point.unwrap() {
debug!(
"current_point {} != prior {}",
current_point,
estate.current_counterparty_point.unwrap()
);
policy_err!(
self,
"policy-commitment-retry-same",
"retry {}: point different than prior",
num
);
}
} else if num == current + 1 {
} else {
policy_err!(
self,
"policy-commitment-previous-revoked",
"invalid progression: {} to {}",
current,
num
);
}
estate.set_next_counterparty_commit_num(num, current_point, current_commitment_info);
Ok(())
}
fn set_next_counterparty_revoke_num(
&self,
estate: &mut EnforcementState,
num: u64,
) -> Result<(), ValidationError> {
if num == 0 {
policy_err!(self, "policy-other", "can't set next to 0");
}
if num + 2 < estate.next_counterparty_commit_num {
policy_err!(
self,
"policy-commitment-previous-revoked",
"{} too small relative to next_counterparty_commit_num {}",
num,
estate.next_counterparty_commit_num
);
}
if num + 1 > estate.next_counterparty_commit_num {
policy_err!(
self,
"policy-commitment-previous-revoked",
"{} too large relative to next_counterparty_commit_num {}",
num,
estate.next_counterparty_commit_num
);
}
let current = estate.next_counterparty_revoke_num;
if num != current && num != current + 1 {
policy_err!(
self,
"policy-commitment-previous-revoked",
"invalid progression: {} to {}",
current,
num
);
}
estate.set_next_counterparty_revoke_num(num);
debug!("next_counterparty_revoke_num {} -> {}", current, num);
Ok(())
}
fn validate_block(
&self,
proof: &TxoProof,
height: u32,
header: &BlockHeader,
prev_filter_header: &FilterHeader,
outpoint_watches: &[OutPoint],
) -> Result<(), ValidationError> {
let secp = Secp256k1::new();
let result = proof.verify(height, header, prev_filter_header, outpoint_watches, &secp);
match result {
Ok(()) => {}
Err(VerifyError::InvalidAttestation) => {
for (pubkey, attestation) in &proof.attestations {
error!(
"invalid attestation for oracle {} at height {} block hash {} - {:?}",
pubkey,
height,
header.block_hash(),
&attestation.attestation
);
}
policy_err!(self, "policy-chain-validated", "invalid attestation");
}
Err(_) => {
policy_err!(self, "policy-chain-validated", "invalid proof {:?}", result);
}
}
Ok(())
}
fn validate_invoice(&self, invoice: &Invoice, now: Duration) -> Result<(), ValidationError> {
if now + MAX_CLOCK_SKEW < invoice.duration_since_epoch() {
policy_err!(
self,
"policy-invoice-not-expired",
"invoice is not yet valid ({} < {})",
now.as_secs(),
invoice.duration_since_epoch().as_secs()
);
}
if now + MIN_INVOICE_EXPIRY
> (invoice.duration_since_epoch() + invoice.expiry_duration()) + MAX_CLOCK_SKEW
{
policy_err!(
self,
"policy-invoice-not-expired",
"invoice is expired ({} + {} (buffer) > {})",
now.as_secs(),
MIN_INVOICE_EXPIRY.as_secs(),
(invoice.duration_since_epoch() + invoice.expiry_duration()).as_secs()
);
}
Ok(())
}
}
#[derive(Debug)]
pub struct ChainState {
pub current_height: u32,
pub funding_depth: u32,
pub funding_double_spent_depth: u32,
pub closing_depth: u32,
}
pub trait ValidatorFactory: Send + Sync {
fn make_validator(
&self,
network: Network,
node_id: PublicKey,
channel_id: Option<ChannelId>,
) -> Arc<dyn Validator>;
fn policy(&self, network: Network) -> Box<dyn Policy>;
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct CommitmentSignatures(pub Signature, pub Vec<Signature>);
#[allow(missing_docs)]
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct EnforcementState {
pub next_holder_commit_num: u64,
pub next_counterparty_commit_num: u64,
pub next_counterparty_revoke_num: u64,
pub current_counterparty_point: Option<PublicKey>, pub previous_counterparty_point: Option<PublicKey>, pub current_holder_commit_info: Option<CommitmentInfo2>,
pub current_counterparty_signatures: Option<CommitmentSignatures>,
pub current_counterparty_commit_info: Option<CommitmentInfo2>,
pub previous_counterparty_commit_info: Option<CommitmentInfo2>,
pub channel_closed: bool,
pub initial_holder_value: u64,
}
impl EnforcementState {
pub fn new(initial_holder_value: u64) -> EnforcementState {
EnforcementState {
next_holder_commit_num: 0,
next_counterparty_commit_num: 0,
next_counterparty_revoke_num: 0,
current_counterparty_point: None,
previous_counterparty_point: None,
current_holder_commit_info: None,
current_counterparty_signatures: None,
current_counterparty_commit_info: None,
previous_counterparty_commit_info: None,
channel_closed: false,
initial_holder_value,
}
}
pub fn minimum_to_holder_value(&self, epsilon_sat: u64) -> Option<u64> {
if let Some(hinfo) = &self.current_holder_commit_info {
if let Some(cinfo) = &self.current_counterparty_commit_info {
let hval = hinfo.to_broadcaster_value_sat;
let cval = cinfo.to_countersigner_value_sat;
debug!("min to_holder: hval={}, cval={}", hval, cval);
if hval > cval {
if hval - cval <= epsilon_sat {
return Some(cval);
}
} else
{
if cval - hval <= epsilon_sat {
return Some(hval);
}
}
}
}
None
}
pub fn minimum_to_counterparty_value(&self, epsilon_sat: u64) -> Option<u64> {
if let Some(hinfo) = &self.current_holder_commit_info {
if let Some(cinfo) = &self.current_counterparty_commit_info {
let hval = hinfo.to_countersigner_value_sat;
let cval = cinfo.to_broadcaster_value_sat;
debug!("min to_cparty: hval={}, cval={}", hval, cval);
if hval > cval {
if hval - cval <= epsilon_sat {
return Some(cval);
}
} else
{
if cval - hval <= epsilon_sat {
return Some(hval);
}
}
}
}
None
}
pub fn set_next_holder_commit_num(
&mut self,
num: u64,
current_commitment_info: CommitmentInfo2,
counterparty_signatures: CommitmentSignatures,
) {
let current = self.next_holder_commit_num;
debug!("next_holder_commit_num {} -> {}", current, num);
self.next_holder_commit_num = num;
self.current_holder_commit_info = Some(current_commitment_info);
self.current_counterparty_signatures = Some(counterparty_signatures);
}
pub fn get_current_holder_commitment_info(&self) -> CommitmentInfo2 {
self.current_holder_commit_info.as_ref().unwrap().clone()
}
pub fn set_next_counterparty_commit_num(
&mut self,
num: u64,
current_point: PublicKey,
current_commitment_info: CommitmentInfo2,
) {
assert!(num > 0);
let current = self.next_counterparty_commit_num;
if num == current + 1 {
self.previous_counterparty_point = self.current_counterparty_point;
self.previous_counterparty_commit_info = self.current_counterparty_commit_info.take();
} else if num > current + 1 || num < current {
self.previous_counterparty_point = None;
self.previous_counterparty_commit_info = None;
}
if num >= current + 1 {
self.current_counterparty_point = Some(current_point);
self.current_counterparty_commit_info = Some(current_commitment_info);
}
self.next_counterparty_commit_num = num;
debug!("next_counterparty_commit_num {} -> {} current {}", current, num, current_point);
}
pub fn get_previous_counterparty_point(&self, num: u64) -> Option<PublicKey> {
if num + 1 == self.next_counterparty_commit_num {
self.current_counterparty_point
} else if num + 2 == self.next_counterparty_commit_num {
self.previous_counterparty_point
} else {
None
}
}
pub fn get_previous_counterparty_commit_info(&self, num: u64) -> Option<CommitmentInfo2> {
if num + 1 == self.next_counterparty_commit_num {
self.current_counterparty_commit_info.clone()
} else if num + 2 == self.next_counterparty_commit_num {
self.previous_counterparty_commit_info.clone()
} else {
None
}
}
pub fn set_next_counterparty_revoke_num(&mut self, num: u64) {
assert_ne!(num, 0);
let current = self.next_counterparty_revoke_num;
if num + 1 >= self.next_counterparty_commit_num {
self.previous_counterparty_commit_info = None;
}
self.next_counterparty_revoke_num = num;
debug!("next_counterparty_revoke_num {} -> {}", current, num);
}
#[allow(missing_docs)]
#[cfg(any(test, feature = "test_utils"))]
pub fn set_next_holder_commit_num_for_testing(&mut self, num: u64) {
debug!(
"set_next_holder_commit_num_for_testing: {} -> {}",
self.next_holder_commit_num, num
);
self.next_holder_commit_num = num;
}
#[allow(missing_docs)]
#[cfg(any(test, feature = "test_utils"))]
pub fn set_next_counterparty_commit_num_for_testing(
&mut self,
num: u64,
current_point: PublicKey,
) {
debug!(
"set_next_counterparty_commit_num_for_testing: {} -> {}",
self.next_counterparty_commit_num, num
);
self.previous_counterparty_point = self.current_counterparty_point;
self.current_counterparty_point = Some(current_point);
self.next_counterparty_commit_num = num;
}
#[allow(missing_docs)]
#[cfg(any(test, feature = "test_utils"))]
pub fn set_next_counterparty_revoke_num_for_testing(&mut self, num: u64) {
debug!(
"set_next_counterparty_revoke_num_for_testing: {} -> {}",
self.next_counterparty_revoke_num, num
);
self.next_counterparty_revoke_num = num;
}
pub fn payments_summary(
&self,
new_holder_tx: Option<&CommitmentInfo2>,
new_counterparty_tx: Option<&CommitmentInfo2>,
) -> Map<PaymentHash, u64> {
let holder_offered =
new_holder_tx.or(self.current_holder_commit_info.as_ref()).map(|h| &h.offered_htlcs);
let counterparty_received = new_counterparty_tx
.or(self.current_counterparty_commit_info.as_ref())
.map(|c| &c.received_htlcs);
let holder_summary =
holder_offered.map(|h| Self::summarize_payments(h)).unwrap_or_else(|| Map::new());
let counterparty_summary = counterparty_received
.map(|h| Self::summarize_payments(h))
.unwrap_or_else(|| Map::new());
let mut summary = holder_summary;
for (k, v) in counterparty_summary {
summary.entry(k).and_modify(|e| *e = max(*e, v)).or_insert(v);
}
summary
}
pub fn incoming_payments_summary(
&self,
new_holder_tx: Option<&CommitmentInfo2>,
new_counterparty_tx: Option<&CommitmentInfo2>,
) -> Map<PaymentHash, u64> {
let holder_received =
new_holder_tx.or(self.current_holder_commit_info.as_ref()).map(|h| &h.received_htlcs);
let counterparty_offered = new_counterparty_tx
.or(self.current_counterparty_commit_info.as_ref())
.map(|c| &c.offered_htlcs);
let holder_summary =
holder_received.map(|h| Self::summarize_payments(h)).unwrap_or_else(|| Map::new());
let counterparty_summary =
counterparty_offered.map(|h| Self::summarize_payments(h)).unwrap_or_else(|| Map::new());
let mut summary = holder_summary;
summary.retain(|k, _| counterparty_summary.contains_key(k));
for (k, v) in counterparty_summary {
summary.entry(k).and_modify(|e| *e = min(*e, v));
}
summary
}
fn summarize_payments(htlcs: &[HTLCInfo2]) -> Map<PaymentHash, u64> {
let mut summary = Map::new();
for h in htlcs {
summary.entry(h.payment_hash).and_modify(|e| *e += h.value_sat).or_insert(h.value_sat);
}
summary
}
pub fn claimable_balances<T: PreimageMap>(
&self,
preimage_map: &T,
new_holder_tx: Option<&CommitmentInfo2>,
new_counterparty_tx: Option<&CommitmentInfo2>,
channel_setup: &ChannelSetup,
) -> BalanceDelta {
assert!(
new_holder_tx.is_some() || new_counterparty_tx.is_some(),
"must have at least one new tx"
);
assert!(
new_holder_tx.is_none() || new_counterparty_tx.is_none(),
"must have at most one new tx"
);
let cur_holder_bal = self.current_holder_commit_info.as_ref().map(|tx| {
tx.claimable_balance(
preimage_map,
channel_setup.is_outbound,
channel_setup.channel_value_sat,
)
});
let cur_cp_bal = self.current_counterparty_commit_info.as_ref().map(|tx| {
tx.claimable_balance(
preimage_map,
channel_setup.is_outbound,
channel_setup.channel_value_sat,
)
});
let cur_bal_opt = min_opt(cur_holder_bal, cur_cp_bal);
let new_holder_bal = new_holder_tx.or(self.current_holder_commit_info.as_ref()).map(|tx| {
tx.claimable_balance(
preimage_map,
channel_setup.is_outbound,
channel_setup.channel_value_sat,
)
});
let new_cp_bal =
new_counterparty_tx.or(self.current_counterparty_commit_info.as_ref()).map(|tx| {
tx.claimable_balance(
preimage_map,
channel_setup.is_outbound,
channel_setup.channel_value_sat,
)
});
let new_bal =
min_opt(new_holder_bal, new_cp_bal).expect("already checked that we have a new tx");
let cur_bal = cur_bal_opt.unwrap_or_else(|| self.initial_holder_value);
log::debug!(
"balance {} -> {} --- cur h {} c {} new h {} c {}",
cur_bal,
new_bal,
self.current_holder_commit_info.is_some(),
self.current_counterparty_commit_info.is_some(),
new_holder_tx.is_some(),
new_counterparty_tx.is_some()
);
BalanceDelta(cur_bal, new_bal)
}
pub fn balance<T: PreimageMap + core::fmt::Debug>(
&self,
preimage_map: &T,
channel_setup: &ChannelSetup,
) -> ChannelBalance {
debug!("{:#?}", preimage_map);
if self.current_holder_commit_info.is_none()
|| self.current_counterparty_commit_info.is_none()
{
return ChannelBalance::zero();
}
let cur_holder_bal = self.current_holder_commit_info.as_ref().unwrap().claimable_balance(
preimage_map,
channel_setup.is_outbound,
channel_setup.channel_value_sat,
);
let cur_cp_bal = self.current_counterparty_commit_info.as_ref().unwrap().claimable_balance(
preimage_map,
channel_setup.is_outbound,
channel_setup.channel_value_sat,
);
let (cur_bal, received_htlc, offered_htlc, received_htlc_count, offered_htlc_count) =
if cur_holder_bal < cur_cp_bal {
let (received_htlc, offered_htlc, received_count, offered_count) =
self.current_holder_commit_info.as_ref().unwrap().htlc_balance();
(cur_holder_bal, received_htlc, offered_htlc, received_count, offered_count)
} else {
let (received_htlc, offered_htlc, received_count, offered_count) =
self.current_counterparty_commit_info.as_ref().unwrap().htlc_balance();
(cur_cp_bal, received_htlc, offered_htlc, received_count, offered_count)
};
let (claimable, sweeping) = if self.channel_closed { (0, cur_bal) } else { (cur_bal, 0) };
let balance = ChannelBalance {
claimable,
received_htlc,
offered_htlc,
sweeping,
channel_count: 1,
received_htlc_count,
offered_htlc_count,
};
balance
}
}
pub struct BalanceDelta(pub u64, pub u64);
impl Default for BalanceDelta {
fn default() -> Self {
BalanceDelta(0, 0)
}
}
fn min_opt(a_opt: Option<u64>, b_opt: Option<u64>) -> Option<u64> {
if let Some(a) = a_opt {
if let Some(b) = b_opt {
Some(a.min(b))
} else {
a_opt
}
} else {
b_opt
}
}