From cfbca30b7fcb506ad5b7e87df5e98eea84fe81a6 Mon Sep 17 00:00:00 2001 From: Rusty Russell Date: Tue, 19 Aug 2025 10:30:46 +0930 Subject: [PATCH] plugins/sql: add listchainmoves and listchannelmoves. And note the other commands in See Also section. Note that this means handling the "outpoint" type. Signed-off-by: Rusty Russell Changelog-Added: JSON-RPC: `sql` plugin now supports `chainmoves` and `channelmoves` tables. --- contrib/msggen/msggen/schema.json | 9 +++++ doc/schemas/sql-template.json | 9 +++++ plugins/Makefile | 2 +- plugins/sql.c | 11 ++++++ tests/test_coinmoves.py | 21 ++++++++++ tests/test_plugin.py | 64 +++++++++++++++++++++++++++++++ 6 files changed, 115 insertions(+), 1 deletion(-) diff --git a/contrib/msggen/msggen/schema.json b/contrib/msggen/msggen/schema.json index 1074b6834..ae0baa69d 100644 --- a/contrib/msggen/msggen/schema.json +++ b/contrib/msggen/msggen/schema.json @@ -33031,8 +33031,17 @@ ], "see_also": [ "lightning-listtransactions(7)", + "lightning-listhtlcs(7)", + "lightning-listinvoices(7)", + "lightning-listoffers(7)", "lightning-listchannels(7)", "lightning-listpeers(7)", + "lightning-listpeerchannels(7)", + "lightning-listsendpays(7)", + "lightning-listchainmoves(7)", + "lightning-listchannelmoves(7)", + "lightning-bkpr-listaccountevents(7)", + "lightning-bkpr-listincome(7)", "lightning-listnodes(7)", "lightning-listforwards(7)" ], diff --git a/doc/schemas/sql-template.json b/doc/schemas/sql-template.json index eb3327466..01c7f36af 100644 --- a/doc/schemas/sql-template.json +++ b/doc/schemas/sql-template.json @@ -123,8 +123,17 @@ ], "see_also": [ "lightning-listtransactions(7)", + "lightning-listhtlcs(7)", + "lightning-listinvoices(7)", + "lightning-listoffers(7)", "lightning-listchannels(7)", "lightning-listpeers(7)", + "lightning-listpeerchannels(7)", + "lightning-listsendpays(7)", + "lightning-listchainmoves(7)", + "lightning-listchannelmoves(7)", + "lightning-bkpr-listaccountevents(7)", + "lightning-bkpr-listincome(7)", "lightning-listnodes(7)", "lightning-listforwards(7)" ], diff --git a/plugins/Makefile b/plugins/Makefile index fc3f2c8cf..6efb6c231 100644 --- a/plugins/Makefile +++ b/plugins/Makefile @@ -256,7 +256,7 @@ plugins/recover: common/gossmap.o common/sciddir_or_pubkey.o common/fp16.o $(PL plugins/recklessrpc: $(PLUGIN_RECKLESSRPC_OBJS) $(PLUGIN_LIB_OBJS) $(PLUGIN_COMMON_OBJS) $(JSMN_OBJS) # This covers all the low-level list RPCs which return simple arrays -SQL_LISTRPCS := listchannels listforwards listhtlcs listinvoices listnodes listoffers listpeers listpeerchannels listclosedchannels listtransactions listsendpays bkpr-listaccountevents bkpr-listincome +SQL_LISTRPCS := listchannels listforwards listhtlcs listinvoices listnodes listoffers listpeers listpeerchannels listclosedchannels listtransactions listsendpays listchainmoves listchannelmoves bkpr-listaccountevents bkpr-listincome SQL_LISTRPCS_SCHEMAS := $(foreach l,$(SQL_LISTRPCS),doc/schemas/$l.json) SQL_SCHEMA_PARTS := $(foreach l,$(SQL_LISTRPCS), plugins/sql-schema_$l_gen.h) diff --git a/plugins/sql.c b/plugins/sql.c index d61452f4b..0a61dd4a0 100644 --- a/plugins/sql.c +++ b/plugins/sql.c @@ -51,6 +51,7 @@ enum fieldtype { FIELD_NUMBER, FIELD_STRING, FIELD_SCID, + FIELD_OUTPOINT, }; struct fieldtypemap { @@ -74,6 +75,7 @@ static const struct fieldtypemap fieldtypemap[] = { { "number", "REAL" }, /* FIELD_NUMBER */ { "string", "TEXT" }, /* FIELD_STRING */ { "short_channel_id", "TEXT" }, /* FIELD_SCID */ + { "outpoint", "TEXT" }, /* FIELD_OUTPOINT */ }; struct column { @@ -171,6 +173,14 @@ static const struct index indices[] = { "transactions", { "hash", NULL }, }, + { + "chainmoves", + { "account_id", NULL }, + }, + { + "channelmoves", + { "account_id", NULL }, + }, }; static enum fieldtype find_fieldtype(const jsmntok_t *name) @@ -626,6 +636,7 @@ static struct command_result *process_json_obj(struct command *cmd, break; case FIELD_SCID: case FIELD_STRING: + case FIELD_OUTPOINT: sqlite3_bind_text(stmt, (*sqloff)++, buf + coltok->start, coltok->end - coltok->start, SQLITE_STATIC); diff --git a/tests/test_coinmoves.py b/tests/test_coinmoves.py index 6db3e2d4f..92eac09e1 100644 --- a/tests/test_coinmoves.py +++ b/tests/test_coinmoves.py @@ -11,6 +11,20 @@ import time from pyln.testing.utils import EXPERIMENTAL_DUAL_FUND +# While we're doing this, check sql plugin's representation too. +def check_sql(node, kind, expected): + columns = only_one(node.rpc.listsqlschemas(kind)['schemas'])['columns'] + ret = node.rpc.sql(f"SELECT * FROM {kind}") + assert len(ret['rows']) == len(expected) + for row, e in zip(ret['rows'], expected): + assert len(row) == len(columns) + for val, col in zip(row, columns): + if col['name'] in e: + assert val == e[col['name']], f"{col['name']} is {val} not {e[col['name']]}: ({row} vs {columns})" + elif col['name'] not in ('rowid', 'timestamp'): + assert val is None, f"{col['name']} is not None ({row} vs {columns})" + + def check_moves(moves, expected): # Can't predict timestamp for m in moves: @@ -23,10 +37,17 @@ def check_moves(moves, expected): def check_channel_moves(node, expected): check_moves(node.rpc.listchannelmoves()['channelmoves'], expected) + check_sql(node, "channelmoves", expected) def check_chain_moves(node, expected): check_moves(node.rpc.listchainmoves()['chainmoves'], expected) + check_sql(node, "chainmoves", expected) + # Check extra_tags. + for e in expected: + rows = node.rpc.sql(f"SELECT cet.extra_tags FROM chainmoves_extra_tags cet LEFT JOIN chainmoves cm ON cet.row = cm.rowid WHERE cm.created_index={e['created_index']} ORDER BY cm.created_index, cet.arrindex;")['rows'] + extra_tags = [only_one(row) for row in rows] + assert extra_tags == e['extra_tags'] def account_balances(accounts): diff --git a/tests/test_plugin.py b/tests/test_plugin.py index 2ac9da76b..3ef2fde08 100644 --- a/tests/test_plugin.py +++ b/tests/test_plugin.py @@ -3756,6 +3756,65 @@ def test_sql(node_factory, bitcoind): 'type': 'msat'}, {'name': 'scriptPubKey', 'type': 'hex'}]}, + 'chainmoves': { + 'indices': [['account_id']], + 'columns': [{'name': 'created_index', + 'type': 'u64'}, + {'name': 'account_id', + 'type': 'string'}, + {'name': 'credit_msat', + 'type': 'msat'}, + {'name': 'debit_msat', + 'type': 'msat'}, + {'name': 'timestamp', + 'type': 'u64'}, + {'name': 'primary_tag', + 'type': 'string'}, + {'name': 'peer_id', + 'type': 'pubkey'}, + {'name': 'originating_account', + 'type': 'string'}, + {'name': 'spending_txid', + 'type': 'txid'}, + {'name': 'utxo', + 'type': 'outpoint'}, + {'name': 'payment_hash', + 'type': 'hash'}, + {'name': 'output_msat', + 'type': 'msat'}, + {'name': 'output_count', + 'type': 'u32'}, + {'name': 'blockheight', + 'type': 'u32'}]}, + 'chainmoves_extra_tags': { + 'columns': [{'name': 'row', + 'type': 'u64'}, + {'name': 'arrindex', + 'type': 'u64'}, + {'name': 'extra_tags', + 'type': 'string'}]}, + 'channelmoves': { + 'indices': [['account_id']], + 'columns': [{'name': 'created_index', + 'type': 'u64'}, + {'name': 'account_id', + 'type': 'string'}, + {'name': 'credit_msat', + 'type': 'msat'}, + {'name': 'debit_msat', + 'type': 'msat'}, + {'name': 'timestamp', + 'type': 'u64'}, + {'name': 'primary_tag', + 'type': 'string'}, + {'name': 'payment_hash', + 'type': 'hash'}, + {'name': 'part_id', + 'type': 'u64'}, + {'name': 'group_id', + 'type': 'u64'}, + {'name': 'fees_msat', + 'type': 'msat'}]}, 'bkpr_accountevents': { 'columns': [{'name': 'account', 'type': 'string'}, @@ -3822,6 +3881,7 @@ def test_sql(node_factory, bitcoind): 'hex': 'BLOB', 'hash': 'BLOB', 'txid': 'BLOB', + 'outpoint': 'TEXT', 'pubkey': 'BLOB', 'secret': 'BLOB', 'number': 'REAL', @@ -3902,6 +3962,10 @@ def test_sql(node_factory, bitcoind): val += "" elif col['type'] == "short_channel_id": assert len(val.split('x')) == 3 + elif col['type'] == "outpoint": + txid, vout = val.split(':') + assert len(bytes.fromhex(txid)) == 32 + int(vout) else: assert False