diff --git a/doc/getting-started/getting-started/configuration.md b/doc/getting-started/getting-started/configuration.md index 6f1ed2631cfd..cbd51c0909e0 100644 --- a/doc/getting-started/getting-started/configuration.md +++ b/doc/getting-started/getting-started/configuration.md @@ -313,6 +313,11 @@ The [`lightning-listconfigs`](ref:lightning-listconfigs) command will output a v The percentage of _estimatesmartfee 2/CONSERVATIVE_ to use for the commitment transactions: default is 100. +- **commit-feerate-offset**=_INTEGER_ + + The additional feerate a channel opener adds to their preferred feerate to + lessen the odds of a disconnect due to feerate disagreement (default 5). + - **max-concurrent-htlcs**=_INTEGER_ Number of HTLCs one channel can handle concurrently in each direction. diff --git a/doc/lightning-listconfigs.7.md b/doc/lightning-listconfigs.7.md index 2c562d49d24d..087e230042c0 100644 --- a/doc/lightning-listconfigs.7.md +++ b/doc/lightning-listconfigs.7.md @@ -281,6 +281,9 @@ On success, an object is returned, containing: - **commit-fee** (object, optional): - **value\_int** (u64): field from config or cmdline, or default - **source** (string): source of configuration setting + - **commit-feerate-offset** (object, optional): + - **value\_int** (u32): field from config or cmdline, or default + - **source** (string): source of configuration setting - **# version** (string, optional): Special field indicating the current version **deprecated, removal in v24.05** - **plugins** (array of objects, optional) **deprecated, removal in v24.05**: - **path** (string): Full path of the plugin @@ -359,6 +362,7 @@ On success, an object is returned, containing: - **developer** (boolean, optional): Whether developer mode is enabled *(added v23.08)* - **commit-fee** (u64, optional): The percentage of the 6-block fee estimate to use for commitment transactions **deprecated, removal in v24.05** *(added v23.05)* - **min-emergency-msat** (msat, optional): field from config or cmdline, or default *(added v23.08)* +- **commit-feerate-offset** (u32, optional): additional commitment feerate applied by channel owner *(added v23.11)* [comment]: # (GENERATE-FROM-SCHEMA-END) @@ -476,4 +480,4 @@ RESOURCES Main web site: -[comment]: # ( SHA256STAMP:cc7b6d10f93b9efb34ad76d0cc2273d29189a8dd7ef4acef2e5227755c279ea8) +[comment]: # ( SHA256STAMP:245e056bdda7c8015917c89e243a0fd3bdd1512ca760da5d7f0a284cb3214ef7) diff --git a/doc/lightningd-config.5.md b/doc/lightningd-config.5.md index 192164f9b423..88522a1e3df4 100644 --- a/doc/lightningd-config.5.md +++ b/doc/lightningd-config.5.md @@ -420,6 +420,11 @@ opens a channel before the channel is usable. The percentage of *estimatesmartfee 2/CONSERVATIVE* to use for the commitment transactions: default is 100. +* **commit-feerate-offset**=*INTEGER* + + The additional feerate a channel opener adds to their preferred feerate to +lessen the odds of a disconnect due to feerate disagreement (default 5). + * **max-concurrent-htlcs**=*INTEGER* Number of HTLCs one channel can handle concurrently in each direction. diff --git a/doc/schemas/listconfigs.schema.json b/doc/schemas/listconfigs.schema.json index 7f971f61af29..bc307d191752 100644 --- a/doc/schemas/listconfigs.schema.json +++ b/doc/schemas/listconfigs.schema.json @@ -1351,6 +1351,24 @@ "description": "source of configuration setting" } } + }, + "commit-feerate-offset": { + "type": "object", + "additionalProperties": false, + "required": [ + "value_int", + "source" + ], + "properties": { + "value_int": { + "type": "u32", + "description": "field from config or cmdline, or default" + }, + "source": { + "type": "string", + "description": "source of configuration setting" + } + } } } }, @@ -1770,6 +1788,11 @@ "type": "msat", "added": "v23.08", "description": "field from config or cmdline, or default" + }, + "commit-feerate-offset": { + "type": "u32", + "added": "v23.11", + "description": "additional commitment feerate applied by channel owner" } } } diff --git a/lightningd/channel_control.c b/lightningd/channel_control.c index 422ea5b43c98..997a12814d2d 100644 --- a/lightningd/channel_control.c +++ b/lightningd/channel_control.c @@ -56,6 +56,13 @@ void channel_update_feerates(struct lightningd *ld, const struct channel *channe else min_feerate = feerate_min(ld, NULL); max_feerate = feerate_max(ld, NULL); + /* The channel opener should use a slightly higher than minimal feerate + * in order to avoid excessive feerate disagreements */ + if (channel->opener == LOCAL) { + feerate += ld->config.feerate_offset; + if (feerate > max_feerate) + feerate = max_feerate; + } if (channel->ignore_fee_limits || ld->config.ignore_fee_limits) { min_feerate = 1; diff --git a/lightningd/lightningd.h b/lightningd/lightningd.h index a2ebcefb1133..776fa54a28a4 100644 --- a/lightningd/lightningd.h +++ b/lightningd/lightningd.h @@ -93,6 +93,9 @@ struct config { /* Percent of CONSERVATIVE/2 feerate we'll use for commitment txs. */ u64 commit_fee_percent; + + /* Commit feerate offset above min_feerate to use as a channel opener */ + u32 feerate_offset; }; typedef STRMAP(const char *) alt_subdaemon_map; diff --git a/lightningd/options.c b/lightningd/options.c index e28482ce9623..9837f9913fda 100644 --- a/lightningd/options.c +++ b/lightningd/options.c @@ -948,6 +948,7 @@ static const struct config testnet_config = { .max_fee_multiplier = 10, .commit_fee_percent = 100, + .feerate_offset = 5, }; /* aka. "Dude, where's my coins?" */ @@ -1022,6 +1023,7 @@ static const struct config mainnet_config = { .max_fee_multiplier = 10, .commit_fee_percent = 100, + .feerate_offset = 5, }; static void check_config(struct lightningd *ld) @@ -1576,6 +1578,10 @@ static void register_opts(struct lightningd *ld) clnopt_witharg("--commit-fee", OPT_SHOWINT, opt_set_u64, opt_show_u64, &ld->config.commit_fee_percent, "Percentage of fee to request for their commitment"); + clnopt_witharg("--commit-feerate-offset", OPT_SHOWINT, + opt_set_u32, opt_show_u32, &ld->config.feerate_offset, + "Additional feerate per kw to apply to feerate updates " + "as the channel opener"); clnopt_witharg("--min-emergency-msat", OPT_SHOWMSATS, opt_set_sat_nondust, opt_show_sat, &ld->emergency_sat, "Amount to leave in wallet for spending anchor closes"); diff --git a/tests/test_closing.py b/tests/test_closing.py index 5e9f7751348d..df9d424c507e 100644 --- a/tests/test_closing.py +++ b/tests/test_closing.py @@ -1251,7 +1251,7 @@ def test_penalty_htlc_tx_fulfill(node_factory, bitcoind, chainparams, anchors): # reconnect with l1, which will fulfill the payment l2.rpc.connect(l1.info['id'], 'localhost', l1.port) - l2.daemon.wait_for_log('got commitsig .*: feerate 11000, blockheight: 0, 0 added, 1 fulfilled, 0 failed, 0 changed') + l2.daemon.wait_for_log('got commitsig .*: feerate 11005, blockheight: 0, 0 added, 1 fulfilled, 0 failed, 0 changed') # l2 moves on for closed l3 bitcoind.generate_block(1) @@ -1463,7 +1463,7 @@ def test_penalty_htlc_tx_timeout(node_factory, bitcoind, chainparams, anchors): # reconnect with l1, which will fulfill the payment l2.rpc.connect(l1.info['id'], 'localhost', l1.port) - l2.daemon.wait_for_log('got commitsig .*: feerate {}, blockheight: 0, 0 added, 1 fulfilled, 0 failed, 0 changed'.format(3750 if anchors else 11000)) + l2.daemon.wait_for_log('got commitsig .*: feerate {}, blockheight: 0, 0 added, 1 fulfilled, 0 failed, 0 changed'.format(3755 if anchors else 11005)) # l2 moves on for closed l3 bitcoind.generate_block(1, wait_for_mempool=1) @@ -2650,12 +2650,12 @@ def test_onchain_different_fees(node_factory, bitcoind, executor): # Both sides should have correct feerate assert l1.db_query('SELECT min_possible_feerate, max_possible_feerate FROM channels;') == [{ - 'min_possible_feerate': 5000, - 'max_possible_feerate': 11000 + 'min_possible_feerate': 5005, + 'max_possible_feerate': 11005 }] assert l2.db_query('SELECT min_possible_feerate, max_possible_feerate FROM channels;') == [{ - 'min_possible_feerate': 5000, - 'max_possible_feerate': 11000 + 'min_possible_feerate': 5005, + 'max_possible_feerate': 11005 }] bitcoind.generate_block(5) diff --git a/tests/test_connection.py b/tests/test_connection.py index cba391444915..1a1da87c19a3 100644 --- a/tests/test_connection.py +++ b/tests/test_connection.py @@ -2412,7 +2412,7 @@ def test_update_fee(node_factory, bitcoind): # Make payments. l1.pay(l2, 200000000) # First payment causes fee update. - l2.daemon.wait_for_log('peer updated fee to 11000') + l2.daemon.wait_for_log('peer updated fee to 11005') l2.pay(l1, 100000000) # Now shutdown cleanly. @@ -2454,22 +2454,22 @@ def test_fee_limits(node_factory, bitcoind): l1.set_feerates((15, 15, 15, 15), False) l1.start() - l1.daemon.wait_for_log('Received WARNING .*: update_fee 253 outside range 1875-75000') + l1.daemon.wait_for_log('Received WARNING .*: update_fee 258 outside range 1875-75000') # They hang up on *us* l1.daemon.wait_for_log('Peer transient failure in CHANNELD_NORMAL: channeld: Owning subdaemon channeld died') # Disconnects, but does not error. Make sure it's noted in their status though. # FIXME: does not happen for l1! # assert 'update_fee 253 outside range 1875-75000' in only_one(l1.rpc.listpeerchannels(l2.info['id'])['channels'])['status'][0] - assert 'update_fee 253 outside range 1875-75000' in only_one(l2.rpc.listpeerchannels(l1.info['id'])['channels'])['status'][0] + assert 'update_fee 258 outside range 1875-75000' in only_one(l2.rpc.listpeerchannels(l1.info['id'])['channels'])['status'][0] - assert only_one(l2.rpc.listpeerchannels()['channels'])['feerate']['perkw'] != 253 + assert only_one(l2.rpc.listpeerchannels()['channels'])['feerate']['perkw'] != 258 # Make l2 accept those fees, and it should recover. assert only_one(l2.rpc.setchannel(l1.get_channel_scid(l2), ignorefeelimits=True)['channels'])['ignore_fee_limits'] is True assert only_one(l2.rpc.listpeerchannels()['channels'])['ignore_fee_limits'] is True # Now we stay happy (and connected!) - wait_for(lambda: only_one(l2.rpc.listpeerchannels()['channels'])['feerate']['perkw'] == 253) + wait_for(lambda: only_one(l2.rpc.listpeerchannels()['channels'])['feerate']['perkw'] == 258) assert only_one(l2.rpc.listpeerchannels()['channels'])['peer_connected'] is True l1.rpc.close(l2.info['id']) @@ -2583,19 +2583,19 @@ def test_update_fee_reconnect(node_factory, bitcoind): # Make l1 send out feechange; triggers disconnect/reconnect. # (Note: < 10% change, so no smoothing here!) l1.set_feerates((14000, 14000, 14000, 3750)) - l1.daemon.wait_for_log('Setting REMOTE feerate to 14000') - l2.daemon.wait_for_log('Setting LOCAL feerate to 14000') + l1.daemon.wait_for_log('Setting REMOTE feerate to 14005') + l2.daemon.wait_for_log('Setting LOCAL feerate to 14005') l1.daemon.wait_for_log(r'dev_disconnect: \+WIRE_COMMITMENT_SIGNED') # Wait for reconnect.... - l1.daemon.wait_for_log('Feerate:.*LOCAL now 14000') + l1.daemon.wait_for_log('Feerate:.*LOCAL now 14005') l1.pay(l2, 200000000) l2.pay(l1, 100000000) # They should both have gotten commits with correct feerate. - assert l1.daemon.is_in_log('got commitsig [0-9]*: feerate 14000') - assert l2.daemon.is_in_log('got commitsig [0-9]*: feerate 14000') + assert l1.daemon.is_in_log('got commitsig [0-9]*: feerate 14005') + assert l2.daemon.is_in_log('got commitsig [0-9]*: feerate 14005') # Now shutdown cleanly. l1.rpc.close(chan) @@ -3347,7 +3347,7 @@ def test_feerate_spam(node_factory, chainparams): l1.pay(l2, 10**9 - slack) # It will send this once (may have happened before line_graph's wait) - wait_for(lambda: l1.daemon.is_in_log('Setting REMOTE feerate to 11000')) + wait_for(lambda: l1.daemon.is_in_log('Setting REMOTE feerate to 11005')) wait_for(lambda: l1.daemon.is_in_log('peer_out WIRE_UPDATE_FEE')) # Now change feerates to something l1 can't afford. diff --git a/tests/test_misc.py b/tests/test_misc.py index 6ebe157c6f1d..e0d2fc04f658 100644 --- a/tests/test_misc.py +++ b/tests/test_misc.py @@ -3831,3 +3831,30 @@ def test_even_sendcustommsg(node_factory): l2.daemon.wait_for_log(r'\[IN\] {}'.format(msg)) l1.daemon.wait_for_log('Invalid unknown even msg') wait_for(lambda: l1.rpc.listpeers(l2.info['id'])['peers'] == []) + + +def test_set_feerate_offset(node_factory, bitcoind): + opts = [{'commit-feerate-offset': 100}, {}] + l1, l2 = node_factory.get_nodes(2, opts=opts) + assert l1.daemon.is_in_log('Server started with public key') + configs = l1.rpc.listconfigs()['configs'] + assert configs['commit-feerate-offset'] == {'source': 'cmdline', + 'value_int': 100} + scid12 = l1.fundchannel(l2)[0] + # chanid = l1.get_channel_scid(l2) + + # node 1 sets fees. + l1.set_feerates((14000, 11000, 7500, 3750)) + + l1.pay(l2, 200000000) + # First payment causes fee update, which should reflect the feerate offset. + l1.daemon.wait_for_log('lightningd: update_feerates: feerate = 11100, ' + 'min=1875, max=150000, penalty=7500') + l2.daemon.wait_for_log('peer updated fee to 11100') + l2.pay(l1, 100000000) + + # Now shutdown cleanly. + l1.rpc.close(scid12) + + l1.daemon.wait_for_log(' to CLOSINGD_COMPLETE') + l2.daemon.wait_for_log(' to CLOSINGD_COMPLETE')