diff --git a/.msggen.json b/.msggen.json index 79601bd08..e79516736 100644 --- a/.msggen.json +++ b/.msggen.json @@ -358,6 +358,7 @@ "FeeratesPerkb": { "Feerates.perkb.delayed_to_us": 6, "Feerates.perkb.estimates[]": 9, + "Feerates.perkb.floor": 10, "Feerates.perkb.htlc_resolution": 7, "Feerates.perkb.max_acceptable": 2, "Feerates.perkb.min_acceptable": 1, @@ -374,6 +375,7 @@ "FeeratesPerkw": { "Feerates.perkw.delayed_to_us": 6, "Feerates.perkw.estimates[]": 9, + "Feerates.perkw.floor": 10, "Feerates.perkw.htlc_resolution": 7, "Feerates.perkw.max_acceptable": 2, "Feerates.perkw.min_acceptable": 1, @@ -1572,6 +1574,10 @@ "added": "v23.05", "deprecated": false }, + "Feerates.perkb.floor": { + "added": "v23.05", + "deprecated": false + }, "Feerates.perkb.htlc_resolution": { "added": "pre-v0.10.1", "deprecated": "v23.05" @@ -1624,6 +1630,10 @@ "added": "v23.05", "deprecated": false }, + "Feerates.perkw.floor": { + "added": "v23.05", + "deprecated": false + }, "Feerates.perkw.htlc_resolution": { "added": "pre-v0.10.1", "deprecated": "v23.05" diff --git a/cln-grpc/proto/node.proto b/cln-grpc/proto/node.proto index 982414fa0..2ff3e7a37 100644 --- a/cln-grpc/proto/node.proto +++ b/cln-grpc/proto/node.proto @@ -1130,6 +1130,7 @@ message FeeratesResponse { message FeeratesPerkb { uint32 min_acceptable = 1; uint32 max_acceptable = 2; + optional uint32 floor = 10; repeated FeeratesPerkbEstimates estimates = 9; optional uint32 opening = 3; optional uint32 mutual_close = 4; @@ -1148,6 +1149,7 @@ message FeeratesPerkbEstimates { message FeeratesPerkw { uint32 min_acceptable = 1; uint32 max_acceptable = 2; + optional uint32 floor = 10; repeated FeeratesPerkwEstimates estimates = 9; optional uint32 opening = 3; optional uint32 mutual_close = 4; diff --git a/cln-grpc/src/convert.rs b/cln-grpc/src/convert.rs index 17a456438..c5d1eff45 100644 --- a/cln-grpc/src/convert.rs +++ b/cln-grpc/src/convert.rs @@ -932,6 +932,7 @@ impl From for pb::FeeratesPerkb { Self { min_acceptable: c.min_acceptable, // Rule #2 for type u32 max_acceptable: c.max_acceptable, // Rule #2 for type u32 + floor: c.floor, // Rule #2 for type u32? estimates: c.estimates.map(|arr| arr.into_iter().map(|i| i.into()).collect()).unwrap_or(vec![]), // Rule #3 opening: c.opening, // Rule #2 for type u32? mutual_close: c.mutual_close, // Rule #2 for type u32? @@ -962,6 +963,7 @@ impl From for pb::FeeratesPerkw { Self { min_acceptable: c.min_acceptable, // Rule #2 for type u32 max_acceptable: c.max_acceptable, // Rule #2 for type u32 + floor: c.floor, // Rule #2 for type u32? estimates: c.estimates.map(|arr| arr.into_iter().map(|i| i.into()).collect()).unwrap_or(vec![]), // Rule #3 opening: c.opening, // Rule #2 for type u32? mutual_close: c.mutual_close, // Rule #2 for type u32? @@ -3297,6 +3299,7 @@ impl From for responses::FeeratesPerkb { Self { min_acceptable: c.min_acceptable, // Rule #1 for type u32 max_acceptable: c.max_acceptable, // Rule #1 for type u32 + floor: c.floor, // Rule #1 for type u32? estimates: Some(c.estimates.into_iter().map(|s| s.into()).collect()), // Rule #4 opening: c.opening, // Rule #1 for type u32? mutual_close: c.mutual_close, // Rule #1 for type u32? @@ -3325,6 +3328,7 @@ impl From for responses::FeeratesPerkw { Self { min_acceptable: c.min_acceptable, // Rule #1 for type u32 max_acceptable: c.max_acceptable, // Rule #1 for type u32 + floor: c.floor, // Rule #1 for type u32? estimates: Some(c.estimates.into_iter().map(|s| s.into()).collect()), // Rule #4 opening: c.opening, // Rule #1 for type u32? mutual_close: c.mutual_close, // Rule #1 for type u32? diff --git a/cln-rpc/src/model.rs b/cln-rpc/src/model.rs index 3b7855863..fc065cd57 100644 --- a/cln-rpc/src/model.rs +++ b/cln-rpc/src/model.rs @@ -3231,6 +3231,8 @@ pub mod responses { pub struct FeeratesPerkb { pub min_acceptable: u32, pub max_acceptable: u32, + #[serde(skip_serializing_if = "Option::is_none")] + pub floor: Option, #[serde(skip_serializing_if = "crate::is_none_or_empty")] pub estimates: Option>, #[serde(skip_serializing_if = "Option::is_none")] @@ -3263,6 +3265,8 @@ pub mod responses { pub struct FeeratesPerkw { pub min_acceptable: u32, pub max_acceptable: u32, + #[serde(skip_serializing_if = "Option::is_none")] + pub floor: Option, #[serde(skip_serializing_if = "crate::is_none_or_empty")] pub estimates: Option>, #[serde(skip_serializing_if = "Option::is_none")] diff --git a/contrib/pyln-testing/pyln/testing/grpc2py.py b/contrib/pyln-testing/pyln/testing/grpc2py.py index 5d2f1d8dd..00229aad6 100644 --- a/contrib/pyln-testing/pyln/testing/grpc2py.py +++ b/contrib/pyln-testing/pyln/testing/grpc2py.py @@ -741,6 +741,7 @@ def feerates_perkb2py(m): return remove_default({ "min_acceptable": m.min_acceptable, # PrimitiveField in generate_composite "max_acceptable": m.max_acceptable, # PrimitiveField in generate_composite + "floor": m.floor, # PrimitiveField in generate_composite "estimates": [feerates_perkb_estimates2py(i) for i in m.estimates], # ArrayField[composite] in generate_composite "opening": m.opening, # PrimitiveField in generate_composite "mutual_close": m.mutual_close, # PrimitiveField in generate_composite @@ -763,6 +764,7 @@ def feerates_perkw2py(m): return remove_default({ "min_acceptable": m.min_acceptable, # PrimitiveField in generate_composite "max_acceptable": m.max_acceptable, # PrimitiveField in generate_composite + "floor": m.floor, # PrimitiveField in generate_composite "estimates": [feerates_perkw_estimates2py(i) for i in m.estimates], # ArrayField[composite] in generate_composite "opening": m.opening, # PrimitiveField in generate_composite "mutual_close": m.mutual_close, # PrimitiveField in generate_composite diff --git a/doc/lightning-feerates.7.md b/doc/lightning-feerates.7.md index bcda750e5..76dcaece3 100644 --- a/doc/lightning-feerates.7.md +++ b/doc/lightning-feerates.7.md @@ -50,6 +50,7 @@ On success, an object is returned, containing: - **perkb** (object, optional): If *style* parameter was perkb: - **min\_acceptable** (u32): The smallest feerate that we allow peers to specify: half the 100-block estimate - **max\_acceptable** (u32): The largest feerate we will accept from remote negotiations. If a peer attempts to set the feerate higher than this we will unilaterally close the channel (or simply forget it if it's not open yet). + - **floor** (u32): The smallest feerate that our backend tells us it will accept (i.e. minrelayfee or mempoolminfee) *(added v23.05)* - **estimates** (array of objects): Feerate estimates from plugin which we are using (usuallly bcli) *(added v23.05)*: - **blockcount** (u32): The number of blocks the feerate is expected to get a transaction in *(added v23.05)* - **feerate** (u32): The feerate for this estimate, in given *style* *(added v23.05)* @@ -63,6 +64,7 @@ On success, an object is returned, containing: - **perkw** (object, optional): If *style* parameter was perkw: - **min\_acceptable** (u32): The smallest feerate that you can use, usually the minimum relayed feerate of the backend - **max\_acceptable** (u32): The largest feerate we will accept from remote negotiations. If a peer attempts to set the feerate higher than this we will unilaterally close the channel (or simply forget it if it's not open yet). + - **floor** (u32): The smallest feerate that our backend tells us it will accept (i.e. minrelayfee or mempoolminfee) *(added v23.05)* - **estimates** (array of objects): Feerate estimates from plugin which we are using (usuallly bcli) *(added v23.05)*: - **blockcount** (u32): The number of blocks the feerate is expected to get a transaction in *(added v23.05)* - **feerate** (u32): The feerate for this estimate, in given *style* *(added v23.05)* @@ -136,4 +138,4 @@ RESOURCES Main web site: -[comment]: # ( SHA256STAMP:c21d903c29fd6195d5890962eaa3265a26a57885b95714696916bd32168b66bc) +[comment]: # ( SHA256STAMP:4921275aec48da8b9ddcba5d4237efa72f06b6e005008f2c3aa7029d3bd187fd) diff --git a/doc/schemas/feerates.schema.json b/doc/schemas/feerates.schema.json index 29c45a69c..9cff8a8bb 100644 --- a/doc/schemas/feerates.schema.json +++ b/doc/schemas/feerates.schema.json @@ -15,6 +15,7 @@ "required": [ "min_acceptable", "max_acceptable", + "floor", "estimates" ], "properties": { @@ -26,6 +27,11 @@ "type": "u32", "description": "The largest feerate we will accept from remote negotiations. If a peer attempts to set the feerate higher than this we will unilaterally close the channel (or simply forget it if it's not open yet)." }, + "floor": { + "type": "u32", + "added": "v23.05", + "description": "The smallest feerate that our backend tells us it will accept (i.e. minrelayfee or mempoolminfee)" + }, "estimates": { "type": "array", "added": "v23.05", @@ -92,6 +98,7 @@ "required": [ "min_acceptable", "max_acceptable", + "floor", "estimates" ], "properties": { @@ -103,6 +110,11 @@ "type": "u32", "description": "The largest feerate we will accept from remote negotiations. If a peer attempts to set the feerate higher than this we will unilaterally close the channel (or simply forget it if it's not open yet)." }, + "floor": { + "type": "u32", + "added": "v23.05", + "description": "The smallest feerate that our backend tells us it will accept (i.e. minrelayfee or mempoolminfee)" + }, "estimates": { "type": "array", "added": "v23.05", diff --git a/lightningd/chaintopology.c b/lightningd/chaintopology.c index 78c3344ea..d9c76e477 100644 --- a/lightningd/chaintopology.c +++ b/lightningd/chaintopology.c @@ -667,6 +667,9 @@ static struct command_result *json_feerates(struct command *cmd, feerate_to_style(feerate_min(cmd->ld, NULL), *style)); json_add_u64(response, "max_acceptable", feerate_to_style(feerate_max(cmd->ld, NULL), *style)); + json_add_u64(response, "floor", + feerate_to_style(get_feerate_floor(cmd->ld->topology), + *style)); json_array_start(response, "estimates"); assert(tal_count(topo->smoothed_feerates) == tal_count(topo->feerates[0])); diff --git a/tests/test_misc.py b/tests/test_misc.py index b28cd8fcb..1601e476e 100644 --- a/tests/test_misc.py +++ b/tests/test_misc.py @@ -1532,13 +1532,14 @@ def test_feerates(node_factory): feerate = l1.rpc.parsefeerate(t) # Query feerates (shouldn't give any!) - wait_for(lambda: len(l1.rpc.feerates('perkw')['perkw']) == 3) + wait_for(lambda: len(l1.rpc.feerates('perkw')['perkw']) == 4) feerates = l1.rpc.feerates('perkw') assert feerates['warning_missing_feerates'] == 'Some fee estimates unavailable: bitcoind startup?' assert 'perkb' not in feerates assert feerates['perkw']['max_acceptable'] == 2**32 - 1 assert feerates['perkw']['min_acceptable'] == 253 assert feerates['perkw']['min_acceptable'] == 253 + assert feerates['perkw']['floor'] == 253 assert feerates['perkw']['estimates'] == [] for t in types: assert t not in feerates['perkw'] @@ -1548,6 +1549,8 @@ def test_feerates(node_factory): assert 'perkw' not in feerates assert feerates['perkb']['max_acceptable'] == (2**32 - 1) assert feerates['perkb']['min_acceptable'] == 253 * 4 + # Note: This is floored at the FEERATE_FLOOR constant (253) + assert feerates['perkb']['floor'] == 1012 assert feerates['perkb']['estimates'] == [] for t in types: assert t not in feerates['perkb'] @@ -1955,6 +1958,7 @@ def test_bitcoind_feerate_floor(node_factory, bitcoind): "penalty": 30000, "min_acceptable": 7500, "max_acceptable": 600000, + "floor": 1012, "estimates": [{"blockcount": 2, "feerate": 60000, "smoothed_feerate": 60000}, @@ -1993,6 +1997,7 @@ def test_bitcoind_feerate_floor(node_factory, bitcoind): # This has increased (rounded up) "min_acceptable": 20004, "max_acceptable": 600000, + "floor": 20004, "estimates": [{"blockcount": 2, "feerate": 60000, "smoothed_feerate": 60000}, @@ -2034,6 +2039,7 @@ def test_bitcoind_feerate_floor(node_factory, bitcoind): # This has increased (rounded up) "min_acceptable": 30004, "max_acceptable": 600000, + "floor": 30004, "estimates": [{"blockcount": 2, "feerate": 60000, "smoothed_feerate": 60000}, @@ -2987,7 +2993,8 @@ def test_force_feerates(node_factory): "penalty": 1111, "min_acceptable": 1875, "max_acceptable": 150000, - "estimates": estimates} + "estimates": estimates, + "floor": 253} l1.stop() l1.daemon.opts['force-feerates'] = '1111/2222' @@ -3001,7 +3008,8 @@ def test_force_feerates(node_factory): "penalty": 2222, "min_acceptable": 1875, "max_acceptable": 150000, - "estimates": estimates} + "estimates": estimates, + "floor": 253} l1.stop() l1.daemon.opts['force-feerates'] = '1111/2222/3333/4444/5555/6666' @@ -3015,7 +3023,8 @@ def test_force_feerates(node_factory): "penalty": 6666, "min_acceptable": 1875, "max_acceptable": 150000, - "estimates": estimates} + "estimates": estimates, + "floor": 253} def test_datastore_escapeing(node_factory):