Skip to content

Commit

Permalink
Add percentual of satoshis owned by us
Browse files Browse the repository at this point in the history
  • Loading branch information
RCasatta authored and daywalker90 committed Jul 5, 2024
1 parent 9a6af7f commit 9d06458
Show file tree
Hide file tree
Showing 6 changed files with 62 additions and 62 deletions.
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
# Changelog

## [Unreleased]

### Added

- New column for the main channels table: ``PERC_US``: the percentage of funds in the channel that belong to us.

## [3.3.1] 2024-06-29

### Added
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -140,7 +140,7 @@ You can mix these methods and if you set the same option with different methods,

# Options
### Channels table
* ``summars-columns`` Comma-separated list of enabled columns in the channel table. Also dictates order of columns. Valid columns: ``GRAPH_SATS``, ``OUT_SATS``, ``IN_SATS``, ``SCID``, ``MAX_HTLC``, ``FLAG``, ``BASE``, ``PPM``, ``ALIAS``, ``PEER_ID``, ``UPTIME``, ``HTLCS``, ``STATE``. Default columns: ``OUT_SATS``, ``IN_SATS``, ``SCID``, ``MAX_HTLC``, ``FLAG``, ``BASE``, ``PPM``, ``ALIAS``, ``PEER_ID``, ``UPTIME``, ``HTLCS``, ``STATE``
* ``summars-columns`` Comma-separated list of enabled columns in the channel table. Also dictates order of columns. Valid columns: ``GRAPH_SATS``, ``PERC_US``, ``OUT_SATS``, ``IN_SATS``, ``SCID``, ``MAX_HTLC``, ``FLAG``, ``BASE``, ``PPM``, ``ALIAS``, ``PEER_ID``, ``UPTIME``, ``HTLCS``, ``STATE``. Default columns: ``OUT_SATS``, ``IN_SATS``, ``SCID``, ``MAX_HTLC``, ``FLAG``, ``BASE``, ``PPM``, ``ALIAS``, ``PEER_ID``, ``UPTIME``, ``HTLCS``, ``STATE``
* ``summars-sort-by`` Sort by column name. Use ``-`` before column name to reverse sort. Valid columns are all ``summars-columns`` except for ``GRAPH_SATS``. Default is ``SCID``
* ``summars-exclude-states`` List if excluded channel states. Comma-separated. Valid states are: ``OPENING``, ``AWAIT_LOCK``, ``OK``, ``SHUTTING_DOWN``, ``CLOSINGD_SIGEX``, ``CLOSINGD_DONE``, ``AWAIT_UNILATERAL``, ``FUNDING_SPEND``, ``ONCHAIN``, ``DUAL_OPEN``, ``DUAL_COMITTED``, ``DUAL_COMMIT_RDY``, ``DUAL_AWAIT``, ``AWAIT_SPLICE`` and ``PUBLIC``, ``PRIVATE`` to filter channels by their network visibility, aswell as or `ONLINE,OFFLINE` to filter by connection status.
### Forwards table
Expand Down
5 changes: 3 additions & 2 deletions src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -53,8 +53,9 @@ async fn main() -> Result<(), anyhow::Error> {
let opt_columns: StringConfigOption = ConfigOption::new_str_no_default(
OPT_COLUMNS,
"Enabled columns in the channel table. Available columns are: \
`GRAPH_SATS,OUT_SATS,IN_SATS,SCID,MAX_HTLC,FLAG,BASE,PPM,ALIAS,PEER_ID,UPTIME,HTLCS,STATE` \
Default is `OUT_SATS,IN_SATS,SCID,MAX_HTLC,FLAG,BASE,PPM,ALIAS,PEER_ID,UPTIME,HTLCS,STATE`",
`GRAPH_SATS,PERC_US,OUT_SATS,IN_SATS,SCID,MAX_HTLC,FLAG,BASE,PPM,ALIAS,PEER_ID,UPTIME,\
HTLCS,STATE` Default is `OUT_SATS,IN_SATS,SCID,MAX_HTLC,FLAG,BASE,PPM,ALIAS,PEER_ID,\
UPTIME,HTLCS,STATE`",
)
.dynamic();
let opt_sort_by: StringConfigOption = ConfigOption::new_str_no_default(
Expand Down
3 changes: 2 additions & 1 deletion src/structs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ impl Config {
value: {
Summary::FIELD_NAMES_AS_ARRAY
.into_iter()
.filter(|t| t != &"graph_sats")
.filter(|t| t != &"graph_sats" && t != &"perc_us")
.map(ToString::to_string)
.collect::<Vec<String>>()
},
Expand Down Expand Up @@ -231,6 +231,7 @@ pub struct Summary {
pub uptime: f64,
pub htlcs: usize,
pub state: String,
pub perc_us: f64,
}

#[derive(Debug, Tabled, FieldNamesAsArray, Serialize)]
Expand Down
27 changes: 27 additions & 0 deletions src/tables.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1039,6 +1039,7 @@ fn chan_to_summary(
uptime: avail * 100.0,
htlcs: chan.htlcs.clone().unwrap_or_default().len(),
state: statestr.to_string(),
perc_us: (to_us_msat as f64 / total_msat as f64) * 100.0,
})
}

Expand Down Expand Up @@ -1149,6 +1150,21 @@ fn sort_summary(config: &Config, table: &mut [Summary]) {
table.sort_by_key(|x| x.state.clone())
}
}
col if col.eq("PERC_US") => {
if reverse {
table.sort_by(|x, y| {
y.perc_us
.partial_cmp(&x.perc_us)
.unwrap_or(std::cmp::Ordering::Equal)
})
} else {
table.sort_by(|x, y| {
x.perc_us
.partial_cmp(&y.perc_us)
.unwrap_or(std::cmp::Ordering::Equal)
})
}
}
_ => {
if reverse {
table.sort_by_key(|x| Reverse(x.scid_raw))
Expand Down Expand Up @@ -1207,6 +1223,7 @@ fn format_summary(config: &Config, sumtable: &mut Table) -> Result<(), Error> {
sumtable.with(Modify::new(ByColumnName::new("BASE")).with(Alignment::right()));
sumtable.with(Modify::new(ByColumnName::new("PPM")).with(Alignment::right()));
sumtable.with(Modify::new(ByColumnName::new("UPTIME")).with(Alignment::right()));
sumtable.with(Modify::new(ByColumnName::new("PERC_US")).with(Alignment::right()));
sumtable.with(Modify::new(ByColumnName::new("HTLCS")).with(Alignment::right()));
sumtable.with(Modify::new(ByColumnName::new("STATE")).with(Alignment::center()));

Expand All @@ -1220,6 +1237,16 @@ fn format_summary(config: &Config, sumtable: &mut Table) -> Result<(), Error> {
}
})),
);
sumtable.with(
Modify::new(ByColumnName::new("PERC_US").not(Rows::first())).with(Format::content(|s| {
let av = s.parse::<f64>().unwrap_or(-1.0);
if av < 0.0 {
"N/A".to_string()
} else {
format!("{:.1}%", av)
}
})),
);
sumtable.with(
Modify::new(ByColumnName::new("OUT_SATS").not(Rows::first())).with(Format::content(|s| {
u64_to_sat_string(config, s.parse::<u64>().unwrap()).unwrap()
Expand Down
81 changes: 23 additions & 58 deletions tests/test_summars.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
"UPTIME",
"HTLCS",
"STATE",
"PERC_US",
]

forwards_columns = [
Expand Down Expand Up @@ -69,7 +70,7 @@ def test_basic(node_factory, get_plugin): # noqa: F811
assert "avail_out=0.00000000 BTC" in result["result"]
assert "avail_in=0.00000000 BTC" in result["result"]

expected_columns = [x for x in columns if x != "GRAPH_SATS"]
expected_columns = [x for x in columns if x != "GRAPH_SATS" and x != "PERC_US"]
for column in expected_columns:
assert column in result["result"]

Expand Down Expand Up @@ -168,9 +169,7 @@ def test_options(node_factory, get_plugin): # noqa: F811
assert sats_sent != -1
assert preimage != -1
assert (
description < destination
and destination < sats_sent
and sats_sent < preimage
description < destination and destination < sats_sent and sats_sent < preimage
)

for col in invoice_columns:
Expand Down Expand Up @@ -205,9 +204,7 @@ def test_options(node_factory, get_plugin): # noqa: F811
assert description != -1
assert label != -1
assert paid_at != -1
assert (
sats_received < description and description < label and label < paid_at
)
assert sats_received < description and description < label and label < paid_at

for col in forwards_columns:
result = node.rpc.call(
Expand Down Expand Up @@ -363,38 +360,26 @@ def test_options(node_factory, get_plugin): # noqa: F811
def test_option_errors(node_factory, get_plugin): # noqa: F811
node = node_factory.get_node(options={"plugin": get_plugin})

with pytest.raises(
RpcError, match="not found in valid summars-columns names"
):
with pytest.raises(RpcError, match="not found in valid summars-columns names"):
node.rpc.call("summars", {"summars-columns": "test"})
with pytest.raises(RpcError, match="Duplicate entry"):
node.rpc.call("summars", {"summars-columns": "IN_SATS,IN_SATS"})
with pytest.raises(
RpcError, match="not found in valid summars-columns names"
):
with pytest.raises(RpcError, match="not found in valid summars-columns names"):
node.rpc.call("summars", {"summars-columns": "PRIVATE"})
with pytest.raises(
RpcError, match="not found in valid summars-columns names"
):
with pytest.raises(RpcError, match="not found in valid summars-columns names"):
node.rpc.call("summars", {"summars-columns": "OFFLINE"})

with pytest.raises(
RpcError, match="not found in valid summars-forwards-columns names"
):
node.rpc.call("summars", {"summars-forwards-columns": "test"})
with pytest.raises(RpcError, match="Duplicate entry"):
node.rpc.call(
"summars", {"summars-forwards-columns": "in_channel,in_channel"}
)
node.rpc.call("summars", {"summars-forwards-columns": "in_channel,in_channel"})

with pytest.raises(
RpcError, match="not found in valid summars-pays-columns names"
):
with pytest.raises(RpcError, match="not found in valid summars-pays-columns names"):
node.rpc.call("summars", {"summars-pays-columns": "test"})
with pytest.raises(RpcError, match="Duplicate entry"):
node.rpc.call(
"summars", {"summars-pays-columns": "description,description"}
)
node.rpc.call("summars", {"summars-pays-columns": "description,description"})

with pytest.raises(
RpcError, match="not found in valid summars-invoices-columns names"
Expand Down Expand Up @@ -425,9 +410,7 @@ def test_option_errors(node_factory, get_plugin): # noqa: F811
node.rpc.call("summars", {"summars-forwards": -1})

with pytest.raises(RpcError, match="not a valid integer"):
node.rpc.call(
"summars", {"summars-forwards-filter-amount-msat": "TEST"}
)
node.rpc.call("summars", {"summars-forwards-filter-amount-msat": "TEST"})

with pytest.raises(RpcError, match="not a valid integer"):
node.rpc.call("summars", {"summars-forwards-filter-fee-msat": "TEST"})
Expand All @@ -448,9 +431,7 @@ def test_option_errors(node_factory, get_plugin): # noqa: F811
node.rpc.call("summars", {"summars-invoices": -1})

with pytest.raises(RpcError, match="not a valid integer"):
node.rpc.call(
"summars", {"summars-invoices-filter-amount-msat": "TEST"}
)
node.rpc.call("summars", {"summars-invoices-filter-amount-msat": "TEST"})

with pytest.raises(RpcError, match="not a valid string"):
node.rpc.call("summars", {"summars-locale": -1})
Expand Down Expand Up @@ -539,24 +520,18 @@ def test_setconfig_options(node_factory, get_plugin): # noqa: F811
assert err.value.error["message"] == "summars-utf8 is not a valid boolean!"
assert err.value.error["code"] == -32602
assert (
node.rpc.listconfigs("summars-utf8")["configs"]["summars-utf8"][
"value_bool"
]
node.rpc.listconfigs("summars-utf8")["configs"]["summars-utf8"]["value_bool"]
!= "test"
)
node.rpc.setconfig("summars-utf8", False)
assert (
node.rpc.listconfigs("summars-utf8")["configs"]["summars-utf8"][
"value_bool"
]
node.rpc.listconfigs("summars-utf8")["configs"]["summars-utf8"]["value_bool"]
is False
)


def test_chanstates(node_factory, bitcoind, get_plugin): # noqa: F811
l1, l2, l3 = node_factory.get_nodes(
3, opts=[{"plugin": get_plugin}, {}, {}]
)
l1, l2, l3 = node_factory.get_nodes(3, opts=[{"plugin": get_plugin}, {}, {}])
l1.fundwallet(10_000_000)
l2.fundwallet(10_000_000)
l1.rpc.fundchannel(
Expand Down Expand Up @@ -621,9 +596,9 @@ def test_chanstates(node_factory, bitcoind, get_plugin): # noqa: F811
l3.stop()

wait_for(
lambda: not only_one(
l1.rpc.listpeerchannels(l3.info["id"])["channels"]
)["peer_connected"]
lambda: not only_one(l1.rpc.listpeerchannels(l3.info["id"])["channels"])[
"peer_connected"
]
)
result = l1.rpc.call("summars", {"summars-exclude-states": "OFFLINE"})
assert "O]" not in result["result"]
Expand All @@ -636,9 +611,7 @@ def test_chanstates(node_factory, bitcoind, get_plugin): # noqa: F811
l1.rpc.close(chans[0]["short_channel_id"])

wait_for(
lambda: only_one(l1.rpc.listpeerchannels(l2.info["id"])["channels"])[
"state"
]
lambda: only_one(l1.rpc.listpeerchannels(l2.info["id"])["channels"])["state"]
== "CLOSINGD_COMPLETE"
)
result = l1.rpc.call("summars")
Expand All @@ -653,22 +626,14 @@ def test_flowtables(node_factory, bitcoind, get_plugin): # noqa: F811
l1.rpc.connect(l2.info["id"], "localhost", l2.port)
l2.rpc.connect(l3.info["id"], "localhost", l3.port)
l1.rpc.connect(l3.info["id"], "localhost", l3.port)
l1.rpc.fundchannel(
l2.info["id"], 1_000_000, push_msat=500_000_000, mindepth=1
)
l2.rpc.fundchannel(
l3.info["id"], 1_000_000, push_msat=500_000_000, mindepth=1
)
l1.rpc.fundchannel(l2.info["id"], 1_000_000, push_msat=500_000_000, mindepth=1)
l2.rpc.fundchannel(l3.info["id"], 1_000_000, push_msat=500_000_000, mindepth=1)

bitcoind.generate_block(6)
sync_blockheight(bitcoind, [l1, l2, l3])

cl1 = l2.rpc.listpeerchannels(l1.info["id"])["channels"][0][
"short_channel_id"
]
cl2 = l2.rpc.listpeerchannels(l3.info["id"])["channels"][0][
"short_channel_id"
]
cl1 = l2.rpc.listpeerchannels(l1.info["id"])["channels"][0]["short_channel_id"]
cl2 = l2.rpc.listpeerchannels(l3.info["id"])["channels"][0]["short_channel_id"]
l2.wait_channel_active(cl1)
l2.wait_channel_active(cl2)

Expand Down

0 comments on commit 9d06458

Please sign in to comment.