Skip to content

Commit 826d9b1

Browse files
rustyrussellShahanaFarooqui
authored andcommitted
wallet: don't insert duplicate chain_moves entries after accounts.db migration.
When we migrate from accounts.db, we use the `account_nonchannel_id` field. But we can replay the block chain and the channel involved is still open, we will use the `account_channel_id` field, and our duplicate detection fails. As a result, we can end up with duplicate entries in the database, which make accounting incorrect. Signed-off-by: Rusty Russell <[email protected]> Changelog-Fixed: JSON-RPC: `listchainmoves` could contain bogus duplicate entries after 25.09 bookkeeper migration.
1 parent e6cfaab commit 826d9b1

File tree

2 files changed

+97
-53
lines changed

2 files changed

+97
-53
lines changed

wallet/wallet.c

Lines changed: 97 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -246,7 +246,7 @@ struct wallet *wallet_new(struct lightningd *ld, struct timers *timers)
246246
}
247247

248248
/* Get id for move_accounts; create if necessary */
249-
u64 move_accounts_id(struct db *db, const char *name)
249+
static u64 move_accounts_id(struct db *db, const char *name, bool create)
250250
{
251251
struct db_stmt *stmt;
252252
u64 ret;
@@ -268,6 +268,9 @@ u64 move_accounts_id(struct db *db, const char *name)
268268
}
269269
tal_free(stmt);
270270

271+
if (!create)
272+
return 0;
273+
271274
/* Does not exist, so create */
272275
stmt = db_prepare_v2(db,
273276
SQL("INSERT INTO move_accounts (name) VALUES (?)"));
@@ -3047,7 +3050,7 @@ void wallet_channel_close(struct wallet *w,
30473050
/* Update all accouting records to use channel_id string, instead of
30483051
* referring to dbid. This is robust if we delete in future, and saves
30493052
* a lookup in the load path. */
3050-
new_move_id = move_accounts_id(w->db, fmt_channel_id(tmpctx, &chan->cid));
3053+
new_move_id = move_accounts_id(w->db, fmt_channel_id(tmpctx, &chan->cid), true);
30513054
stmt = db_prepare_v2(w->db, SQL("UPDATE chain_moves "
30523055
"SET account_channel_id=?,"
30533056
" account_nonchannel_id=? "
@@ -6959,7 +6962,7 @@ void db_bind_mvt_account_id(struct db_stmt *stmt,
69596962
db_bind_null(stmt);
69606963
} else {
69616964
db_bind_null(stmt);
6962-
db_bind_u64(stmt, move_accounts_id(db, account->alt_account));
6965+
db_bind_u64(stmt, move_accounts_id(db, account->alt_account, true));
69636966
}
69646967
}
69656968

@@ -7085,73 +7088,124 @@ static u64 insert_chain_mvt(struct lightningd *ld,
70857088
return id;
70867089
}
70877090

7088-
void wallet_save_chain_mvt(struct lightningd *ld,
7089-
const struct chain_coin_mvt *chain_mvt)
7091+
static struct mvt_tags db_col_mvt_tags(struct db_stmt *stmt,
7092+
const char *colname)
7093+
{
7094+
struct mvt_tags tags;
7095+
tags.bits = db_col_u64(stmt, colname);
7096+
assert(mvt_tags_valid(tags));
7097+
return tags;
7098+
}
7099+
7100+
static bool find_duplicate_chain_move(struct db *db,
7101+
const char *nonchannel_acctname,
7102+
/* 0 if none */
7103+
u64 channel_dbid,
7104+
const struct bitcoin_outpoint *outpoint,
7105+
/* optional */
7106+
const struct bitcoin_txid *spending_txid,
7107+
struct mvt_tags tags,
7108+
u64 id_ceiling)
70907109
{
70917110
struct db_stmt *stmt;
7092-
u64 id;
7111+
u64 nonchannel_id;
70937112

7094-
/* On restart, we do chain replay. For this (and other
7095-
* reorgs) we need to de-duplicate here. The other db tables
7096-
* do this by deleting old entries on reorg, but we never
7097-
* delete. */
7098-
if (chain_mvt->account.channel) {
7099-
stmt = db_prepare_v2(ld->wallet->db,
7100-
SQL("SELECT"
7101-
" cm.spending_txid, cm.tag_bitmap, cm.id"
7102-
" FROM chain_moves cm"
7103-
" WHERE "
7104-
" account_channel_id = ?"
7105-
" AND utxo = ?"));
7106-
db_bind_u64(stmt, chain_mvt->account.channel->dbid);
7107-
} else {
7108-
stmt = db_prepare_v2(ld->wallet->db,
7109-
SQL("SELECT"
7110-
" cm.spending_txid, cm.tag_bitmap, cm.id"
7111-
" FROM chain_moves cm"
7112-
" JOIN move_accounts ma ON cm.account_nonchannel_id = ma.id"
7113-
" WHERE"
7114-
" ma.name = ?"
7115-
" AND utxo = ?"));
7116-
db_bind_text(stmt, chain_mvt->account.alt_account);
7117-
}
7118-
db_bind_outpoint(stmt, &chain_mvt->outpoint);
7113+
nonchannel_id = move_accounts_id(db, nonchannel_acctname, false);
7114+
7115+
stmt = db_prepare_v2(db,
7116+
SQL("SELECT"
7117+
" spending_txid, tag_bitmap, account_channel_id, account_nonchannel_id"
7118+
" FROM chain_moves"
7119+
" WHERE "
7120+
" utxo = ?"
7121+
" AND "
7122+
" id < ?"));
7123+
db_bind_outpoint(stmt, outpoint);
7124+
db_bind_u64(stmt, id_ceiling);
71197125
db_query_prepared(stmt);
71207126

71217127
/* Check that spending_txid and primary_tag match. We could
71227128
* probably just match on spending_txid, but this is robust. */
71237129
while (db_step(stmt)) {
7124-
struct mvt_tags tags;
7130+
struct mvt_tags these_tags;
7131+
u64 this_nonchannel_id, this_channel_id;
7132+
bool have_spending_txid;
71257133

71267134
/* Access this now so it never complains we don't */
7127-
tags.bits = db_col_u64(stmt, "cm.tag_bitmap");
7128-
id = db_col_u64(stmt, "cm.id");
7135+
these_tags = db_col_mvt_tags(stmt, "tag_bitmap");
7136+
have_spending_txid = !db_col_is_null(stmt, "spending_txid");
7137+
this_nonchannel_id = db_col_is_null(stmt, "account_nonchannel_id") ? 0 : db_col_u64(stmt, "account_nonchannel_id");
7138+
this_channel_id = db_col_is_null(stmt, "account_channel_id") ? 0 : db_col_u64(stmt, "account_channel_id");
7139+
7140+
/* Either nonchannel id, or channel id must match */
7141+
if (nonchannel_id != this_nonchannel_id
7142+
&& channel_dbid != this_channel_id) {
7143+
continue;
7144+
}
71297145

71307146
/* spending_txid must match */
7131-
if (chain_mvt->spending_txid) {
7147+
if (spending_txid) {
71327148
struct bitcoin_txid txid;
7133-
if (db_col_is_null(stmt, "cm.spending_txid"))
7149+
if (!have_spending_txid)
71347150
continue;
7135-
db_col_txid(stmt, "cm.spending_txid", &txid);
7151+
db_col_txid(stmt, "spending_txid", &txid);
71367152
/* This would only happen for reorgs */
7137-
if (!bitcoin_txid_eq(&txid, chain_mvt->spending_txid))
7153+
if (!bitcoin_txid_eq(&txid, spending_txid))
71387154
continue;
71397155
} else {
7140-
if (!db_col_is_null(stmt, "cm.spending_txid"))
7156+
if (have_spending_txid)
71417157
continue;
71427158
}
71437159

71447160
/* Tags must match */
7145-
if (primary_mvt_tag(tags) != primary_mvt_tag(chain_mvt->tags))
7161+
if (primary_mvt_tag(these_tags) != primary_mvt_tag(tags))
71467162
continue;
71477163

7148-
/* It's a duplicate. Don't re-add. */
7164+
/* It's a duplicate. */
71497165
tal_free(stmt);
7150-
goto out;
7166+
return true;
71517167
}
71527168
tal_free(stmt);
7169+
return false;
7170+
}
7171+
7172+
static bool is_duplicate(struct db *db, const struct chain_coin_mvt *chain_mvt)
7173+
{
7174+
const char *nonchannel_acctname;
7175+
u64 channel_dbid;
7176+
7177+
/* If we migrated in account_migration.c, it will use the
7178+
* nonchannel id! But we don't worry about adding a
7179+
* non-channel which conflicts with a channel. */
7180+
if (chain_mvt->account.channel) {
7181+
nonchannel_acctname = fmt_channel_id(tmpctx, &chain_mvt->account.channel->cid);
7182+
channel_dbid = chain_mvt->account.channel->dbid;
7183+
} else {
7184+
nonchannel_acctname = chain_mvt->account.alt_account;
7185+
channel_dbid = 0;
7186+
}
71537187

7154-
id = insert_chain_mvt(ld, ld->wallet->db, chain_mvt),
7188+
return find_duplicate_chain_move(db, nonchannel_acctname,
7189+
channel_dbid,
7190+
&chain_mvt->outpoint,
7191+
chain_mvt->spending_txid,
7192+
chain_mvt->tags,
7193+
INT64_MAX);
7194+
}
7195+
7196+
void wallet_save_chain_mvt(struct lightningd *ld,
7197+
const struct chain_coin_mvt *chain_mvt)
7198+
{
7199+
u64 id;
7200+
7201+
/* On restart, we do chain replay. For this (and other
7202+
* reorgs) we need to de-duplicate here. The other db tables
7203+
* do this by deleting old entries on reorg, but we never
7204+
* delete. */
7205+
if (is_duplicate(ld->wallet->db, chain_mvt))
7206+
goto out;
7207+
7208+
id = insert_chain_mvt(ld, ld->wallet->db, chain_mvt);
71557209
notify_chain_mvt(ld, chain_mvt, id);
71567210
out:
71577211
if (taken(chain_mvt))
@@ -7190,15 +7244,6 @@ static void db_col_credit_or_debit(struct db_stmt *stmt,
71907244
}
71917245
}
71927246

7193-
static struct mvt_tags db_col_mvt_tags(struct db_stmt *stmt,
7194-
const char *colname)
7195-
{
7196-
struct mvt_tags tags;
7197-
tags.bits = db_col_u64(stmt, colname);
7198-
assert(mvt_tags_valid(tags));
7199-
return tags;
7200-
}
7201-
72027247
struct chain_coin_mvt *wallet_chain_move_extract(const tal_t *ctx,
72037248
struct db_stmt *stmt,
72047249
struct lightningd *ld,

wallet/wallet.h

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1919,7 +1919,6 @@ void db_bind_mvt_account_id(struct db_stmt *stmt,
19191919
void db_bind_credit_debit(struct db_stmt *stmt,
19201920
struct amount_msat credit,
19211921
struct amount_msat debit);
1922-
u64 move_accounts_id(struct db *db, const char *name);
19231922
void wallet_datastore_save_utxo_description(struct db *db,
19241923
const struct bitcoin_outpoint *outpoint,
19251924
const char *desc);

0 commit comments

Comments
 (0)