@@ -619,9 +619,25 @@ where
619619 _ => 1 ,
620620 } ;
621621
622+ let last_sync_height = self
623+ . database ( )
624+ . get_sync_time ( ) ?
625+ . map ( |sync_time| sync_time. block_time . height ) ;
626+
627+ // If they didn't tell us the current height, we assume it's the latest sync height.
628+ let current_height = params. current_height . or ( last_sync_height) ;
629+
622630 let lock_time = match params. locktime {
623- // No nLockTime, default to 0
624- None => requirements. timelock . unwrap_or ( 0 ) ,
631+ // When no nLockTime is specified, we try to prevent fee sniping, if possible
632+ None => {
633+ // Fee sniping can be partially prevented by setting the timelock
634+ // to current_height. If we don't know the current_height,
635+ // we default to 0.
636+ let fee_sniping_height = current_height. unwrap_or ( 0 ) ;
637+ // We choose the bigger between the required height and the fee sniping
638+ // height
639+ std:: cmp:: max ( requirements. timelock . unwrap_or ( 0 ) , fee_sniping_height)
640+ }
625641 // Specific nLockTime required and we have no constraints, so just set to that value
626642 Some ( x) if requirements. timelock . is_none ( ) => x,
627643 // Specific nLockTime required and it's compatible with the constraints
@@ -1980,9 +1996,59 @@ pub(crate) mod test {
19801996 builder. add_recipient ( addr. script_pubkey ( ) , 25_000 ) ;
19811997 let ( psbt, _) = builder. finish ( ) . unwrap ( ) ;
19821998
1999+ // Since we never synced the wallet we don't have a last_sync_height
2000+ // we could use to try to prevent fee sniping. We default to 0.
19832001 assert_eq ! ( psbt. unsigned_tx. lock_time, 0 ) ;
19842002 }
19852003
2004+ #[ test]
2005+ fn test_create_tx_fee_sniping_locktime_provided_height ( ) {
2006+ let ( wallet, _, _) = get_funded_wallet ( get_test_wpkh ( ) ) ;
2007+ let addr = wallet. get_address ( New ) . unwrap ( ) ;
2008+ let mut builder = wallet. build_tx ( ) ;
2009+ builder. add_recipient ( addr. script_pubkey ( ) , 25_000 ) ;
2010+ let sync_time = SyncTime {
2011+ block_time : BlockTime {
2012+ height : 24 ,
2013+ timestamp : 0 ,
2014+ } ,
2015+ } ;
2016+ wallet
2017+ . database
2018+ . borrow_mut ( )
2019+ . set_sync_time ( sync_time)
2020+ . unwrap ( ) ;
2021+ let current_height = 25 ;
2022+ builder. set_current_height ( current_height) ;
2023+ let ( psbt, _) = builder. finish ( ) . unwrap ( ) ;
2024+
2025+ // current_height will override the last sync height
2026+ assert_eq ! ( psbt. unsigned_tx. lock_time, current_height) ;
2027+ }
2028+
2029+ #[ test]
2030+ fn test_create_tx_fee_sniping_locktime_last_sync ( ) {
2031+ let ( wallet, _, _) = get_funded_wallet ( get_test_wpkh ( ) ) ;
2032+ let addr = wallet. get_address ( New ) . unwrap ( ) ;
2033+ let mut builder = wallet. build_tx ( ) ;
2034+ builder. add_recipient ( addr. script_pubkey ( ) , 25_000 ) ;
2035+ let sync_time = SyncTime {
2036+ block_time : BlockTime {
2037+ height : 25 ,
2038+ timestamp : 0 ,
2039+ } ,
2040+ } ;
2041+ wallet
2042+ . database
2043+ . borrow_mut ( )
2044+ . set_sync_time ( sync_time. clone ( ) )
2045+ . unwrap ( ) ;
2046+ let ( psbt, _) = builder. finish ( ) . unwrap ( ) ;
2047+
2048+ // If there's no current_height we're left with using the last sync height
2049+ assert_eq ! ( psbt. unsigned_tx. lock_time, sync_time. block_time. height) ;
2050+ }
2051+
19862052 #[ test]
19872053 fn test_create_tx_default_locktime_cltv ( ) {
19882054 let ( wallet, _, _) = get_funded_wallet ( get_test_single_sig_cltv ( ) ) ;
@@ -2001,9 +2067,13 @@ pub(crate) mod test {
20012067 let mut builder = wallet. build_tx ( ) ;
20022068 builder
20032069 . add_recipient ( addr. script_pubkey ( ) , 25_000 )
2070+ . set_current_height ( 630_001 )
20042071 . nlocktime ( 630_000 ) ;
20052072 let ( psbt, _) = builder. finish ( ) . unwrap ( ) ;
20062073
2074+ // When we explicitly specify a nlocktime
2075+ // we don't try any fee sniping prevention trick
2076+ // (we ignore the current_height)
20072077 assert_eq ! ( psbt. unsigned_tx. lock_time, 630_000 ) ;
20082078 }
20092079
0 commit comments