json-rpc: Add `listforwardings` command

This commit is contained in:
Christian Decker 2018-10-17 20:11:04 +02:00 committed by Rusty Russell
parent 6efdd5275c
commit facd7d16aa
5 changed files with 192 additions and 1 deletions

View File

@ -428,6 +428,15 @@ class LightningRpc(UnixDomainSocketRpc):
"""
return self.call("listfunds")
def getroutestats(self, details=True):
"""Get statistics about routed payments.
If @details is True, this'll include the individual forwarded
payments.
"""
return self.call("getroutestats", payload={'details': details})
def dev_rescan_outputs(self):
"""
Synchronize the state of our funds with bitcoind

View File

@ -1842,3 +1842,82 @@ static const struct json_command dev_ignore_htlcs = {
};
AUTODATA(json_command, &dev_ignore_htlcs);
#endif /* DEVELOPER */
static void listforwardings_add_stats(struct json_result *response, struct wallet *wallet)
{
const struct forwarding_stats *stats;
stats = wallet_forwarded_payments_stats(wallet, tmpctx);
json_object_start(response, "stats");
json_object_start(response, "settled");
json_add_num(response, "fee_msatoshis", stats->fee[FORWARD_SETTLED]);
json_add_num(response, "count", stats->count[FORWARD_SETTLED]);
json_add_num(response, "msatoshi", stats->msatoshi[FORWARD_SETTLED]);
json_object_end(response);
json_object_start(response, "failed");
json_add_num(response, "fee_msatoshis", stats->fee[FORWARD_FAILED]);
json_add_num(response, "count", stats->count[FORWARD_FAILED]);
json_add_num(response, "msatoshi", stats->msatoshi[FORWARD_FAILED]);
json_object_end(response);
json_object_start(response, "pending");
json_add_num(response, "fee_msatoshis", stats->fee[FORWARD_OFFERED]);
json_add_num(response, "count", stats->count[FORWARD_FAILED]);
json_add_num(response, "msatoshi", stats->msatoshi[FORWARD_FAILED]);
json_object_end(response);
json_object_end(response);
tal_free(stats);
}
static void listforwardings_add_forwardings(struct json_result *response, struct wallet *wallet)
{
const struct forwarding *forwardings;
forwardings = wallet_forwarded_payments_get(wallet, tmpctx);
json_array_start(response, "forwards");
for (size_t i=0; i<tal_count(forwardings); i++) {
const struct forwarding *cur = &forwardings[i];
json_object_start(response, NULL);
json_add_short_channel_id(response, "in_channel", &cur->channel_in);
json_add_short_channel_id(response, "out_channel", &cur->channel_out);
json_add_num(response, "in_msatoshi", cur->msatoshi_in);
json_add_num(response, "out_msatoshi", cur->msatoshi_out);
json_add_num(response, "fee", cur->fee);
json_add_string(response, "status", forward_status_name(cur->status));
json_object_end(response);
}
json_array_end(response);
tal_free(forwardings);
}
static void json_getroutestats(struct command *cmd, const char *buffer,
const jsmntok_t *params)
{
struct json_result *response = new_json_result(cmd);
bool *details;
if (!param(cmd, buffer, params,
p_opt_def("details", json_tok_bool, &details, true),
NULL))
return;
json_object_start(response, NULL);
listforwardings_add_stats(response, cmd->ld->wallet);
if (*details)
listforwardings_add_forwardings(response, cmd->ld->wallet);
json_object_end(response);
command_success(cmd, response);
}
static const struct json_command getroutestats_command = {
"getroutestats", json_getroutestats,
"Get statistics about routed / forwarded payments", false,
"Get statistics about routed payments, i.e., the ones we aren't the initiator or recipient, including a detailed list if {details} is true."
};
AUTODATA(json_command, &getroutestats_command);

View File

@ -985,7 +985,7 @@ def test_forward_stats(node_factory, bitcoind):
hash) and l5 will keep the HTLC dangling by disconnecting.
"""
amount = 10**4
amount = 10**5
l1, l2, l3 = node_factory.line_graph(3, announce=False)
l4 = node_factory.get_node()
l5 = node_factory.get_node(may_fail=True)
@ -1046,3 +1046,7 @@ def test_forward_stats(node_factory, bitcoind):
assert outchan['out_msatoshi_offered'] == outchan['out_msatoshi_fulfilled']
assert outchan['out_msatoshi_fulfilled'] < inchan['in_msatoshi_fulfilled']
stats = l2.rpc.getroutestats()
assert [f['status'] for f in stats['forwards']] == ['settled', 'failed', 'offered']

View File

@ -2430,3 +2430,63 @@ void wallet_forwarded_payment_add(struct wallet *w, const struct htlc_in *in,
sqlite3_bind_int(stmt, 7, state);
db_exec_prepared(w->db, stmt);
}
const struct forwarding_stats *wallet_forwarded_payments_stats(struct wallet *w,
const tal_t *ctx)
{
struct forwarding_stats *stats = talz(ctx, struct forwarding_stats);
sqlite3_stmt *stmt;
stmt = db_prepare(w->db,
"SELECT"
" state"
", COUNT(*)"
", SUM(out_msatoshi) as total"
", SUM(in_msatoshi - out_msatoshi) as fee "
"FROM forwarded_payments "
"GROUP BY state;");
while (sqlite3_step(stmt) == SQLITE_ROW) {
enum forward_status state = sqlite3_column_int(stmt, 0);
stats->count[state] = sqlite3_column_int64(stmt, 1);
stats->msatoshi[state] = sqlite3_column_int64(stmt, 2);
stats->fee[state] = sqlite3_column_int64(stmt, 3);
}
db_stmt_done(stmt);
return stats;
}
const struct forwarding *wallet_forwarded_payments_get(struct wallet *w,
const tal_t *ctx)
{
struct forwarding *results = tal_arr(ctx, struct forwarding, 0);
size_t count = 0;
sqlite3_stmt *stmt;
stmt = db_prepare(w->db,
"SELECT"
" f.state"
", in_msatoshi"
", out_msatoshi"
", hin.payment_hash as payment_hash"
", in_channel_scid"
", out_channel_scid "
"FROM forwarded_payments f "
"LEFT JOIN channel_htlcs hin ON (f.in_htlc_id == hin.id)");
for (count=0; sqlite3_step(stmt) == SQLITE_ROW; count++) {
tal_resize(&results, count+1);
struct forwarding *cur = &results[count];
cur->status = sqlite3_column_int(stmt, 0);
cur->msatoshi_in = sqlite3_column_int64(stmt, 1);
cur->msatoshi_out = sqlite3_column_int64(stmt, 2);
cur->fee = cur->msatoshi_in - cur->msatoshi_out;
sqlite3_column_sha256_double(stmt, 3, &cur->payment_hash);
sqlite3_column_short_channel_id(stmt, 4, &cur->channel_in);
sqlite3_column_short_channel_id(stmt, 5, &cur->channel_out);
}
db_stmt_done(stmt);
return results;
}

View File

@ -146,6 +146,31 @@ static inline enum forward_status wallet_forward_status_in_db(enum forward_statu
fatal("%s: %u is invalid", __func__, s);
}
static inline const char* forward_status_name(enum forward_status status)
{
switch(status) {
case FORWARD_OFFERED:
return "offered";
case FORWARD_SETTLED:
return "settled";
case FORWARD_FAILED:
return "failed";
}
abort();
}
struct forwarding {
struct short_channel_id channel_in, channel_out;
u64 msatoshi_in, msatoshi_out, fee;
struct sha256_double payment_hash;
enum forward_status status;
};
struct forwarding_stats {
/* One entry for each of the forward_statuses */
u64 count[3], msatoshi[3], fee[3];
};
/* A database backed shachain struct. The datastructure is
* writethrough, reads are performed from an in-memory version, all
* writes are passed through to the DB. */
@ -995,8 +1020,22 @@ u32 *wallet_onchaind_channels(struct wallet *w,
struct channeltx *wallet_channeltxs_get(struct wallet *w, const tal_t *ctx,
u32 channel_id);
/**
* Add of update a forwarded_payment
*/
void wallet_forwarded_payment_add(struct wallet *w, const struct htlc_in *in,
const struct htlc_out *out,
enum forward_status state);
/**
* Retrieve global stats about all forwarded_payments
*/
const struct forwarding_stats *wallet_forwarded_payments_stats(struct wallet *w,
const tal_t *ctx);
/**
* Retrieve a list of all forwarded_payments
*/
const struct forwarding *wallet_forwarded_payments_get(struct wallet *w,
const tal_t *ctx);
#endif /* LIGHTNING_WALLET_WALLET_H */