From e048292fdf0a50854da40079af8f01f1a89e8537 Mon Sep 17 00:00:00 2001 From: niftynei Date: Wed, 27 Jul 2022 16:18:05 -0500 Subject: [PATCH] bkpr-zeroconf: Zeroconfs will emit 'channel_proposed' event 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). --- plugins/bkpr/recorder.c | 24 ++++++++++++----- plugins/bkpr/test/Makefile | 1 + plugins/bkpr/test/run-bkpr_db.c | 9 ++++--- plugins/bkpr/test/run-recorder.c | 9 ++++--- tests/test_opening.py | 44 +++++++++++++++++++++++++++++--- 5 files changed, 71 insertions(+), 16 deletions(-) diff --git a/plugins/bkpr/recorder.c b/plugins/bkpr/recorder.c index 2eb500c5e..f2a0f4f4c 100644 --- a/plugins/bkpr/recorder.c +++ b/plugins/bkpr/recorder.c @@ -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)); @@ -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); } @@ -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; @@ -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); @@ -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" diff --git a/plugins/bkpr/test/Makefile b/plugins/bkpr/test/Makefile index 642e35214..e6f16016f 100644 --- a/plugins/bkpr/test/Makefile +++ b/plugins/bkpr/test/Makefile @@ -11,6 +11,7 @@ BOOKKEEPER_TEST_COMMON_OBJS := \ common/base32.o \ common/blockheight_states.o \ common/channel_type.o \ + common/coin_mvt.o \ common/features.o \ common/json_stream.o \ common/key_derive.o \ diff --git a/plugins/bkpr/test/run-bkpr_db.c b/plugins/bkpr/test/run-bkpr_db.c index cab5ff9fb..198512849 100644 --- a/plugins/bkpr/test/run-bkpr_db.c +++ b/plugins/bkpr/test/run-bkpr_db.c @@ -88,6 +88,9 @@ u8 fromwire_u8(const u8 **cursor UNNEEDED, size_t *max UNNEEDED) /* Generated stub for fromwire_u8_array */ void fromwire_u8_array(const u8 **cursor UNNEEDED, size_t *max UNNEEDED, u8 *arr UNNEEDED, size_t num UNNEEDED) { fprintf(stderr, "fromwire_u8_array called!\n"); abort(); } +/* Generated stub for fromwire_wirestring */ +char *fromwire_wirestring(const tal_t *ctx UNNEEDED, const u8 **cursor UNNEEDED, size_t *max UNNEEDED) +{ fprintf(stderr, "fromwire_wirestring called!\n"); abort(); } /* Generated stub for htlc_state_flags */ int htlc_state_flags(enum htlc_state state UNNEEDED) { fprintf(stderr, "htlc_state_flags called!\n"); abort(); } @@ -177,9 +180,6 @@ enum htlc_state last_fee_state(enum side opener UNNEEDED) /* Generated stub for log_level_name */ const char *log_level_name(enum log_level level UNNEEDED) { fprintf(stderr, "log_level_name called!\n"); abort(); } -/* Generated stub for mvt_tag_str */ -const char *mvt_tag_str(enum mvt_tag tag UNNEEDED) -{ fprintf(stderr, "mvt_tag_str called!\n"); abort(); } /* Generated stub for new_channel_event */ struct channel_event *new_channel_event(const tal_t *ctx UNNEEDED, const char *tag UNNEEDED, @@ -225,6 +225,9 @@ void towire_u8(u8 **pptr UNNEEDED, u8 v UNNEEDED) /* Generated stub for towire_u8_array */ void towire_u8_array(u8 **pptr UNNEEDED, const u8 *arr UNNEEDED, size_t num UNNEEDED) { fprintf(stderr, "towire_u8_array called!\n"); abort(); } +/* Generated stub for towire_wirestring */ +void towire_wirestring(u8 **pptr UNNEEDED, const char *str UNNEEDED) +{ fprintf(stderr, "towire_wirestring called!\n"); abort(); } /* AUTOGENERATED MOCKS END */ static char *tmp_dsn(const tal_t *ctx) diff --git a/plugins/bkpr/test/run-recorder.c b/plugins/bkpr/test/run-recorder.c index b62cedb61..c71f040fe 100644 --- a/plugins/bkpr/test/run-recorder.c +++ b/plugins/bkpr/test/run-recorder.c @@ -94,6 +94,9 @@ u8 fromwire_u8(const u8 **cursor UNNEEDED, size_t *max UNNEEDED) /* Generated stub for fromwire_u8_array */ void fromwire_u8_array(const u8 **cursor UNNEEDED, size_t *max UNNEEDED, u8 *arr UNNEEDED, size_t num UNNEEDED) { fprintf(stderr, "fromwire_u8_array called!\n"); abort(); } +/* Generated stub for fromwire_wirestring */ +char *fromwire_wirestring(const tal_t *ctx UNNEEDED, const u8 **cursor UNNEEDED, size_t *max UNNEEDED) +{ fprintf(stderr, "fromwire_wirestring called!\n"); abort(); } /* Generated stub for htlc_state_flags */ int htlc_state_flags(enum htlc_state state UNNEEDED) { fprintf(stderr, "htlc_state_flags called!\n"); abort(); } @@ -183,9 +186,6 @@ enum htlc_state last_fee_state(enum side opener UNNEEDED) /* Generated stub for log_level_name */ const char *log_level_name(enum log_level level UNNEEDED) { fprintf(stderr, "log_level_name called!\n"); abort(); } -/* Generated stub for mvt_tag_str */ -const char *mvt_tag_str(enum mvt_tag tag UNNEEDED) -{ fprintf(stderr, "mvt_tag_str called!\n"); abort(); } /* Generated stub for new_channel_event */ struct channel_event *new_channel_event(const tal_t *ctx UNNEEDED, const char *tag UNNEEDED, @@ -231,6 +231,9 @@ void towire_u8(u8 **pptr UNNEEDED, u8 v UNNEEDED) /* Generated stub for towire_u8_array */ void towire_u8_array(u8 **pptr UNNEEDED, const u8 *arr UNNEEDED, size_t num UNNEEDED) { fprintf(stderr, "towire_u8_array called!\n"); abort(); } +/* Generated stub for towire_wirestring */ +void towire_wirestring(u8 **pptr UNNEEDED, const char *str UNNEEDED) +{ fprintf(stderr, "towire_wirestring called!\n"); abort(); } /* AUTOGENERATED MOCKS END */ static char *tmp_dsn(const tal_t *ctx) diff --git a/tests/test_opening.py b/tests/test_opening.py index f90a18a1b..96db12fc0 100644 --- a/tests/test_opening.py +++ b/tests/test_opening.py @@ -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) @@ -1342,6 +1344,7 @@ 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) @@ -1351,10 +1354,22 @@ def test_zeroconf_public(bitcoind, node_factory, chainparams): chan_val = 993198000 if chainparams['elements'] else 995673000 l1_mvts = [ {'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') @@ -1364,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': 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. @@ -1378,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.