lightning_signer/policy/
onchain_validator.rs

1use bitcoin::bip32::DerivationPath;
2use bitcoin::secp256k1::{PublicKey, SecretKey};
3use bitcoin::sighash::{EcdsaSighashType, SegwitV0Sighash};
4use bitcoin::{Network, ScriptBuf, Transaction};
5use lightning::ln::chan_utils::{ClosingTransaction, HTLCOutputInCommitment, TxCreationKeys};
6use lightning::sign::InMemorySigner;
7
8use super::filter::PolicyFilter;
9use super::simple_validator::SimpleValidatorFactory;
10use super::validator::EnforcementState;
11use super::validator::{ChainState, Validator, ValidatorFactory};
12use super::{policy_error_with_filter, Policy, DEFAULT_FEE_VELOCITY_CONTROL};
13use crate::channel::{ChannelId, ChannelSetup, ChannelSlot};
14use crate::prelude::*;
15use crate::tx::tx::{CommitmentInfo, CommitmentInfo2};
16use crate::util::velocity::VelocityControlSpec;
17use crate::wallet::Wallet;
18
19use crate::policy::temporary_policy_error_with_filter;
20use log::*;
21
22extern crate scopeguard;
23
24use super::error::ValidationError;
25
26/// A factory for OnchainValidator
27pub struct OnchainValidatorFactory {
28    inner_factory: SimpleValidatorFactory,
29}
30
31impl OnchainValidatorFactory {
32    /// Create a new onchain validator factory with default policy
33    pub fn new() -> Self {
34        Self { inner_factory: SimpleValidatorFactory::new() }
35    }
36
37    /// Create a new onchain validator factory with specific inner simple factory
38    pub fn new_with_simple_factory(inner_factory: SimpleValidatorFactory) -> Self {
39        Self { inner_factory }
40    }
41}
42
43impl ValidatorFactory for OnchainValidatorFactory {
44    fn make_validator(
45        &self,
46        network: Network,
47        node_id: PublicKey,
48        channel_id: Option<ChannelId>,
49    ) -> Arc<dyn Validator> {
50        // copy the filter from the inner
51        let filter =
52            self.inner_factory.policy.as_ref().map(|p| p.filter.clone()).unwrap_or_default();
53        let validator = OnchainValidator {
54            inner: self.inner_factory.make_validator(network, node_id, channel_id),
55            policy: make_onchain_policy(network, filter),
56        };
57        Arc::new(validator)
58    }
59
60    fn policy(&self, network: Network) -> Box<dyn Policy> {
61        self.inner_factory.policy(network)
62    }
63}
64
65/// An on-chain validator, subsumes the policy checks of SimpleValidator
66pub struct OnchainValidator {
67    inner: Arc<dyn Validator>,
68    policy: OnchainPolicy,
69}
70
71/// Policy to configure the onchain validator
72pub struct OnchainPolicy {
73    /// Policy filter
74    pub filter: PolicyFilter,
75    /// Minimum funding confirmations
76    pub min_funding_depth: u16,
77}
78
79impl Policy for OnchainPolicy {
80    fn policy_error(&self, tag: String, msg: String) -> Result<(), ValidationError> {
81        policy_error_with_filter(tag, msg, &self.filter)
82    }
83
84    fn temporary_policy_error(&self, tag: String, msg: String) -> Result<(), ValidationError> {
85        temporary_policy_error_with_filter(tag, msg, &self.filter)
86    }
87
88    fn policy_log(&self, _tag: String, msg: String) {
89        error!("{}", msg);
90    }
91
92    fn global_velocity_control(&self) -> VelocityControlSpec {
93        VelocityControlSpec::UNLIMITED
94    }
95
96    fn fee_velocity_control(&self) -> VelocityControlSpec {
97        DEFAULT_FEE_VELOCITY_CONTROL
98    }
99}
100
101fn make_onchain_policy(_network: Network, filter: PolicyFilter) -> OnchainPolicy {
102    OnchainPolicy { filter, min_funding_depth: 1 }
103}
104
105impl Validator for OnchainValidator {
106    fn validate_setup_channel(
107        &self,
108        wallet: &dyn Wallet,
109        setup: &ChannelSetup,
110        holder_shutdown_key_path: &DerivationPath,
111    ) -> Result<(), ValidationError> {
112        self.inner.validate_setup_channel(wallet, setup, holder_shutdown_key_path)
113    }
114
115    fn validate_channel_value(&self, setup: &ChannelSetup) -> Result<(), ValidationError> {
116        self.inner.validate_channel_value(setup)
117    }
118
119    fn validate_onchain_tx(
120        &self,
121        wallet: &dyn Wallet,
122        channels: Vec<Option<Arc<Mutex<ChannelSlot>>>>,
123        tx: &Transaction,
124        segwit_flags: &[bool],
125        values_sat: &[u64],
126        opaths: &[DerivationPath],
127        weight: usize,
128    ) -> Result<u64, ValidationError> {
129        self.inner.validate_onchain_tx(
130            wallet,
131            channels,
132            tx,
133            segwit_flags,
134            values_sat,
135            opaths,
136            weight,
137        )
138    }
139
140    fn decode_commitment_tx(
141        &self,
142        keys: &InMemorySigner,
143        setup: &ChannelSetup,
144        is_counterparty: bool,
145        tx: &bitcoin::Transaction,
146        output_witscripts: &[Vec<u8>],
147    ) -> Result<CommitmentInfo, ValidationError> {
148        // Delegate to SimplePolicy
149        self.inner.decode_commitment_tx(keys, setup, is_counterparty, tx, output_witscripts)
150    }
151
152    fn validate_counterparty_commitment_tx(
153        &self,
154        estate: &EnforcementState,
155        commit_num: u64,
156        commitment_point: &PublicKey,
157        setup: &ChannelSetup,
158        cstate: &ChainState,
159        info2: &CommitmentInfo2,
160    ) -> Result<(), ValidationError> {
161        // Only allow state advancement if funding is buried and unspent
162        self.ensure_funding_buried_and_unspent(commit_num, cstate)?;
163        self.inner.validate_counterparty_commitment_tx(
164            estate,
165            commit_num,
166            commitment_point,
167            setup,
168            cstate,
169            info2,
170        )
171    }
172
173    fn validate_holder_commitment_tx(
174        &self,
175        estate: &EnforcementState,
176        commit_num: u64,
177        commitment_point: &PublicKey,
178        setup: &ChannelSetup,
179        cstate: &ChainState,
180        info2: &CommitmentInfo2,
181    ) -> Result<(), ValidationError> {
182        // Only allow state advancement if funding is buried and unspent
183        if estate.next_holder_commit_num <= commit_num {
184            self.ensure_funding_buried_and_unspent(commit_num, cstate)?;
185        }
186        self.inner.validate_holder_commitment_tx(
187            estate,
188            commit_num,
189            commitment_point,
190            setup,
191            cstate,
192            info2,
193        )
194    }
195
196    fn validate_counterparty_revocation(
197        &self,
198        state: &EnforcementState,
199        revoke_num: u64,
200        commitment_secret: &SecretKey,
201    ) -> Result<(), ValidationError> {
202        self.inner.validate_counterparty_revocation(state, revoke_num, commitment_secret)
203    }
204
205    // Phase 1
206    // setup and txkeys must come from a trusted source
207    fn decode_and_validate_htlc_tx(
208        &self,
209        is_counterparty: bool,
210        setup: &ChannelSetup,
211        txkeys: &TxCreationKeys,
212        tx: &Transaction,
213        redeemscript: &ScriptBuf,
214        htlc_amount_sat: u64,
215        output_witscript: &ScriptBuf,
216    ) -> Result<(u32, HTLCOutputInCommitment, SegwitV0Sighash, EcdsaSighashType), ValidationError>
217    {
218        // Delegate to SimplePolicy
219        self.inner.decode_and_validate_htlc_tx(
220            is_counterparty,
221            setup,
222            txkeys,
223            tx,
224            redeemscript,
225            htlc_amount_sat,
226            output_witscript,
227        )
228    }
229
230    fn validate_htlc_tx(
231        &self,
232        setup: &ChannelSetup,
233        cstate: &ChainState,
234        is_counterparty: bool,
235        htlc: &HTLCOutputInCommitment,
236        feerate_per_kw: u32,
237    ) -> Result<(), ValidationError> {
238        self.inner.validate_htlc_tx(setup, cstate, is_counterparty, htlc, feerate_per_kw)
239    }
240
241    fn decode_and_validate_mutual_close_tx(
242        &self,
243        wallet: &dyn Wallet,
244        setup: &ChannelSetup,
245        estate: &EnforcementState,
246        tx: &Transaction,
247        wallet_paths: &[DerivationPath],
248    ) -> Result<ClosingTransaction, ValidationError> {
249        // Delegate to SimplePolicy
250        self.inner.decode_and_validate_mutual_close_tx(wallet, setup, estate, tx, wallet_paths)
251    }
252
253    fn validate_mutual_close_tx(
254        &self,
255        wallet: &dyn Wallet,
256        setup: &ChannelSetup,
257        state: &EnforcementState,
258        to_holder_value_sat: u64,
259        to_counterparty_value_sat: u64,
260        holder_script: &Option<ScriptBuf>,
261        counterparty_script: &Option<ScriptBuf>,
262        holder_wallet_path_hint: &DerivationPath,
263    ) -> Result<(), ValidationError> {
264        self.inner.validate_mutual_close_tx(
265            wallet,
266            setup,
267            state,
268            to_holder_value_sat,
269            to_counterparty_value_sat,
270            holder_script,
271            counterparty_script,
272            holder_wallet_path_hint,
273        )
274    }
275
276    fn validate_delayed_sweep(
277        &self,
278        wallet: &dyn Wallet,
279        setup: &ChannelSetup,
280        cstate: &ChainState,
281        tx: &Transaction,
282        input: usize,
283        amount_sat: u64,
284        wallet_path: &DerivationPath,
285    ) -> Result<(), ValidationError> {
286        self.inner.validate_delayed_sweep(wallet, setup, cstate, tx, input, amount_sat, wallet_path)
287    }
288
289    fn validate_counterparty_htlc_sweep(
290        &self,
291        wallet: &dyn Wallet,
292        setup: &ChannelSetup,
293        cstate: &ChainState,
294        tx: &Transaction,
295        redeemscript: &ScriptBuf,
296        input: usize,
297        amount_sat: u64,
298        wallet_path: &DerivationPath,
299    ) -> Result<(), ValidationError> {
300        self.inner.validate_counterparty_htlc_sweep(
301            wallet,
302            setup,
303            cstate,
304            tx,
305            redeemscript,
306            input,
307            amount_sat,
308            wallet_path,
309        )
310    }
311
312    fn validate_justice_sweep(
313        &self,
314        wallet: &dyn Wallet,
315        setup: &ChannelSetup,
316        cstate: &ChainState,
317        tx: &Transaction,
318        input: usize,
319        amount_sat: u64,
320        wallet_path: &DerivationPath,
321    ) -> Result<(), ValidationError> {
322        self.inner.validate_justice_sweep(wallet, setup, cstate, tx, input, amount_sat, wallet_path)
323    }
324
325    fn validate_payment_balance(
326        &self,
327        incoming_msat: u64,
328        outgoing_msat: u64,
329        invoiced_amount_msat: Option<u64>,
330    ) -> Result<(), ValidationError> {
331        self.inner.validate_payment_balance(incoming_msat, outgoing_msat, invoiced_amount_msat)
332    }
333
334    fn minimum_initial_balance(&self, holder_value_msat: u64) -> u64 {
335        self.inner.minimum_initial_balance(holder_value_msat)
336    }
337
338    fn is_ready(&self, cstate: &ChainState) -> bool {
339        cstate.funding_depth >= self.policy.min_funding_depth as u32 && cstate.closing_depth == 0
340    }
341
342    fn policy(&self) -> Box<&dyn Policy> {
343        Box::new(&self.policy)
344    }
345}
346
347impl OnchainValidator {
348    fn ensure_funding_buried_and_unspent(
349        &self,
350        commit_num: u64,
351        cstate: &ChainState,
352    ) -> Result<(), ValidationError> {
353        debug!(
354            "ensure_funding_buried_and_unspent commit_num {} depth {} our height {}",
355            commit_num, cstate.funding_depth, cstate.current_height
356        );
357
358        // If we are trying to move beyond the initial commitment, ensure funding is on-chain and
359        // had enough confirmations.
360        if commit_num > 0 {
361            if cstate.funding_depth < self.policy.min_funding_depth as u32 {
362                temporary_policy_err!(
363                    self,
364                    "policy-commitment-spends-active-utxo",
365                    "tried commitment {} when funding is not buried at depth {}, our height {}",
366                    commit_num,
367                    cstate.funding_depth,
368                    cstate.current_height,
369                );
370            }
371
372            if cstate.closing_depth > 0 {
373                policy_err!(
374                    self,
375                    "policy-commitment-spends-active-utxo",
376                    "tried commitment {} after closed on-chain at depth {}",
377                    commit_num,
378                    cstate.closing_depth
379                );
380            }
381        }
382        Ok(())
383    }
384}