diff --git a/.msggen.json b/.msggen.json index 5b58cc3bf..79601bd08 100644 --- a/.msggen.json +++ b/.msggen.json @@ -357,6 +357,7 @@ }, "FeeratesPerkb": { "Feerates.perkb.delayed_to_us": 6, + "Feerates.perkb.estimates[]": 9, "Feerates.perkb.htlc_resolution": 7, "Feerates.perkb.max_acceptable": 2, "Feerates.perkb.min_acceptable": 1, @@ -365,8 +366,14 @@ "Feerates.perkb.penalty": 8, "Feerates.perkb.unilateral_close": 5 }, + "FeeratesPerkbEstimates": { + "Feerates.perkb.estimates[].blockcount": 1, + "Feerates.perkb.estimates[].feerate": 2, + "Feerates.perkb.estimates[].smoothed_feerate": 3 + }, "FeeratesPerkw": { "Feerates.perkw.delayed_to_us": 6, + "Feerates.perkw.estimates[]": 9, "Feerates.perkw.htlc_resolution": 7, "Feerates.perkw.max_acceptable": 2, "Feerates.perkw.min_acceptable": 1, @@ -375,6 +382,11 @@ "Feerates.perkw.penalty": 8, "Feerates.perkw.unilateral_close": 5 }, + "FeeratesPerkwEstimates": { + "Feerates.perkw.estimates[].blockcount": 1, + "Feerates.perkw.estimates[].feerate": 2, + "Feerates.perkw.estimates[].smoothed_feerate": 3 + }, "FeeratesRequest": { "Feerates.style": 1 }, @@ -1542,11 +1554,27 @@ }, "Feerates.perkb.delayed_to_us": { "added": "pre-v0.10.1", + "deprecated": "v23.05" + }, + "Feerates.perkb.estimates[]": { + "added": "v23.05", + "deprecated": false + }, + "Feerates.perkb.estimates[].blockcount": { + "added": "v23.05", + "deprecated": false + }, + "Feerates.perkb.estimates[].feerate": { + "added": "v23.05", + "deprecated": false + }, + "Feerates.perkb.estimates[].smoothed_feerate": { + "added": "v23.05", "deprecated": false }, "Feerates.perkb.htlc_resolution": { "added": "pre-v0.10.1", - "deprecated": false + "deprecated": "v23.05" }, "Feerates.perkb.max_acceptable": { "added": "pre-v0.10.1", @@ -1578,11 +1606,27 @@ }, "Feerates.perkw.delayed_to_us": { "added": "pre-v0.10.1", + "deprecated": "v23.05" + }, + "Feerates.perkw.estimates[]": { + "added": "v23.05", + "deprecated": false + }, + "Feerates.perkw.estimates[].blockcount": { + "added": "v23.05", + "deprecated": false + }, + "Feerates.perkw.estimates[].feerate": { + "added": "v23.05", + "deprecated": false + }, + "Feerates.perkw.estimates[].smoothed_feerate": { + "added": "v23.05", "deprecated": false }, "Feerates.perkw.htlc_resolution": { "added": "pre-v0.10.1", - "deprecated": false + "deprecated": "v23.05" }, "Feerates.perkw.max_acceptable": { "added": "pre-v0.10.1", diff --git a/cln-grpc/proto/node.proto b/cln-grpc/proto/node.proto index 4f48ee7ec..982414fa0 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; + repeated FeeratesPerkbEstimates estimates = 9; optional uint32 opening = 3; optional uint32 mutual_close = 4; optional uint32 unilateral_close = 5; @@ -1138,9 +1139,16 @@ message FeeratesPerkb { optional uint32 penalty = 8; } +message FeeratesPerkbEstimates { + optional uint32 blockcount = 1; + optional uint32 feerate = 2; + optional uint32 smoothed_feerate = 3; +} + message FeeratesPerkw { uint32 min_acceptable = 1; uint32 max_acceptable = 2; + repeated FeeratesPerkwEstimates estimates = 9; optional uint32 opening = 3; optional uint32 mutual_close = 4; optional uint32 unilateral_close = 5; @@ -1149,6 +1157,12 @@ message FeeratesPerkw { optional uint32 penalty = 8; } +message FeeratesPerkwEstimates { + optional uint32 blockcount = 1; + optional uint32 feerate = 2; + optional uint32 smoothed_feerate = 3; +} + message FeeratesOnchain_fee_estimates { uint64 opening_channel_satoshis = 1; uint64 mutual_close_satoshis = 2; diff --git a/cln-grpc/src/convert.rs b/cln-grpc/src/convert.rs index 5e2039db7..17a456438 100644 --- a/cln-grpc/src/convert.rs +++ b/cln-grpc/src/convert.rs @@ -915,32 +915,60 @@ impl From for pb::DisconnectResponse { } } +#[allow(unused_variables,deprecated)] +impl From for pb::FeeratesPerkbEstimates { + fn from(c: responses::FeeratesPerkbEstimates) -> Self { + Self { + blockcount: c.blockcount, // Rule #2 for type u32? + feerate: c.feerate, // Rule #2 for type u32? + smoothed_feerate: c.smoothed_feerate, // Rule #2 for type u32? + } + } +} + #[allow(unused_variables,deprecated)] impl From for pb::FeeratesPerkb { fn from(c: responses::FeeratesPerkb) -> Self { Self { min_acceptable: c.min_acceptable, // Rule #2 for type u32 max_acceptable: c.max_acceptable, // 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? unilateral_close: c.unilateral_close, // Rule #2 for type u32? + #[allow(deprecated)] delayed_to_us: c.delayed_to_us, // Rule #2 for type u32? + #[allow(deprecated)] htlc_resolution: c.htlc_resolution, // Rule #2 for type u32? penalty: c.penalty, // Rule #2 for type u32? } } } +#[allow(unused_variables,deprecated)] +impl From for pb::FeeratesPerkwEstimates { + fn from(c: responses::FeeratesPerkwEstimates) -> Self { + Self { + blockcount: c.blockcount, // Rule #2 for type u32? + feerate: c.feerate, // Rule #2 for type u32? + smoothed_feerate: c.smoothed_feerate, // Rule #2 for type u32? + } + } +} + #[allow(unused_variables,deprecated)] impl From for pb::FeeratesPerkw { fn from(c: responses::FeeratesPerkw) -> Self { Self { min_acceptable: c.min_acceptable, // Rule #2 for type u32 max_acceptable: c.max_acceptable, // 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? unilateral_close: c.unilateral_close, // Rule #2 for type u32? + #[allow(deprecated)] delayed_to_us: c.delayed_to_us, // Rule #2 for type u32? + #[allow(deprecated)] htlc_resolution: c.htlc_resolution, // Rule #2 for type u32? penalty: c.penalty, // Rule #2 for type u32? } @@ -3252,12 +3280,24 @@ impl From for responses::DisconnectResponse { } } +#[allow(unused_variables,deprecated)] +impl From for responses::FeeratesPerkbEstimates { + fn from(c: pb::FeeratesPerkbEstimates) -> Self { + Self { + blockcount: c.blockcount, // Rule #1 for type u32? + feerate: c.feerate, // Rule #1 for type u32? + smoothed_feerate: c.smoothed_feerate, // Rule #1 for type u32? + } + } +} + #[allow(unused_variables,deprecated)] impl From for responses::FeeratesPerkb { fn from(c: pb::FeeratesPerkb) -> Self { Self { min_acceptable: c.min_acceptable, // Rule #1 for type u32 max_acceptable: c.max_acceptable, // 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? unilateral_close: c.unilateral_close, // Rule #1 for type u32? @@ -3268,12 +3308,24 @@ impl From for responses::FeeratesPerkb { } } +#[allow(unused_variables,deprecated)] +impl From for responses::FeeratesPerkwEstimates { + fn from(c: pb::FeeratesPerkwEstimates) -> Self { + Self { + blockcount: c.blockcount, // Rule #1 for type u32? + feerate: c.feerate, // Rule #1 for type u32? + smoothed_feerate: c.smoothed_feerate, // Rule #1 for type u32? + } + } +} + #[allow(unused_variables,deprecated)] impl From for responses::FeeratesPerkw { fn from(c: pb::FeeratesPerkw) -> Self { Self { min_acceptable: c.min_acceptable, // Rule #1 for type u32 max_acceptable: c.max_acceptable, // 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? unilateral_close: c.unilateral_close, // Rule #1 for type u32? diff --git a/cln-rpc/src/model.rs b/cln-rpc/src/model.rs index a79780216..3b7855863 100644 --- a/cln-rpc/src/model.rs +++ b/cln-rpc/src/model.rs @@ -3217,36 +3217,64 @@ pub mod responses { } } + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct FeeratesPerkbEstimates { + #[serde(skip_serializing_if = "Option::is_none")] + pub blockcount: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub feerate: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub smoothed_feerate: Option, + } + #[derive(Clone, Debug, Deserialize, Serialize)] pub struct FeeratesPerkb { pub min_acceptable: u32, pub max_acceptable: u32, + #[serde(skip_serializing_if = "crate::is_none_or_empty")] + pub estimates: Option>, #[serde(skip_serializing_if = "Option::is_none")] pub opening: Option, #[serde(skip_serializing_if = "Option::is_none")] pub mutual_close: Option, #[serde(skip_serializing_if = "Option::is_none")] pub unilateral_close: Option, + #[deprecated] #[serde(skip_serializing_if = "Option::is_none")] pub delayed_to_us: Option, + #[deprecated] #[serde(skip_serializing_if = "Option::is_none")] pub htlc_resolution: Option, #[serde(skip_serializing_if = "Option::is_none")] pub penalty: Option, } + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct FeeratesPerkwEstimates { + #[serde(skip_serializing_if = "Option::is_none")] + pub blockcount: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub feerate: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub smoothed_feerate: Option, + } + #[derive(Clone, Debug, Deserialize, Serialize)] pub struct FeeratesPerkw { pub min_acceptable: u32, pub max_acceptable: u32, + #[serde(skip_serializing_if = "crate::is_none_or_empty")] + pub estimates: Option>, #[serde(skip_serializing_if = "Option::is_none")] pub opening: Option, #[serde(skip_serializing_if = "Option::is_none")] pub mutual_close: Option, #[serde(skip_serializing_if = "Option::is_none")] pub unilateral_close: Option, + #[deprecated] #[serde(skip_serializing_if = "Option::is_none")] pub delayed_to_us: Option, + #[deprecated] #[serde(skip_serializing_if = "Option::is_none")] pub htlc_resolution: Option, #[serde(skip_serializing_if = "Option::is_none")] diff --git a/common/jsonrpc_errors.h b/common/jsonrpc_errors.h index 899081765..6744e87d3 100644 --- a/common/jsonrpc_errors.h +++ b/common/jsonrpc_errors.h @@ -71,6 +71,7 @@ enum jsonrpc_errcode { /* bitcoin-cli plugin errors */ BCLI_ERROR = 500, + BCLI_NO_FEE_ESTIMATES = 501, /* Errors from `invoice` or `delinvoice` commands */ INVOICE_LABEL_ALREADY_EXISTS = 900, diff --git a/contrib/pyln-testing/pyln/testing/grpc2py.py b/contrib/pyln-testing/pyln/testing/grpc2py.py index b82591ec6..5d2f1d8dd 100644 --- a/contrib/pyln-testing/pyln/testing/grpc2py.py +++ b/contrib/pyln-testing/pyln/testing/grpc2py.py @@ -729,10 +729,19 @@ def disconnect2py(m): }) +def feerates_perkb_estimates2py(m): + return remove_default({ + "blockcount": m.blockcount, # PrimitiveField in generate_composite + "feerate": m.feerate, # PrimitiveField in generate_composite + "smoothed_feerate": m.smoothed_feerate, # PrimitiveField in generate_composite + }) + + 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 + "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 "unilateral_close": m.unilateral_close, # PrimitiveField in generate_composite @@ -742,10 +751,19 @@ def feerates_perkb2py(m): }) +def feerates_perkw_estimates2py(m): + return remove_default({ + "blockcount": m.blockcount, # PrimitiveField in generate_composite + "feerate": m.feerate, # PrimitiveField in generate_composite + "smoothed_feerate": m.smoothed_feerate, # PrimitiveField in generate_composite + }) + + 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 + "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 "unilateral_close": m.unilateral_close, # PrimitiveField in generate_composite diff --git a/doc/lightning-feerates.7.md b/doc/lightning-feerates.7.md index e047e72fe..3344f4b92 100644 --- a/doc/lightning-feerates.7.md +++ b/doc/lightning-feerates.7.md @@ -50,25 +50,33 @@ 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). + - **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)* + - **smoothed\_feerate** (u32): The feerate, smoothed over time (useful for coordinating with other nodes) *(added v23.05)* - **opening** (u32, optional): Default feerate for lightning-fundchannel(7) and lightning-withdraw(7) - **mutual\_close** (u32, optional): Feerate to aim for in cooperative shutdown. Note that since mutual close is a **negotiation**, the actual feerate used in mutual close will be somewhere between this and the corresponding mutual close feerate of the peer. - **unilateral\_close** (u32, optional): Feerate for commitment\_transaction in a live channel which we originally funded - - **delayed\_to\_us** (u32, optional): Feerate for returning unilateral close funds to our wallet - - **htlc\_resolution** (u32, optional): Feerate for returning unilateral close HTLC outputs to our wallet - - **penalty** (u32, optional): Feerate to start at when penalizing a cheat attempt + - **delayed\_to\_us** (u32, optional): Feerate for returning unilateral close funds to our wallet **deprecated, removal in v24.02** + - **htlc\_resolution** (u32, optional): Feerate for returning unilateral close HTLC outputs to our wallet **deprecated, removal in v24.02** + - **penalty** (u32, optional): Feerate to use when creating penalty tx for watchtowers - **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). + - **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)* + - **smoothed\_feerate** (u32): The feerate, smoothed over time (useful for coordinating with other nodes) *(added v23.05)* - **opening** (u32, optional): Default feerate for lightning-fundchannel(7) and lightning-withdraw(7) - **mutual\_close** (u32, optional): Feerate to aim for in cooperative shutdown. Note that since mutual close is a **negotiation**, the actual feerate used in mutual close will be somewhere between this and the corresponding mutual close feerate of the peer. - **unilateral\_close** (u32, optional): Feerate for commitment\_transaction in a live channel which we originally funded - - **delayed\_to\_us** (u32, optional): Feerate for returning unilateral close funds to our wallet - - **htlc\_resolution** (u32, optional): Feerate for returning unilateral close HTLC outputs to our wallet - - **penalty** (u32, optional): Feerate to start at when penalizing a cheat attempt + - **delayed\_to\_us** (u32, optional): Feerate for returning unilateral close funds to our wallet **deprecated, removal in v24.02** + - **htlc\_resolution** (u32, optional): Feerate for returning unilateral close HTLC outputs to our wallet **deprecated, removal in v24.02** + - **penalty** (u32, optional): Feerate to use when creating penalty tx for watchtowers - **onchain\_fee\_estimates** (object, optional): - **opening\_channel\_satoshis** (u64): Estimated cost of typical channel open - **mutual\_close\_satoshis** (u64): Estimated cost of typical channel close - - **unilateral\_close\_satoshis** (u64): Estimated cost of typical unilateral close (without HTLCs) + - **unilateral\_close\_satoshis** (u64): Estimated cost of typical (non-anchor) unilateral close (without HTLCs) - **htlc\_timeout\_satoshis** (u64): Estimated cost of typical HTLC timeout transaction - **htlc\_success\_satoshis** (u64): Estimated cost of typical HTLC fulfillment transaction @@ -121,4 +129,4 @@ RESOURCES Main web site: -[comment]: # ( SHA256STAMP:773e4e66cb3654b7c3aafe54c33d433c52ff89f7a5a8be0a71a93da21a6b7eaa) +[comment]: # ( SHA256STAMP:c21d903c29fd6195d5890962eaa3265a26a57885b95714696916bd32168b66bc) diff --git a/doc/schemas/feerates.schema.json b/doc/schemas/feerates.schema.json index d7019f17d..29c45a69c 100644 --- a/doc/schemas/feerates.schema.json +++ b/doc/schemas/feerates.schema.json @@ -14,7 +14,8 @@ "additionalProperties": false, "required": [ "min_acceptable", - "max_acceptable" + "max_acceptable", + "estimates" ], "properties": { "min_acceptable": { @@ -25,6 +26,37 @@ "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)." }, + "estimates": { + "type": "array", + "added": "v23.05", + "description": "Feerate estimates from plugin which we are using (usuallly bcli)", + "items": { + "type": "object", + "additionalProperties": false, + "required": [ + "blockcount", + "feerate", + "smoothed_feerate" + ], + "properties": { + "blockcount": { + "type": "u32", + "added": "v23.05", + "description": "The number of blocks the feerate is expected to get a transaction in" + }, + "feerate": { + "type": "u32", + "added": "v23.05", + "description": "The feerate for this estimate, in given *style*" + }, + "smoothed_feerate": { + "type": "u32", + "added": "v23.05", + "description": "The feerate, smoothed over time (useful for coordinating with other nodes)" + } + } + } + }, "opening": { "type": "u32", "description": "Default feerate for lightning-fundchannel(7) and lightning-withdraw(7)" @@ -39,15 +71,17 @@ }, "delayed_to_us": { "type": "u32", + "deprecated": "v23.05", "description": "Feerate for returning unilateral close funds to our wallet" }, "htlc_resolution": { "type": "u32", + "deprecated": "v23.05", "description": "Feerate for returning unilateral close HTLC outputs to our wallet" }, "penalty": { "type": "u32", - "description": "Feerate to start at when penalizing a cheat attempt" + "description": "Feerate to use when creating penalty tx for watchtowers" } } }, @@ -57,7 +91,8 @@ "additionalProperties": false, "required": [ "min_acceptable", - "max_acceptable" + "max_acceptable", + "estimates" ], "properties": { "min_acceptable": { @@ -68,6 +103,37 @@ "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)." }, + "estimates": { + "type": "array", + "added": "v23.05", + "description": "Feerate estimates from plugin which we are using (usuallly bcli)", + "items": { + "type": "object", + "additionalProperties": false, + "required": [ + "blockcount", + "feerate", + "smoothed_feerate" + ], + "properties": { + "blockcount": { + "type": "u32", + "added": "v23.05", + "description": "The number of blocks the feerate is expected to get a transaction in" + }, + "feerate": { + "type": "u32", + "added": "v23.05", + "description": "The feerate for this estimate, in given *style*" + }, + "smoothed_feerate": { + "type": "u32", + "added": "v23.05", + "description": "The feerate, smoothed over time (useful for coordinating with other nodes)" + } + } + } + }, "opening": { "type": "u32", "description": "Default feerate for lightning-fundchannel(7) and lightning-withdraw(7)" @@ -82,15 +148,17 @@ }, "delayed_to_us": { "type": "u32", + "deprecated": "v23.05", "description": "Feerate for returning unilateral close funds to our wallet" }, "htlc_resolution": { "type": "u32", + "deprecated": "v23.05", "description": "Feerate for returning unilateral close HTLC outputs to our wallet" }, "penalty": { "type": "u32", - "description": "Feerate to start at when penalizing a cheat attempt" + "description": "Feerate to use when creating penalty tx for watchtowers" } } }, @@ -115,7 +183,7 @@ }, "unilateral_close_satoshis": { "type": "u64", - "description": "Estimated cost of typical unilateral close (without HTLCs)" + "description": "Estimated cost of typical (non-anchor) unilateral close (without HTLCs)" }, "htlc_timeout_satoshis": { "type": "u64", diff --git a/lightningd/chaintopology.c b/lightningd/chaintopology.c index 041a205dd..3b56c465c 100644 --- a/lightningd/chaintopology.c +++ b/lightningd/chaintopology.c @@ -5,6 +5,7 @@ #include #include #include +#include #include #include #include @@ -545,34 +546,66 @@ static void start_fee_estimate(struct chain_topology *topo) bitcoind_estimate_fees(topo->bitcoind, update_feerates); } +struct rate_conversion { + u32 blockcount; +}; + +static struct rate_conversion conversions[] = { + [FEERATE_OPENING] = { 12 }, + [FEERATE_MUTUAL_CLOSE] = { 100 }, + [FEERATE_UNILATERAL_CLOSE] = { 6 }, + [FEERATE_DELAYED_TO_US] = { 12 }, + [FEERATE_HTLC_RESOLUTION] = { 6 }, + [FEERATE_PENALTY] = { 12 }, +}; + u32 opening_feerate(struct chain_topology *topo) { - return try_get_feerate(topo, FEERATE_OPENING); + if (topo->ld->force_feerates) + return topo->ld->force_feerates[FEERATE_OPENING]; + return feerate_for_deadline(topo, + conversions[FEERATE_OPENING].blockcount); } u32 mutual_close_feerate(struct chain_topology *topo) { - return try_get_feerate(topo, FEERATE_MUTUAL_CLOSE); + if (topo->ld->force_feerates) + return topo->ld->force_feerates[FEERATE_MUTUAL_CLOSE]; + return smoothed_feerate_for_deadline(topo, + conversions[FEERATE_MUTUAL_CLOSE].blockcount); } u32 unilateral_feerate(struct chain_topology *topo) { - return try_get_feerate(topo, FEERATE_UNILATERAL_CLOSE); + if (topo->ld->force_feerates) + return topo->ld->force_feerates[FEERATE_UNILATERAL_CLOSE]; + return smoothed_feerate_for_deadline(topo, + conversions[FEERATE_UNILATERAL_CLOSE].blockcount) + * topo->ld->config.commit_fee_percent / 100; } u32 delayed_to_us_feerate(struct chain_topology *topo) { - return try_get_feerate(topo, FEERATE_DELAYED_TO_US); + if (topo->ld->force_feerates) + return topo->ld->force_feerates[FEERATE_DELAYED_TO_US]; + return smoothed_feerate_for_deadline(topo, + conversions[FEERATE_DELAYED_TO_US].blockcount); } u32 htlc_resolution_feerate(struct chain_topology *topo) { - return try_get_feerate(topo, FEERATE_HTLC_RESOLUTION); + if (topo->ld->force_feerates) + return topo->ld->force_feerates[FEERATE_HTLC_RESOLUTION]; + return smoothed_feerate_for_deadline(topo, + conversions[FEERATE_HTLC_RESOLUTION].blockcount); } u32 penalty_feerate(struct chain_topology *topo) { - return try_get_feerate(topo, FEERATE_PENALTY); + if (topo->ld->force_feerates) + return topo->ld->force_feerates[FEERATE_PENALTY]; + return smoothed_feerate_for_deadline(topo, + conversions[FEERATE_PENALTY].blockcount); } u32 get_feerate_floor(const struct chain_topology *topo) @@ -588,39 +621,68 @@ static struct command_result *json_feerates(struct command *cmd, { struct chain_topology *topo = cmd->ld->topology; struct json_stream *response; - u32 feerates[NUM_FEERATES]; bool missing; enum feerate_style *style; + u32 rate; if (!param(cmd, buffer, params, p_req("style", param_feerate_style, &style), NULL)) return command_param_failed(); - missing = false; - for (size_t i = 0; i < ARRAY_SIZE(feerates); i++) { - feerates[i] = try_get_feerate(topo, i); - if (!feerates[i]) - missing = true; - } + missing = (tal_count(topo->feerates[0]) == 0); response = json_stream_success(cmd); - if (missing) json_add_string(response, "warning_missing_feerates", "Some fee estimates unavailable: bitcoind startup?"); json_object_start(response, feerate_style_name(*style)); - for (size_t i = 0; i < ARRAY_SIZE(feerates); i++) { - if (!feerates[i] || i == FEERATE_MIN || i == FEERATE_MAX) - continue; - json_add_num(response, feerate_name(i), - feerate_to_style(feerates[i], *style)); + rate = opening_feerate(topo); + if (rate) + json_add_num(response, "opening", feerate_to_style(rate, *style)); + rate = mutual_close_feerate(topo); + if (rate) + json_add_num(response, "mutual_close", + feerate_to_style(rate, *style)); + rate = unilateral_feerate(topo); + if (rate) + json_add_num(response, "unilateral_close", + feerate_to_style(rate, *style)); + rate = penalty_feerate(topo); + if (rate) + json_add_num(response, "penalty", + feerate_to_style(rate, *style)); + if (deprecated_apis) { + rate = delayed_to_us_feerate(topo); + if (rate) + json_add_num(response, "delayed_to_us", + feerate_to_style(rate, *style)); + rate = htlc_resolution_feerate(topo); + if (rate) + json_add_num(response, "htlc_resolution", + feerate_to_style(rate, *style)); } + json_add_u64(response, "min_acceptable", 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_array_start(response, "estimates"); + assert(tal_count(topo->smoothed_feerates) == tal_count(topo->feerates[0])); + for (size_t i = 0; i < tal_count(topo->feerates[0]); i++) { + json_object_start(response, NULL); + json_add_num(response, "blockcount", + topo->feerates[0][i].blockcount); + json_add_u64(response, "feerate", + feerate_to_style(topo->feerates[0][i].rate, *style)); + json_add_u64(response, "smoothed_feerate", + feerate_to_style(topo->smoothed_feerates[i].rate, + *style)); + json_object_end(response); + } + json_array_end(response); json_object_end(response); if (!missing) { @@ -998,62 +1060,9 @@ u32 get_network_blockheight(const struct chain_topology *topo) return topo->headercount; } -struct rate_conversion { - u32 blockcount; -}; - -static struct rate_conversion conversions[] = { - [FEERATE_OPENING] = { 12 }, - [FEERATE_MUTUAL_CLOSE] = { 100 }, - [FEERATE_UNILATERAL_CLOSE] = { 6 }, - [FEERATE_DELAYED_TO_US] = { 12 }, - [FEERATE_HTLC_RESOLUTION] = { 6 }, - [FEERATE_PENALTY] = { 12 }, -}; - -u32 try_get_feerate(const struct chain_topology *topo, enum feerate feerate) -{ - u32 val; - - /* Max and min look over history as well. */ - if (feerate == FEERATE_MAX) { - u32 max = 0; - for (size_t i = 0; i < ARRAY_SIZE(topo->feerates); i++) { - for (size_t j = 0; j < tal_count(topo->feerates[i]); j++) { - if (topo->feerates[i][j].rate > max) - max = topo->feerates[i][j].rate; - } - } - return max * topo->ld->config.max_fee_multiplier; - } - - if (feerate == FEERATE_MIN) { - u32 min = 0xFFFFFFFF; - for (size_t i = 0; i < ARRAY_SIZE(topo->feerates); i++) { - for (size_t j = 0; j < tal_count(topo->feerates[i]); j++) { - if (topo->feerates[i][j].rate < min) - min = topo->feerates[i][j].rate; - } - } - if (min == 0xFFFFFFFF) - return 0; - /* FIXME: This is what bcli used to do: halve the slow feerate! */ - min /= 2; - return min; - } - - if (topo->ld->force_feerates) - val = topo->ld->force_feerates[feerate]; - else - val = smoothed_feerate_for_deadline(topo, conversions[feerate].blockcount); - if (feerate == FEERATE_UNILATERAL_CLOSE) - val = val * topo->ld->config.commit_fee_percent / 100; - - return val; -} - u32 feerate_min(struct lightningd *ld, bool *unknown) { + const struct chain_topology *topo = ld->topology; u32 min; if (unknown) @@ -1063,21 +1072,32 @@ u32 feerate_min(struct lightningd *ld, bool *unknown) if (ld->config.ignore_fee_limits) min = 1; else { - min = try_get_feerate(ld->topology, FEERATE_MIN); - if (!min) { + min = 0xFFFFFFFF; + for (size_t i = 0; i < ARRAY_SIZE(topo->feerates); i++) { + for (size_t j = 0; j < tal_count(topo->feerates[i]); j++) { + if (topo->feerates[i][j].rate < min) + min = topo->feerates[i][j].rate; + } + } + if (min == 0xFFFFFFFF) { if (unknown) *unknown = true; + min = 0; } + + /* FIXME: This is what bcli used to do: halve the slow feerate! */ + min /= 2; } - if (min < get_feerate_floor(ld->topology)) - return get_feerate_floor(ld->topology); + if (min < get_feerate_floor(topo)) + return get_feerate_floor(topo); return min; } u32 feerate_max(struct lightningd *ld, bool *unknown) { - u32 feerate; + const struct chain_topology *topo = ld->topology; + u32 max = 0; if (unknown) *unknown = false; @@ -1085,14 +1105,18 @@ u32 feerate_max(struct lightningd *ld, bool *unknown) if (ld->config.ignore_fee_limits) return UINT_MAX; - /* If we don't know feerate, don't limit other side. */ - feerate = try_get_feerate(ld->topology, FEERATE_MAX); - if (!feerate) { + for (size_t i = 0; i < ARRAY_SIZE(topo->feerates); i++) { + for (size_t j = 0; j < tal_count(topo->feerates[i]); j++) { + if (topo->feerates[i][j].rate > max) + max = topo->feerates[i][j].rate; + } + } + if (!max) { if (unknown) *unknown = true; return UINT_MAX; } - return feerate; + return max * topo->ld->config.max_fee_multiplier; } /* On shutdown, channels get deleted last. That frees from our list, so diff --git a/lightningd/chaintopology.h b/lightningd/chaintopology.h index b123885e8..a90b50c23 100644 --- a/lightningd/chaintopology.h +++ b/lightningd/chaintopology.h @@ -181,14 +181,12 @@ u32 get_network_blockheight(const struct chain_topology *topo); u32 feerate_for_deadline(const struct chain_topology *topo, u32 blockcount); u32 smoothed_feerate_for_deadline(const struct chain_topology *topo, u32 blockcount); -/* Get fee rate in satoshi per kiloweight, or 0 if unavailable! */ -u32 try_get_feerate(const struct chain_topology *topo, enum feerate feerate); - /* Get range of feerates to insist other side abide by for normal channels. * If we have to guess, sets *unknown to true, otherwise false. */ u32 feerate_min(struct lightningd *ld, bool *unknown); u32 feerate_max(struct lightningd *ld, bool *unknown); +/* These return 0 if unknown */ u32 opening_feerate(struct chain_topology *topo); u32 mutual_close_feerate(struct chain_topology *topo); u32 unilateral_feerate(struct chain_topology *topo); diff --git a/lightningd/channel_control.c b/lightningd/channel_control.c index adee64850..2bbcef4aa 100644 --- a/lightningd/channel_control.c +++ b/lightningd/channel_control.c @@ -38,12 +38,12 @@ static void update_feerates(struct lightningd *ld, struct channel *channel) feerate, feerate_min(ld, NULL), feerate_max(ld, NULL), - try_get_feerate(ld->topology, FEERATE_PENALTY)); + penalty_feerate(ld->topology)); msg = towire_channeld_feerates(NULL, feerate, feerate_min(ld, NULL), feerate_max(ld, NULL), - try_get_feerate(ld->topology, FEERATE_PENALTY)); + penalty_feerate(ld->topology)); subd_send_msg(channel->owner, take(msg)); } @@ -736,7 +736,7 @@ bool peer_start_channeld(struct channel *channel, channel->fee_states, feerate_min(ld, NULL), feerate_max(ld, NULL), - try_get_feerate(ld->topology, FEERATE_PENALTY), + penalty_feerate(ld->topology), &channel->last_sig, &channel->channel_info.remote_fundingkey, &channel->channel_info.theirbase, @@ -1146,8 +1146,7 @@ static struct command_result *json_dev_feerate(struct command *cmd, msg = towire_channeld_feerates(NULL, *feerate, feerate_min(cmd->ld, NULL), feerate_max(cmd->ld, NULL), - try_get_feerate(cmd->ld->topology, - FEERATE_PENALTY)); + penalty_feerate(cmd->ld->topology)); subd_send_msg(channel->owner, take(msg)); response = json_stream_success(cmd); diff --git a/lightningd/feerate.c b/lightningd/feerate.c index dd0600321..d0e0a277b 100644 --- a/lightningd/feerate.c +++ b/lightningd/feerate.c @@ -1,4 +1,5 @@ #include "config.h" +#include #include #include #include @@ -45,36 +46,100 @@ struct command_result *param_feerate_style(struct command *cmd, json_tok_full_len(tok), json_tok_full(buffer, tok)); } +/* This can set **feerate to 0, if it's unknown. */ +static struct command_result *param_feerate_unchecked(struct command *cmd, + const char *name, + const char *buffer, + const jsmntok_t *tok, + u32 **feerate) +{ + *feerate = tal(cmd, u32); + + if (json_tok_streq(buffer, tok, "opening")) { + **feerate = opening_feerate(cmd->ld->topology); + return NULL; + } + if (json_tok_streq(buffer, tok, "mutual_close")) { + **feerate = mutual_close_feerate(cmd->ld->topology); + return NULL; + } + if (json_tok_streq(buffer, tok, "penalty")) { + **feerate = penalty_feerate(cmd->ld->topology); + return NULL; + } + if (json_tok_streq(buffer, tok, "unilateral_close")) { + **feerate = unilateral_feerate(cmd->ld->topology); + return NULL; + } + + /* Other names are deprecated */ + for (size_t i = 0; i < NUM_FEERATES; i++) { + bool unknown; + + if (!json_tok_streq(buffer, tok, feerate_name(i))) + continue; + if (!deprecated_apis) + return command_fail_badparam(cmd, name, buffer, tok, + "removed feerate by names"); + switch (i) { + case FEERATE_OPENING: + case FEERATE_MUTUAL_CLOSE: + case FEERATE_PENALTY: + case FEERATE_UNILATERAL_CLOSE: + /* Handled above */ + abort(); + case FEERATE_DELAYED_TO_US: + **feerate = delayed_to_us_feerate(cmd->ld->topology); + return NULL; + case FEERATE_HTLC_RESOLUTION: + **feerate = htlc_resolution_feerate(cmd->ld->topology); + return NULL; + case FEERATE_MAX: + **feerate = feerate_max(cmd->ld, &unknown); + if (unknown) + **feerate = 0; + return NULL; + case FEERATE_MIN: + **feerate = feerate_min(cmd->ld, &unknown); + if (unknown) + **feerate = 0; + return NULL; + } + abort(); + } + + /* We used SLOW, NORMAL, and URGENT as feerate targets previously, + * and many commands rely on this syntax now. + * It's also really more natural for an user interface. */ + if (json_tok_streq(buffer, tok, "slow")) { + **feerate = feerate_for_deadline(cmd->ld->topology, 100); + return NULL; + } else if (json_tok_streq(buffer, tok, "normal")) { + **feerate = feerate_for_deadline(cmd->ld->topology, 12); + return NULL; + } else if (json_tok_streq(buffer, tok, "urgent")) { + **feerate = feerate_for_deadline(cmd->ld->topology, 6); + return NULL; + } + + /* It's a number... */ + tal_free(*feerate); + return param_feerate_val(cmd, name, buffer, tok, feerate); +} + struct command_result *param_feerate(struct command *cmd, const char *name, const char *buffer, const jsmntok_t *tok, u32 **feerate) { - for (size_t i = 0; i < NUM_FEERATES; i++) { - if (json_tok_streq(buffer, tok, feerate_name(i))) - return param_feerate_estimate(cmd, feerate, i); - } - /* We used SLOW, NORMAL, and URGENT as feerate targets previously, - * and many commands rely on this syntax now. - * It's also really more natural for an user interface. */ - if (json_tok_streq(buffer, tok, "slow")) - return param_feerate_estimate(cmd, feerate, FEERATE_MIN); - else if (json_tok_streq(buffer, tok, "normal")) - return param_feerate_estimate(cmd, feerate, FEERATE_OPENING); - else if (json_tok_streq(buffer, tok, "urgent")) - return param_feerate_estimate(cmd, feerate, FEERATE_UNILATERAL_CLOSE); + struct command_result *ret; - /* It's a number... */ - return param_feerate_val(cmd, name, buffer, tok, feerate); -} + ret = param_feerate_unchecked(cmd, name, buffer, tok, feerate); + if (ret) + return ret; -struct command_result *param_feerate_estimate(struct command *cmd, - u32 **feerate_per_kw, - enum feerate feerate) -{ - *feerate_per_kw = tal(cmd, u32); - **feerate_per_kw = try_get_feerate(cmd->ld->topology, feerate); - if (!**feerate_per_kw) - return command_fail(cmd, LIGHTNINGD, "Cannot estimate fees"); + if (**feerate == 0) + return command_fail(cmd, BCLI_NO_FEE_ESTIMATES, + "Cannot estimate fees (yet)"); return NULL; } diff --git a/lightningd/feerate.h b/lightningd/feerate.h index 80ab365f8..ca0793d5b 100644 --- a/lightningd/feerate.h +++ b/lightningd/feerate.h @@ -28,11 +28,6 @@ struct command_result *param_feerate_style(struct command *cmd, const jsmntok_t *tok, enum feerate_style **style); -/* Set feerate_per_kw to this estimate & return NULL, or fail cmd */ -struct command_result *param_feerate_estimate(struct command *cmd, - u32 **feerate_per_kw, - enum feerate feerate); - /* Extract a feerate with optional style suffix. */ struct command_result *param_feerate_val(struct command *cmd, const char *name, const char *buffer, diff --git a/lightningd/test/run-jsonrpc.c b/lightningd/test/run-jsonrpc.c index 0919a3a93..87475b204 100644 --- a/lightningd/test/run-jsonrpc.c +++ b/lightningd/test/run-jsonrpc.c @@ -13,11 +13,23 @@ void db_begin_transaction_(struct db *db UNNEEDED, const char *location UNNEEDED /* Generated stub for db_commit_transaction */ void db_commit_transaction(struct db *db UNNEEDED) { fprintf(stderr, "db_commit_transaction called!\n"); abort(); } +/* Generated stub for delayed_to_us_feerate */ +u32 delayed_to_us_feerate(struct chain_topology *topo UNNEEDED) +{ fprintf(stderr, "delayed_to_us_feerate called!\n"); abort(); } /* Generated stub for deprecated_apis */ bool deprecated_apis; /* Generated stub for fatal */ void fatal(const char *fmt UNNEEDED, ...) { fprintf(stderr, "fatal called!\n"); abort(); } +/* Generated stub for feerate_for_deadline */ +u32 feerate_for_deadline(const struct chain_topology *topo UNNEEDED, u32 blockcount UNNEEDED) +{ fprintf(stderr, "feerate_for_deadline called!\n"); abort(); } +/* Generated stub for feerate_max */ +u32 feerate_max(struct lightningd *ld UNNEEDED, bool *unknown UNNEEDED) +{ fprintf(stderr, "feerate_max called!\n"); abort(); } +/* Generated stub for feerate_min */ +u32 feerate_min(struct lightningd *ld UNNEEDED, bool *unknown UNNEEDED) +{ fprintf(stderr, "feerate_min called!\n"); abort(); } /* Generated stub for fromwire_bigsize */ bigsize_t fromwire_bigsize(const u8 **cursor UNNEEDED, size_t *max UNNEEDED) { fprintf(stderr, "fromwire_bigsize called!\n"); abort(); } @@ -28,6 +40,9 @@ bool fromwire_channel_id(const u8 **cursor UNNEEDED, size_t *max UNNEEDED, /* Generated stub for fromwire_node_id */ void fromwire_node_id(const u8 **cursor UNNEEDED, size_t *max UNNEEDED, struct node_id *id UNNEEDED) { fprintf(stderr, "fromwire_node_id called!\n"); abort(); } +/* Generated stub for htlc_resolution_feerate */ +u32 htlc_resolution_feerate(struct chain_topology *topo UNNEEDED) +{ fprintf(stderr, "htlc_resolution_feerate called!\n"); abort(); } /* Generated stub for json_to_jsonrpc_errcode */ bool json_to_jsonrpc_errcode(const char *buffer UNNEEDED, const jsmntok_t *tok UNNEEDED, enum jsonrpc_errcode *errcode UNNEEDED) @@ -52,6 +67,9 @@ void log_io(struct log *log UNNEEDED, enum log_level dir UNNEEDED, /* Generated stub for log_level_name */ const char *log_level_name(enum log_level level UNNEEDED) { fprintf(stderr, "log_level_name called!\n"); abort(); } +/* Generated stub for mutual_close_feerate */ +u32 mutual_close_feerate(struct chain_topology *topo UNNEEDED) +{ fprintf(stderr, "mutual_close_feerate called!\n"); abort(); } /* Generated stub for new_log */ struct log *new_log(const tal_t *ctx UNNEEDED, struct log_book *record UNNEEDED, const struct node_id *default_node_id UNNEEDED, @@ -63,6 +81,9 @@ struct oneshot *new_reltimer_(struct timers *timers UNNEEDED, struct timerel expire UNNEEDED, void (*cb)(void *) UNNEEDED, void *arg UNNEEDED) { fprintf(stderr, "new_reltimer_ called!\n"); abort(); } +/* Generated stub for opening_feerate */ +u32 opening_feerate(struct chain_topology *topo UNNEEDED) +{ fprintf(stderr, "opening_feerate called!\n"); abort(); } /* Generated stub for param */ bool param(struct command *cmd UNNEEDED, const char *buffer UNNEEDED, const jsmntok_t params[] UNNEEDED, ...) @@ -97,6 +118,9 @@ const char *param_subcommand(struct command *cmd UNNEEDED, const char *buffer UN const jsmntok_t tokens[] UNNEEDED, const char *name UNNEEDED, ...) { fprintf(stderr, "param_subcommand called!\n"); abort(); } +/* Generated stub for penalty_feerate */ +u32 penalty_feerate(struct chain_topology *topo UNNEEDED) +{ fprintf(stderr, "penalty_feerate called!\n"); abort(); } /* Generated stub for plugin_hook_call_ */ bool plugin_hook_call_(struct lightningd *ld UNNEEDED, const struct plugin_hook *hook UNNEEDED, @@ -112,9 +136,9 @@ void towire_channel_id(u8 **pptr UNNEEDED, const struct channel_id *channel_id U /* Generated stub for towire_node_id */ void towire_node_id(u8 **pptr UNNEEDED, const struct node_id *id UNNEEDED) { fprintf(stderr, "towire_node_id called!\n"); abort(); } -/* Generated stub for try_get_feerate */ -u32 try_get_feerate(const struct chain_topology *topo UNNEEDED, enum feerate feerate UNNEEDED) -{ fprintf(stderr, "try_get_feerate called!\n"); abort(); } +/* Generated stub for unilateral_feerate */ +u32 unilateral_feerate(struct chain_topology *topo UNNEEDED) +{ fprintf(stderr, "unilateral_feerate called!\n"); abort(); } /* AUTOGENERATED MOCKS END */ static int test_json_filter(void) diff --git a/tests/test_closing.py b/tests/test_closing.py index 4c1e664cb..736dbf610 100644 --- a/tests/test_closing.py +++ b/tests/test_closing.py @@ -2688,7 +2688,7 @@ def test_onchain_all_dust(node_factory, bitcoind, executor): # Make l1's fees really high (and wait for it to exceed 50000) l1.set_feerates((1000000, 1000000, 1000000, 1000000)) - l1.daemon.wait_for_log('Feerate estimate for unilateral_close set to [56789][0-9]{4}') + l1.daemon.wait_for_log('feerate estimate for 6 blocks smoothed to [56789][0-9]{4}') bitcoind.generate_block(1) l1.daemon.wait_for_log(' to ONCHAIN') diff --git a/tests/test_misc.py b/tests/test_misc.py index 7cbf13fa5..c808fd479 100644 --- a/tests/test_misc.py +++ b/tests/test_misc.py @@ -1524,8 +1524,7 @@ def test_feerates(node_factory): l1.start() # All estimation types - types = ["opening", "mutual_close", "unilateral_close", "delayed_to_us", - "htlc_resolution", "penalty"] + types = ["opening", "mutual_close", "unilateral_close", "penalty"] # Try parsing the feerates, won't work because can't estimate for t in types: @@ -1533,21 +1532,23 @@ 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']) == 2) + wait_for(lambda: len(l1.rpc.feerates('perkw')['perkw']) == 3) 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']['estimates'] == [] for t in types: assert t not in feerates['perkw'] - wait_for(lambda: len(l1.rpc.feerates('perkb')['perkb']) == 2) feerates = l1.rpc.feerates('perkb') assert feerates['warning_missing_feerates'] == 'Some fee estimates unavailable: bitcoind startup?' assert 'perkw' not in feerates assert feerates['perkb']['max_acceptable'] == (2**32 - 1) assert feerates['perkb']['min_acceptable'] == 253 * 4 + assert feerates['perkb']['estimates'] == [] for t in types: assert t not in feerates['perkb'] @@ -1561,24 +1562,30 @@ def test_feerates(node_factory): assert 'perkb' not in feerates # With only one data point, this is a terrible guess! assert feerates['perkw']['min_acceptable'] == 15000 // 2 - # assert feerates['perkw']['min_acceptable'] == 253 + assert feerates['perkw']['estimates'] == [{'blockcount': 2, + 'feerate': 15000, + 'smoothed_feerate': 15000}] # Set ECONOMICAL/6 feerate, for unilateral_close and htlc_resolution l1.set_feerates((15000, 11000, 0, 0), True) feerates = l1.rpc.feerates('perkw') assert feerates['perkw']['unilateral_close'] == 11000 - assert feerates['perkw']['htlc_resolution'] == 11000 assert 'warning_missing_feerates' not in feerates assert 'perkb' not in feerates assert feerates['perkw']['max_acceptable'] == 15000 * 10 # With only two data points, this is a terrible guess! assert feerates['perkw']['min_acceptable'] == 11000 // 2 + assert feerates['perkw']['estimates'] == [{'blockcount': 2, + 'feerate': 15000, + 'smoothed_feerate': 15000}, + {'blockcount': 6, + 'feerate': 11000, + 'smoothed_feerate': 11000}] # Set ECONOMICAL/12 feerate, for all but min (so, no mutual_close feerate) l1.set_feerates((15000, 11000, 6250, 0), True) feerates = l1.rpc.feerates('perkb') assert feerates['perkb']['unilateral_close'] == 11000 * 4 - assert feerates['perkb']['htlc_resolution'] == 11000 * 4 # We dont' extrapolate, so it uses the same for mutual_close assert feerates['perkb']['mutual_close'] == 6250 * 4 for t in types: @@ -1589,13 +1596,21 @@ def test_feerates(node_factory): assert feerates['perkb']['max_acceptable'] == 15000 * 4 * 10 # With only three data points, this is a terrible guess! assert feerates['perkb']['min_acceptable'] == 6250 // 2 * 4 + assert feerates['perkb']['estimates'] == [{'blockcount': 2, + 'feerate': 15000 * 4, + 'smoothed_feerate': 15000 * 4}, + {'blockcount': 6, + 'feerate': 11000 * 4, + 'smoothed_feerate': 11000 * 4}, + {'blockcount': 12, + 'feerate': 6250 * 4, + 'smoothed_feerate': 6250 * 4}] # Set ECONOMICAL/100 feerate for min and mutual_close l1.set_feerates((15000, 11000, 6250, 5000), True) wait_for(lambda: len(l1.rpc.feerates('perkw')['perkw']) >= len(types) + 2) feerates = l1.rpc.feerates('perkw') assert feerates['perkw']['unilateral_close'] == 11000 - assert feerates['perkw']['htlc_resolution'] == 11000 assert feerates['perkw']['mutual_close'] == 5000 for t in types: if t not in ("unilateral_close", "htlc_resolution", "mutual_close"): @@ -1604,12 +1619,25 @@ def test_feerates(node_factory): assert 'perkb' not in feerates assert feerates['perkw']['max_acceptable'] == 15000 * 10 assert feerates['perkw']['min_acceptable'] == 5000 // 2 + assert feerates['perkw']['estimates'] == [{'blockcount': 2, + 'feerate': 15000, + 'smoothed_feerate': 15000}, + {'blockcount': 6, + 'feerate': 11000, + 'smoothed_feerate': 11000}, + {'blockcount': 12, + 'feerate': 6250, + 'smoothed_feerate': 6250}, + {'blockcount': 100, + 'feerate': 5000, + 'smoothed_feerate': 5000}] assert len(feerates['onchain_fee_estimates']) == 5 assert feerates['onchain_fee_estimates']['opening_channel_satoshis'] == feerates['perkw']['opening'] * 702 // 1000 assert feerates['onchain_fee_estimates']['mutual_close_satoshis'] == feerates['perkw']['mutual_close'] * 673 // 1000 assert feerates['onchain_fee_estimates']['unilateral_close_satoshis'] == feerates['perkw']['unilateral_close'] * 598 // 1000 - htlc_feerate = feerates["perkw"]["htlc_resolution"] + # htlc resolution currently uses 6 block estimate + htlc_feerate = [f['feerate'] for f in feerates['perkw']['estimates'] if f['blockcount'] == 6][0] htlc_timeout_cost = feerates["onchain_fee_estimates"]["htlc_timeout_satoshis"] htlc_success_cost = feerates["onchain_fee_estimates"]["htlc_success_satoshis"] @@ -2908,15 +2936,28 @@ def test_force_feerates(node_factory): l1 = node_factory.get_node(options={'force-feerates': 1111}) assert l1.rpc.listconfigs()['force-feerates'] == '1111' + # Note that estimates are still valid here, despite "force-feerates" + estimates = [{"blockcount": 2, + "feerate": 15000, + "smoothed_feerate": 15000}, + {"blockcount": 6, + "feerate": 11000, + "smoothed_feerate": 11000}, + {"blockcount": 12, + "feerate": 7500, + "smoothed_feerate": 7500}, + {"blockcount": 100, + "feerate": 3750, + "smoothed_feerate": 3750}] + assert l1.rpc.feerates('perkw')['perkw'] == { "opening": 1111, "mutual_close": 1111, "unilateral_close": 1111, - "delayed_to_us": 1111, - "htlc_resolution": 1111, "penalty": 1111, "min_acceptable": 1875, - "max_acceptable": 150000} + "max_acceptable": 150000, + "estimates": estimates} l1.stop() l1.daemon.opts['force-feerates'] = '1111/2222' @@ -2927,11 +2968,10 @@ def test_force_feerates(node_factory): "opening": 1111, "mutual_close": 2222, "unilateral_close": 2222, - "delayed_to_us": 2222, - "htlc_resolution": 2222, "penalty": 2222, "min_acceptable": 1875, - "max_acceptable": 150000} + "max_acceptable": 150000, + "estimates": estimates} l1.stop() l1.daemon.opts['force-feerates'] = '1111/2222/3333/4444/5555/6666' @@ -2942,11 +2982,10 @@ def test_force_feerates(node_factory): "opening": 1111, "mutual_close": 2222, "unilateral_close": 3333, - "delayed_to_us": 4444, - "htlc_resolution": 5555, "penalty": 6666, "min_acceptable": 1875, - "max_acceptable": 150000} + "max_acceptable": 150000, + "estimates": estimates} def test_datastore_escapeing(node_factory): @@ -3236,16 +3275,12 @@ def test_feerate_arg(node_factory): fees["urgent"] = by_blocks[6] fees["normal"] = by_blocks[12] - fees["slow"] = by_blocks[100] // 2 + fees["slow"] = by_blocks[100] fees["opening"] = by_blocks[12] fees["mutual_close"] = by_blocks[100] fees["penalty"] = by_blocks[12] fees["unilateral_close"] = by_blocks[6] - fees["delayed_to_us"] = by_blocks[12] - fees["htlc_resolution"] = by_blocks[6] - fees["min_acceptable"] = by_blocks[100] // 2 - fees["max_acceptable"] = by_blocks[2] * 10 for fee, expect in fees.items(): # Put arg in assertion, so it gets printed on failure! diff --git a/tests/test_wallet.py b/tests/test_wallet.py index 883d4aef7..61d5f2075 100644 --- a/tests/test_wallet.py +++ b/tests/test_wallet.py @@ -1658,12 +1658,12 @@ def test_upgradewallet(node_factory, bitcoind): # Doing it with 'reserved ok' should have 1 # We use a big feerate so we can get over the RBF hump - upgrade = l1.rpc.upgradewallet(feerate="max_acceptable", reservedok=True) + upgrade = l1.rpc.upgradewallet(feerate="urgent", reservedok=True) assert upgrade['upgraded_outs'] == 1 assert bitcoind.rpc.getmempoolinfo()['size'] == 1 # Mine it, nothing to upgrade l1.bitcoin.generate_block(1) sync_blockheight(l1.bitcoin, [l1]) - upgrade = l1.rpc.upgradewallet(feerate="max_acceptable", reservedok=True) + upgrade = l1.rpc.upgradewallet(feerate="urgent", reservedok=True) assert upgrade['upgraded_outs'] == 0