Skip to content

Commit c620944

Browse files
committed
Make the base fee configurable in ChannelConfig
Currently the base fee we apply is always the expected cost to claim an HTLC on-chain in case of closure. This results in significantly higher than market rate fees [1], and doesn't really match the actual forwarding trust model anyway - as long as channel counterparties are honest, our HTLCs shouldn't end up on-chain no matter what the HTLC sender/recipient do. While some users may wish to use a feerate that implies they will not lose funds even if they go to chain (assuming no flood-and-loot style attacks), they should do so by calculating fees themselves; since they're already charging well above market-rate, over-estimating some won't have a large impact. Worse, we current re-calculate fees at forward-time, not based on the fee we set in the channel_update. This means that the fees others expect to pay us (and which they calculate their route based on), is not what we actually want to charge, and that any attempt to forward through us is inherently race-y. This commit adds a configuration knob to set the base fee explicitly, defaulting to 1 sat, which appears to be market-rate today. [1] Note that due to an msat-vs-sat bug we currently actually charge 1000x *less* than the calculated cost.
1 parent dac8b7b commit c620944

File tree

11 files changed

+148
-75
lines changed

11 files changed

+148
-75
lines changed

fuzz/src/chanmon_consistency.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -338,7 +338,7 @@ pub fn do_test<Out: test_logger::Output>(data: &[u8], out: Out) {
338338
let monitor = Arc::new(TestChainMonitor::new(broadcast.clone(), logger.clone(), fee_est.clone(), Arc::new(TestPersister{}), Arc::clone(&keys_manager)));
339339

340340
let mut config = UserConfig::default();
341-
config.channel_options.fee_proportional_millionths = 0;
341+
config.channel_options.forwarding_fee_proportional_millionths = 0;
342342
config.channel_options.announced_channel = true;
343343
let network = Network::Bitcoin;
344344
let params = ChainParameters {
@@ -357,7 +357,7 @@ pub fn do_test<Out: test_logger::Output>(data: &[u8], out: Out) {
357357
let chain_monitor = Arc::new(TestChainMonitor::new(broadcast.clone(), logger.clone(), fee_est.clone(), Arc::new(TestPersister{}), Arc::clone(& $keys_manager)));
358358

359359
let mut config = UserConfig::default();
360-
config.channel_options.fee_proportional_millionths = 0;
360+
config.channel_options.forwarding_fee_proportional_millionths = 0;
361361
config.channel_options.announced_channel = true;
362362

363363
let mut monitors = HashMap::new();

fuzz/src/full_stack.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -359,7 +359,7 @@ pub fn do_test(data: &[u8], logger: &Arc<dyn Logger>) {
359359

360360
let keys_manager = Arc::new(KeyProvider { node_secret: our_network_key.clone(), counter: AtomicU64::new(0) });
361361
let mut config = UserConfig::default();
362-
config.channel_options.fee_proportional_millionths = slice_to_be32(get_slice!(4));
362+
config.channel_options.forwarding_fee_proportional_millionths = slice_to_be32(get_slice!(4));
363363
config.channel_options.announced_channel = get_slice!(1)[0] != 0;
364364
let network = Network::Bitcoin;
365365
let params = ChainParameters {

lightning-background-processor/src/lib.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -238,7 +238,7 @@ mod tests {
238238
let mut nodes = Vec::new();
239239
for i in 0..num_nodes {
240240
let tx_broadcaster = Arc::new(test_utils::TestBroadcaster{txn_broadcasted: Mutex::new(Vec::new()), blocks: Arc::new(Mutex::new(Vec::new()))});
241-
let fee_estimator = Arc::new(test_utils::TestFeeEstimator { sat_per_kw: 253 });
241+
let fee_estimator = Arc::new(test_utils::TestFeeEstimator { sat_per_kw: Mutex::new(253) });
242242
let chain_source = Arc::new(test_utils::TestChainSource::new(Network::Testnet));
243243
let logger = Arc::new(test_utils::TestLogger::with_id(format!("node {}", i)));
244244
let persister = Arc::new(FilesystemPersister::new(format!("{}_persister_{}", persist_dir, i)));

lightning/src/chain/channelmonitor.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2852,7 +2852,7 @@ mod tests {
28522852
let secp_ctx = Secp256k1::new();
28532853
let logger = Arc::new(TestLogger::new());
28542854
let broadcaster = Arc::new(TestBroadcaster{txn_broadcasted: Mutex::new(Vec::new()), blocks: Arc::new(Mutex::new(Vec::new()))});
2855-
let fee_estimator = Arc::new(TestFeeEstimator { sat_per_kw: 253 });
2855+
let fee_estimator = Arc::new(TestFeeEstimator { sat_per_kw: Mutex::new(253) });
28562856

28572857
let dummy_key = PublicKey::from_secret_key(&secp_ctx, &SecretKey::from_slice(&[42; 32]).unwrap());
28582858
let dummy_tx = Transaction { version: 0, lock_time: 0, input: Vec::new(), output: Vec::new() };

lightning/src/ln/channel.rs

Lines changed: 5 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -456,7 +456,6 @@ struct CommitmentTxInfoCached {
456456
}
457457

458458
pub const OUR_MAX_HTLCS: u16 = 50; //TODO
459-
const SPENDING_INPUT_FOR_A_OUTPUT_WEIGHT: u64 = 79; // prevout: 36, nSequence: 4, script len: 1, witness lengths: (3+1)/4, sig: 73/4, if-selector: 1, redeemScript: (6 ops + 2*33 pubkeys + 1*2 delay)/4
460459

461460
#[cfg(not(test))]
462461
const COMMITMENT_TX_BASE_WEIGHT: u64 = 724;
@@ -3426,7 +3425,7 @@ impl<Signer: Sign> Channel<Signer> {
34263425
}
34273426

34283427
pub fn get_fee_proportional_millionths(&self) -> u32 {
3429-
self.config.fee_proportional_millionths
3428+
self.config.forwarding_fee_proportional_millionths
34303429
}
34313430

34323431
pub fn get_cltv_expiry_delta(&self) -> u16 {
@@ -3499,24 +3498,8 @@ impl<Signer: Sign> Channel<Signer> {
34993498

35003499
/// Gets the fee we'd want to charge for adding an HTLC output to this Channel
35013500
/// Allowed in any state (including after shutdown)
3502-
pub fn get_holder_fee_base_msat<F: Deref>(&self, fee_estimator: &F) -> u32
3503-
where F::Target: FeeEstimator
3504-
{
3505-
// For lack of a better metric, we calculate what it would cost to consolidate the new HTLC
3506-
// output value back into a transaction with the regular channel output:
3507-
3508-
// the fee cost of the HTLC-Success/HTLC-Timeout transaction:
3509-
let mut res = self.feerate_per_kw as u64 * cmp::max(HTLC_TIMEOUT_TX_WEIGHT, HTLC_SUCCESS_TX_WEIGHT) / 1000;
3510-
3511-
if self.is_outbound() {
3512-
// + the marginal fee increase cost to us in the commitment transaction:
3513-
res += self.feerate_per_kw as u64 * COMMITMENT_TX_WEIGHT_PER_HTLC / 1000;
3514-
}
3515-
3516-
// + the marginal cost of an input which spends the HTLC-Success/HTLC-Timeout output:
3517-
res += fee_estimator.get_est_sat_per_1000_weight(ConfirmationTarget::Normal) as u64 * SPENDING_INPUT_FOR_A_OUTPUT_WEIGHT / 1000;
3518-
3519-
res as u32
3501+
pub fn get_outbound_forwarding_fee_base_msat(&self) -> u32 {
3502+
self.config.forwarding_fee_base_msat
35203503
}
35213504

35223505
/// Returns true if we've ever received a message from the remote end for this Channel
@@ -4505,7 +4488,7 @@ impl<Signer: Sign> Writeable for Channel<Signer> {
45054488

45064489
// Write out the old serialization for the config object. This is read by version-1
45074490
// deserializers, but we will read the version in the TLV at the end instead.
4508-
self.config.fee_proportional_millionths.write(writer)?;
4491+
self.config.forwarding_fee_proportional_millionths.write(writer)?;
45094492
self.config.cltv_expiry_delta.write(writer)?;
45104493
self.config.announced_channel.write(writer)?;
45114494
self.config.commit_upfront_shutdown_pubkey.write(writer)?;
@@ -4724,7 +4707,7 @@ impl<'a, Signer: Sign, K: Deref> ReadableArgs<&'a K> for Channel<Signer>
47244707
let mut config = Some(ChannelConfig::default());
47254708
if ver == 1 {
47264709
// Read the old serialization of the ChannelConfig from version 0.0.98.
4727-
config.as_mut().unwrap().fee_proportional_millionths = Readable::read(reader)?;
4710+
config.as_mut().unwrap().forwarding_fee_proportional_millionths = Readable::read(reader)?;
47284711
config.as_mut().unwrap().cltv_expiry_delta = Readable::read(reader)?;
47294712
config.as_mut().unwrap().announced_channel = Readable::read(reader)?;
47304713
config.as_mut().unwrap().commit_upfront_shutdown_pubkey = Readable::read(reader)?;

lightning/src/ln/channelmanager.rs

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1575,7 +1575,9 @@ impl<Signer: Sign, M: Deref, T: Deref, K: Deref, F: Deref, L: Deref> ChannelMana
15751575
if *amt_to_forward < chan.get_counterparty_htlc_minimum_msat() { // amount_below_minimum
15761576
break Some(("HTLC amount was below the htlc_minimum_msat", 0x1000 | 11, Some(self.get_channel_update_for_unicast(chan).unwrap())));
15771577
}
1578-
let fee = amt_to_forward.checked_mul(chan.get_fee_proportional_millionths() as u64).and_then(|prop_fee| { (prop_fee / 1000000).checked_add(chan.get_holder_fee_base_msat(&self.fee_estimator) as u64) });
1578+
let fee = amt_to_forward.checked_mul(chan.get_fee_proportional_millionths() as u64)
1579+
.and_then(|prop_fee| { (prop_fee / 1000000)
1580+
.checked_add(chan.get_outbound_forwarding_fee_base_msat() as u64) });
15791581
if fee.is_none() || msg.amount_msat < fee.unwrap() || (msg.amount_msat - fee.unwrap()) < *amt_to_forward { // fee_insufficient
15801582
break Some(("Prior hop has deviated from specified fees parameters or origin node has obsolete ones", 0x1000 | 12, Some(self.get_channel_update_for_unicast(chan).unwrap())));
15811583
}
@@ -1660,7 +1662,7 @@ impl<Signer: Sign, M: Deref, T: Deref, K: Deref, F: Deref, L: Deref> ChannelMana
16601662
cltv_expiry_delta: chan.get_cltv_expiry_delta(),
16611663
htlc_minimum_msat: chan.get_counterparty_htlc_minimum_msat(),
16621664
htlc_maximum_msat: OptionalField::Present(chan.get_announced_htlc_max_msat()),
1663-
fee_base_msat: chan.get_holder_fee_base_msat(&self.fee_estimator),
1665+
fee_base_msat: chan.get_outbound_forwarding_fee_base_msat(),
16641666
fee_proportional_millionths: chan.get_fee_proportional_millionths(),
16651667
excess_data: Vec::new(),
16661668
};
@@ -5107,7 +5109,7 @@ pub mod bench {
51075109
let genesis_hash = bitcoin::blockdata::constants::genesis_block(network).header.block_hash();
51085110

51095111
let tx_broadcaster = test_utils::TestBroadcaster{txn_broadcasted: Mutex::new(Vec::new()), blocks: Arc::new(Mutex::new(Vec::new()))};
5110-
let fee_estimator = test_utils::TestFeeEstimator { sat_per_kw: 253 };
5112+
let fee_estimator = test_utils::TestFeeEstimator { sat_per_kw: Mutex::new(253) };
51115113

51125114
let mut config: UserConfig = Default::default();
51135115
config.own_channel_config.minimum_depth = 1;

lightning/src/ln/functional_test_utils.rs

Lines changed: 18 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -269,7 +269,7 @@ impl<'a, 'b, 'c> Drop for Node<'a, 'b, 'c> {
269269
// Check that if we serialize and then deserialize all our channel monitors we get the
270270
// same set of outputs to watch for on chain as we have now. Note that if we write
271271
// tests that fully close channels and remove the monitors at some point this may break.
272-
let feeest = test_utils::TestFeeEstimator { sat_per_kw: 253 };
272+
let feeest = test_utils::TestFeeEstimator { sat_per_kw: Mutex::new(253) };
273273
let mut deserialized_monitors = Vec::new();
274274
{
275275
let old_monitors = self.chain_monitor.chain_monitor.monitors.read().unwrap();
@@ -295,7 +295,7 @@ impl<'a, 'b, 'c> Drop for Node<'a, 'b, 'c> {
295295
<(BlockHash, ChannelManager<EnforcingSigner, &test_utils::TestChainMonitor, &test_utils::TestBroadcaster, &test_utils::TestKeysInterface, &test_utils::TestFeeEstimator, &test_utils::TestLogger>)>::read(&mut ::std::io::Cursor::new(w.0), ChannelManagerReadArgs {
296296
default_config: *self.node.get_current_default_configuration(),
297297
keys_manager: self.keys_manager,
298-
fee_estimator: &test_utils::TestFeeEstimator { sat_per_kw: 253 },
298+
fee_estimator: &test_utils::TestFeeEstimator { sat_per_kw: Mutex::new(253) },
299299
chain_monitor: self.chain_monitor,
300300
tx_broadcaster: &test_utils::TestBroadcaster {
301301
txn_broadcasted: Mutex::new(self.tx_broadcaster.txn_broadcasted.lock().unwrap().clone()),
@@ -1316,7 +1316,7 @@ pub fn create_chanmon_cfgs(node_count: usize) -> Vec<TestChanMonCfg> {
13161316
txn_broadcasted: Mutex::new(Vec::new()),
13171317
blocks: Arc::new(Mutex::new(vec![(genesis_block(Network::Testnet).header, 0)])),
13181318
};
1319-
let fee_estimator = test_utils::TestFeeEstimator { sat_per_kw: 253 };
1319+
let fee_estimator = test_utils::TestFeeEstimator { sat_per_kw: Mutex::new(253) };
13201320
let chain_source = test_utils::TestChainSource::new(Network::Testnet);
13211321
let logger = test_utils::TestLogger::with_id(format!("node {}", i));
13221322
let persister = test_utils::TestPersister::new();
@@ -1341,22 +1341,29 @@ pub fn create_node_cfgs<'a>(node_count: usize, chanmon_cfgs: &'a Vec<TestChanMon
13411341
nodes
13421342
}
13431343

1344+
pub fn test_default_channel_config() -> UserConfig {
1345+
let mut default_config = UserConfig::default();
1346+
// Set cltv_expiry_delta slightly lower to keep the final CLTV values inside one byte in our
1347+
// tests so that our script-length checks don't fail (see ACCEPTED_HTLC_SCRIPT_WEIGHT).
1348+
default_config.channel_options.cltv_expiry_delta = 6*6;
1349+
default_config.channel_options.announced_channel = true;
1350+
default_config.peer_channel_config_limits.force_announced_channel_preference = false;
1351+
// When most of our tests were written, the default HTLC minimum was fixed at 1000.
1352+
// It now defaults to 1, so we simply set it to the expected value here.
1353+
default_config.own_channel_config.our_htlc_minimum_msat = 1000;
1354+
default_config
1355+
}
1356+
13441357
pub fn create_node_chanmgrs<'a, 'b>(node_count: usize, cfgs: &'a Vec<NodeCfg<'b>>, node_config: &[Option<UserConfig>]) -> Vec<ChannelManager<EnforcingSigner, &'a TestChainMonitor<'b>, &'b test_utils::TestBroadcaster, &'a test_utils::TestKeysInterface, &'b test_utils::TestFeeEstimator, &'b test_utils::TestLogger>> {
13451358
let mut chanmgrs = Vec::new();
13461359
for i in 0..node_count {
1347-
let mut default_config = UserConfig::default();
1348-
// Set cltv_expiry_delta slightly lower to keep the final CLTV values inside one byte in our
1349-
// tests so that our script-length checks don't fail (see ACCEPTED_HTLC_SCRIPT_WEIGHT).
1350-
default_config.channel_options.cltv_expiry_delta = 6*6;
1351-
default_config.channel_options.announced_channel = true;
1352-
default_config.peer_channel_config_limits.force_announced_channel_preference = false;
1353-
default_config.own_channel_config.our_htlc_minimum_msat = 1000; // sanitization being done by the sender, to exerce receiver logic we need to lift of limit
13541360
let network = Network::Testnet;
13551361
let params = ChainParameters {
13561362
network,
13571363
best_block: BestBlock::from_genesis(network),
13581364
};
1359-
let node = ChannelManager::new(cfgs[i].fee_estimator, &cfgs[i].chain_monitor, cfgs[i].tx_broadcaster, cfgs[i].logger, cfgs[i].keys_manager, if node_config[i].is_some() { node_config[i].clone().unwrap() } else { default_config }, params);
1365+
let node = ChannelManager::new(cfgs[i].fee_estimator, &cfgs[i].chain_monitor, cfgs[i].tx_broadcaster, cfgs[i].logger, cfgs[i].keys_manager,
1366+
if node_config[i].is_some() { node_config[i].clone().unwrap() } else { test_default_channel_config() }, params);
13601367
chanmgrs.push(node);
13611368
}
13621369

0 commit comments

Comments
 (0)