mirror of
https://github.com/aljazceru/lightning.git
synced 2026-02-12 01:24:23 +01:00
balance-snaps: add a balance_snapshot event; fires after first catchup
Fire off a snapshot of current account balances (node wallet + every 'active' channel) after we've caught up to the chain tip for the *first* time (in other words, on start).
This commit is contained in:
@@ -791,6 +791,47 @@ at the time lightningd broadcasts the notification.
|
||||
|
||||
`coin_type` is the BIP173 name for the coin which moved.
|
||||
|
||||
### `balance_snapshot`
|
||||
|
||||
Emitted after we've caught up to the chain head on first start. Lists all
|
||||
current accounts (`account_id` matches the `account_id` emitted from
|
||||
`coin_movement`). Useful for checkpointing account balances.
|
||||
|
||||
```json
|
||||
{
|
||||
"balance_snapshots": [
|
||||
{
|
||||
'node_id': '035d2b1192dfba134e10e540875d366ebc8bc353d5aa766b80c090b39c3a5d885d',
|
||||
'blockheight': 101,
|
||||
'timestamp': 1639076327,
|
||||
'accounts': [
|
||||
{
|
||||
'account_id': 'wallet',
|
||||
'balance': '0msat',
|
||||
'coin_type': 'bcrt'
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
'node_id': '035d2b1192dfba134e10e540875d366ebc8bc353d5aa766b80c090b39c3a5d885d',
|
||||
'blockheight': 110,
|
||||
'timestamp': 1639076343,
|
||||
'accounts': [
|
||||
{
|
||||
'account_id': 'wallet',
|
||||
'balance': '995433000msat',
|
||||
'coin_type': 'bcrt'
|
||||
}, {
|
||||
'account_id': '5b65c199ee862f49758603a5a29081912c8816a7c0243d1667489d244d3d055f',
|
||||
'balance': '500000000msat',
|
||||
'coin_type': 'bcrt'
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
### `openchannel_peer_sigs`
|
||||
|
||||
When opening a channel with a peer using the collaborative transaction protocol
|
||||
|
||||
@@ -27,6 +27,8 @@
|
||||
/* Mutual recursion via timer. */
|
||||
static void try_extend_tip(struct chain_topology *topo);
|
||||
|
||||
static bool first_update_complete = false;
|
||||
|
||||
/* init_topo sets topo->root, start_fee_estimate clears
|
||||
* feerate_uninitialized (even if unsuccessful) */
|
||||
static void maybe_completed_init(struct chain_topology *topo)
|
||||
@@ -631,8 +633,10 @@ static void updates_complete(struct chain_topology *topo)
|
||||
topo->prev_tip = topo->tip->blkid;
|
||||
|
||||
/* Send out an account balance snapshot */
|
||||
/* FIXME: only issue on first updates_complete call */
|
||||
send_account_balance_snapshot(topo->ld, topo->tip->height);
|
||||
if (!first_update_complete) {
|
||||
send_account_balance_snapshot(topo->ld, topo->tip->height);
|
||||
first_update_complete = true;
|
||||
}
|
||||
}
|
||||
|
||||
/* If bitcoind is synced, we're now synced. */
|
||||
|
||||
@@ -100,6 +100,7 @@ void send_account_balance_snapshot(struct lightningd *ld, u32 blockheight)
|
||||
/* Add the 'wallet' account balance */
|
||||
snap->accts = tal_arr(snap, struct account_balance *, 1);
|
||||
bal = tal(snap, struct account_balance);
|
||||
bal->balance = AMOUNT_MSAT(0);
|
||||
bal->acct_id = WALLET;
|
||||
bal->bip173_name = chainparams->lightning_hrp;
|
||||
|
||||
|
||||
30
tests/plugins/balance_snaps.py
Executable file
30
tests/plugins/balance_snaps.py
Executable file
@@ -0,0 +1,30 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
from pyln.client import Plugin
|
||||
|
||||
import json
|
||||
import os.path
|
||||
|
||||
|
||||
plugin = Plugin()
|
||||
|
||||
|
||||
@plugin.subscribe("balance_snapshot")
|
||||
def notify_balance_snapshot(plugin, balance_snapshot, **kwargs):
|
||||
# we save to disk so that we don't get borked if the node restarts
|
||||
# assumes notification calls are synchronous (not thread safe)
|
||||
with open('snaps.json', 'a') as f:
|
||||
f.write(json.dumps(balance_snapshot) + ',')
|
||||
|
||||
|
||||
@plugin.method('listsnapshots')
|
||||
def return_moves(plugin):
|
||||
result = []
|
||||
if os.path.exists('snaps.json'):
|
||||
with open('snaps.json', 'r') as f:
|
||||
jd = f.read()
|
||||
result = json.loads('[' + jd[:-1] + ']')
|
||||
return {'balance_snapshots': result}
|
||||
|
||||
|
||||
plugin.run()
|
||||
@@ -7,7 +7,8 @@ from utils import (
|
||||
only_one, sync_blockheight, wait_for, TIMEOUT,
|
||||
account_balance, first_channel_id, closing_fee, TEST_NETWORK,
|
||||
scriptpubkey_addr, calc_lease_fee, EXPERIMENTAL_FEATURES,
|
||||
check_utxos_channel, anchor_expected, check_coin_moves
|
||||
check_utxos_channel, anchor_expected, check_coin_moves,
|
||||
check_balance_snaps
|
||||
)
|
||||
|
||||
import os
|
||||
@@ -1038,12 +1039,15 @@ def test_channel_lease_lessor_cheat(node_factory, bitcoind, chainparams):
|
||||
'''
|
||||
Check that lessee can recover funds if lessor cheats
|
||||
'''
|
||||
balance_snaps = os.path.join(os.getcwd(), 'tests/plugins/balance_snaps.py')
|
||||
opts = [{'funder-policy': 'match', 'funder-policy-mod': 100,
|
||||
'lease-fee-base-msat': '100sat', 'lease-fee-basis': 100,
|
||||
'may_reconnect': True, 'allow_warning': True},
|
||||
'may_reconnect': True, 'allow_warning': True,
|
||||
'plugin': balance_snaps},
|
||||
{'funder-policy': 'match', 'funder-policy-mod': 100,
|
||||
'lease-fee-base-msat': '100sat', 'lease-fee-basis': 100,
|
||||
'may_reconnect': True, 'allow_broken_log': True}]
|
||||
'may_reconnect': True, 'allow_broken_log': True,
|
||||
'plugin': balance_snaps}]
|
||||
l1, l2, = node_factory.get_nodes(2, opts=opts)
|
||||
amount = 500000
|
||||
feerate = 2000
|
||||
@@ -1203,17 +1207,18 @@ def test_penalty_htlc_tx_fulfill(node_factory, bitcoind, chainparams):
|
||||
|
||||
# We track channel balances, to verify that accounting is ok.
|
||||
coin_mvt_plugin = os.path.join(os.getcwd(), 'tests/plugins/coin_movements.py')
|
||||
balance_snaps = os.path.join(os.getcwd(), 'tests/plugins/balance_snaps.py')
|
||||
|
||||
l1, l2, l3, l4 = node_factory.line_graph(4,
|
||||
opts=[{'disconnect': ['-WIRE_UPDATE_FULFILL_HTLC'],
|
||||
'may_reconnect': True,
|
||||
'dev-no-reconnect': None},
|
||||
{'plugin': coin_mvt_plugin,
|
||||
{'plugin': [coin_mvt_plugin, balance_snaps],
|
||||
'disable-mpp': None,
|
||||
'dev-no-reconnect': None,
|
||||
'may_reconnect': True,
|
||||
'allow_broken_log': True},
|
||||
{'plugin': coin_mvt_plugin,
|
||||
{'plugin': [coin_mvt_plugin, balance_snaps],
|
||||
'dev-no-reconnect': None,
|
||||
'may_reconnect': True,
|
||||
'allow_broken_log': True},
|
||||
@@ -1331,6 +1336,17 @@ def test_penalty_htlc_tx_fulfill(node_factory, bitcoind, chainparams):
|
||||
tags = check_utxos_channel(l2, [channel_id], expected_2, filter_channel=channel_id)
|
||||
check_utxos_channel(l3, [channel_id], expected_3, tags, filter_channel=channel_id)
|
||||
|
||||
if not chainparams['elements']:
|
||||
# Also check snapshots
|
||||
expected_bals_2 = [
|
||||
{'blockheight': 101, 'accounts': [{'balance': '0msat'}]},
|
||||
{'blockheight': 108, 'accounts': [{'balance': '995433000msat'}, {'balance': '500000000msat'}, {'balance': '499994999msat'}]},
|
||||
# There's a duplicate because we stop and restart l2 twice
|
||||
# (both times at block 108)
|
||||
{'blockheight': 108, 'accounts': [{'balance': '995433000msat'}, {'balance': '500000000msat'}, {'balance': '499994999msat'}]},
|
||||
]
|
||||
check_balance_snaps(l2, expected_bals_2)
|
||||
|
||||
|
||||
@pytest.mark.developer("needs DEVELOPER=1")
|
||||
@unittest.skipIf(os.getenv('TEST_DB_PROVIDER', 'sqlite3') != 'sqlite3', "Makes use of the sqlite3 db")
|
||||
|
||||
@@ -96,6 +96,16 @@ def calc_lease_fee(amt, feerate, rates):
|
||||
return fee
|
||||
|
||||
|
||||
def check_balance_snaps(n, expected_bals):
|
||||
snaps = n.rpc.listsnapshots()['balance_snapshots']
|
||||
for snap, exp in zip(snaps, expected_bals):
|
||||
assert snap['blockheight'] == exp['blockheight']
|
||||
for acct, exp_acct in zip(snap['accounts'], exp['accounts']):
|
||||
# FIXME: also check 'account_id's (these change every run)
|
||||
for item in ['balance']:
|
||||
assert acct[item] == exp_acct[item]
|
||||
|
||||
|
||||
def check_coin_moves(n, account_id, expected_moves, chainparams):
|
||||
moves = n.rpc.call('listcoinmoves_plugin')['coin_moves']
|
||||
node_id = n.info['id']
|
||||
|
||||
Reference in New Issue
Block a user