Skip to content

Commit

Permalink
bkpr-zeroconf: Zeroconfs will emit 'channel_proposed' event
Browse files Browse the repository at this point in the history
Keep the accounts as an 'append only' log, instead we move the marker
for the 'channel_open' forward when a 'channel_open' comes out.

We also neatly hide the 'channel_proposed' events in 'inspect' if
there's a 'channel_open' for that same event.

If you call inspect before the 'channel_open' is confirmed, you'll see
the tag as 'channel_proposed', afterwards it shows up as
'channel_open'. However the event log rolls forward -- listaccountevents
will show the correct history of the proposal then open confirming (plus
any routing that happened before the channel confirmed).
  • Loading branch information
niftynei committed Jul 27, 2022
1 parent da742b5 commit 4822c54
Show file tree
Hide file tree
Showing 2 changed files with 61 additions and 12 deletions.
24 changes: 18 additions & 6 deletions plugins/bkpr/recorder.c
Original file line number Diff line number Diff line change
Expand Up @@ -225,7 +225,8 @@ static struct chain_event **find_txos_for_tx(const tal_t *ctx,
" ORDER BY "
" e.utxo_txid"
", e.outnum"
", e.spending_txid NULLS FIRST"));
", e.spending_txid NULLS FIRST"
", e.blockheight"));

db_bind_txid(stmt, 0, txid);
return find_chain_events(ctx, take(stmt));
Expand Down Expand Up @@ -403,8 +404,14 @@ static struct txo_set *find_txo_set(const tal_t *ctx,
} else {
/* We might not have a spend event
* for everything */
if (pr)
tal_arr_expand(&txos->pairs, pr);
if (pr) {
/* Disappear "channel_proposed" events */
if (streq(pr->txo->tag,
mvt_tag_str(CHANNEL_PROPOSED)))
pr = tal_free(pr);
else
tal_arr_expand(&txos->pairs, pr);
}
pr = new_txo_pair(txos->pairs);
pr->txo = tal_steal(pr, ev);
}
Expand Down Expand Up @@ -671,7 +678,8 @@ static struct chain_event *find_chain_event(const tal_t *ctx,
struct db *db,
const struct account *acct,
const struct bitcoin_outpoint *outpoint,
const struct bitcoin_txid *spending_txid)
const struct bitcoin_txid *spending_txid,
const char *tag)

{
struct db_stmt *stmt;
Expand Down Expand Up @@ -733,7 +741,10 @@ static struct chain_event *find_chain_event(const tal_t *ctx,
" e.account_id = ?"
" AND e.utxo_txid = ?"
" AND e.outnum = ?"
" AND e.spending_txid IS NULL"));
" AND e.spending_txid IS NULL"
" AND e.tag = ?"));

db_bind_text(stmt, 3, tag);
}

db_bind_u64(stmt, 0, acct->db_id);
Expand Down Expand Up @@ -1850,7 +1861,8 @@ bool log_chain_event(struct db *db,

/* We're responsible for de-duping chain events! */
if (find_chain_event(e, db, acct,
&e->outpoint, e->spending_txid))
&e->outpoint, e->spending_txid,
e->tag))
return false;

stmt = db_prepare_v2(db, SQL("INSERT INTO chain_events"
Expand Down
49 changes: 43 additions & 6 deletions tests/test_opening.py
Original file line number Diff line number Diff line change
Expand Up @@ -1331,9 +1331,11 @@ def test_zeroconf_public(bitcoind, node_factory, chainparams):
},
{}
])
# Advances blockheight to 102
l1.fundwallet(10**6)
push_msat = 20000 * 1000
l1.connect(l2)
l1.rpc.fundchannel(l2.info['id'], 'all', mindepth=0)
l1.rpc.fundchannel(l2.info['id'], 'all', mindepth=0, push_msat=push_msat)

# Wait for the update to be signed (might not be the most reliable
# signal)
Expand All @@ -1342,18 +1344,32 @@ def test_zeroconf_public(bitcoind, node_factory, chainparams):

l1chan = l1.rpc.listpeers()['peers'][0]['channels'][0]
l2chan = l2.rpc.listpeers()['peers'][0]['channels'][0]
channel_id = l1chan['channel_id']

# We have no confirmation yet, so no `short_channel_id`
assert('short_channel_id' not in l1chan)
assert('short_channel_id' not in l2chan)

# Channel is "proposed"
chan_val = 993198000 if chainparams['elements'] else 995673000
l1_mvts = [
{'type': 'chain_mvt', 'credit_msat': 995673000, 'debit_msat': 0, 'tags': ['channel_proposed', 'opener']},
{'type': 'chain_mvt', 'credit_msat': chan_val, 'debit_msat': 0, 'tags': ['channel_proposed', 'opener']},
{'type': 'channel_mvt', 'credit_msat': 0, 'debit_msat': 20000000, 'tags': ['pushed'], 'fees_msat': '0msat'},
]
check_coin_moves(l1, l1chan['channel_id'], l1_mvts, chainparams)

# Now add 1 confirmation, we should get a `short_channel_id`
# Check that the channel_open event has blockheight of zero
for n in [l1, l2]:
evs = n.rpc.bkpr_listaccountevents(channel_id)['events']
open_ev = only_one([e for e in evs if e['tag'] == 'channel_proposed'])
assert open_ev['blockheight'] == 0

# Call inspect, should have pending event in it
tx = only_one(n.rpc.bkpr_inspect(channel_id)['txs'])
assert 'blockheight' not in tx
assert only_one(tx['outputs'])['output_tag'] == 'channel_proposed'

# Now add 1 confirmation, we should get a `short_channel_id` (block 103)
bitcoind.generate_block(1)
l1.daemon.wait_for_log(r'Funding tx [a-f0-9]{64} depth 1 of 0')
l2.daemon.wait_for_log(r'Funding tx [a-f0-9]{64} depth 1 of 0')
Expand All @@ -1363,11 +1379,24 @@ def test_zeroconf_public(bitcoind, node_factory, chainparams):
assert('short_channel_id' in l1chan)
assert('short_channel_id' in l2chan)

# We also now have an 'open' event
# We also now have an 'open' event, the push event isn't re-recorded
l1_mvts += [
{'type': 'chain_mvt', 'credit_msat': 995673000, 'debit_msat': 0, 'tags': ['channel_open', 'opener']},
{'type': 'chain_mvt', 'credit_msat': chan_val, 'debit_msat': 0, 'tags': ['channel_open', 'opener']},
]
check_coin_moves(l1, l1chan['channel_id'], l1_mvts, chainparams)
check_coin_moves(l1, channel_id, l1_mvts, chainparams)

# Check that there is a channel_open event w/ real blockheight
for n in [l1, l2]:
evs = n.rpc.bkpr_listaccountevents(channel_id)['events']
# Still has the channel-proposed event
only_one([e for e in evs if e['tag'] == 'channel_proposed'])
open_ev = only_one([e for e in evs if e['tag'] == 'channel_open'])
assert open_ev['blockheight'] == 103

# Call inspect, should have open event in it
tx = only_one(n.rpc.bkpr_inspect(channel_id)['txs'])
assert tx['blockheight'] == 103
assert only_one(tx['outputs'])['output_tag'] == 'channel_open'

# Now make it public, we should be switching over to the real
# scid.
Expand All @@ -1377,6 +1406,14 @@ def test_zeroconf_public(bitcoind, node_factory, chainparams):
l3.connect(l1)
wait_for(lambda: len(l3.rpc.listchannels()['channels']) == 2)

# Close the zerconf channel, check that we mark it as onchain_resolved ok
l1.rpc.close(l2.info['id'])
bitcoind.generate_block(1, wait_for_mempool=1)

# Channel should be marked resolved
for n in [l1, l2]:
wait_for(lambda: only_one([x for x in n.rpc.bkpr_listbalances()['accounts'] if x['account'] == channel_id])['account_resolved'])


def test_zeroconf_forward(node_factory, bitcoind):
"""Ensure that we can use zeroconf channels in forwards.
Expand Down

0 comments on commit 4822c54

Please sign in to comment.