diff --git a/common/utxo.h b/common/utxo.h index 93a16bbf5..09ec3db8e 100644 --- a/common/utxo.h +++ b/common/utxo.h @@ -39,6 +39,9 @@ struct utxo { /* NULL if not spent yet, otherwise, the block the spending transaction is in */ const u32 *spendheight; + /* Block this utxo becomes unreserved, if applicable */ + u32 *reserved_til; + /* The scriptPubkey if it is known */ u8 *scriptPubkey; diff --git a/wallet/db.c b/wallet/db.c index 5ef666b0e..b3cca1b9e 100644 --- a/wallet/db.c +++ b/wallet/db.c @@ -617,6 +617,7 @@ static struct migration dbmigrations[] = { /* We track the counter for coin_moves, as a convenience for notification consumers */ {SQL("INSERT INTO vars (name, intval) VALUES ('coin_moves_count', 0);"), NULL}, {NULL, migrate_last_tx_to_psbt}, + {SQL("ALTER TABLE outputs ADD reserved_til INTEGER DEFAULT NULL;"), NULL}, }; /* Leak tracking. */ diff --git a/wallet/wallet.c b/wallet/wallet.c index 3fdc8ee9d..1dfce9445 100644 --- a/wallet/wallet.c +++ b/wallet/wallet.c @@ -158,7 +158,7 @@ static bool wallet_add_utxo(struct wallet *w, struct utxo *utxo, static struct utxo *wallet_stmt2output(const tal_t *ctx, struct db_stmt *stmt) { struct utxo *utxo = tal(ctx, struct utxo); - u32 *blockheight, *spendheight; + u32 *blockheight, *spendheight, *reserved_til; db_column_txid(stmt, 0, &utxo->txid); utxo->outnum = db_column_int(stmt, 1); db_column_amount_sat(stmt, 2, &utxo->amount); @@ -184,6 +184,7 @@ static struct utxo *wallet_stmt2output(const tal_t *ctx, struct db_stmt *stmt) utxo->spendheight = NULL; utxo->scriptPubkey = NULL; utxo->scriptSig = NULL; + utxo->reserved_til = NULL; if (!db_column_is_null(stmt, 9)) { blockheight = tal(utxo, u32); @@ -202,6 +203,11 @@ static struct utxo *wallet_stmt2output(const tal_t *ctx, struct db_stmt *stmt) tal_dup_arr(utxo, u8, db_column_blob(stmt, 11), db_column_bytes(stmt, 11), 0); } + if (!db_column_is_null(stmt, 12)) { + reserved_til = tal(utxo, u32); + *reserved_til = db_column_int(stmt, 12); + utxo->reserved_til = reserved_til; + } return utxo; } @@ -255,6 +261,7 @@ struct utxo **wallet_get_utxos(const tal_t *ctx, struct wallet *w, const enum ou ", confirmation_height" ", spend_height" ", scriptpubkey " + ", reserved_til " "FROM outputs")); } else { stmt = db_prepare_v2(w->db, SQL("SELECT" @@ -270,6 +277,7 @@ struct utxo **wallet_get_utxos(const tal_t *ctx, struct wallet *w, const enum ou ", confirmation_height" ", spend_height" ", scriptpubkey " + ", reserved_til " "FROM outputs " "WHERE status= ? ")); db_bind_int(stmt, 0, output_status_in_db(state)); @@ -306,6 +314,7 @@ struct utxo **wallet_get_unconfirmed_closeinfo_utxos(const tal_t *ctx, ", confirmation_height" ", spend_height" ", scriptpubkey" + ", reserved_til" " FROM outputs" " WHERE channel_id IS NOT NULL AND " "confirmation_height IS NULL")); @@ -341,6 +350,7 @@ struct utxo *wallet_utxo_get(const tal_t *ctx, struct wallet *w, ", confirmation_height" ", spend_height" ", scriptpubkey" + ", reserved_til" " FROM outputs" " WHERE prev_out_tx = ?" " AND prev_out_index = ?")); @@ -3799,14 +3809,17 @@ static void process_utxo_result(struct bitcoind *bitcoind, enum output_status newstate = txout == NULL ? output_state_spent : output_state_available; - log_unusual(bitcoind->ld->wallet->log, - "wallet: reserved output %s/%u reset to %s", - type_to_string(tmpctx, struct bitcoin_txid, &utxos[0]->txid), - utxos[0]->outnum, - newstate == output_state_spent ? "spent" : "available"); - wallet_update_output_status(bitcoind->ld->wallet, - &utxos[0]->txid, utxos[0]->outnum, - utxos[0]->status, newstate); + /* Don't unreserve ones which are on timers */ + if (!utxos[0]->reserved_til || newstate == output_state_spent) { + log_unusual(bitcoind->ld->wallet->log, + "wallet: reserved output %s/%u reset to %s", + type_to_string(tmpctx, struct bitcoin_txid, &utxos[0]->txid), + utxos[0]->outnum, + newstate == output_state_spent ? "spent" : "available"); + wallet_update_output_status(bitcoind->ld->wallet, + &utxos[0]->txid, utxos[0]->outnum, + utxos[0]->status, newstate); + } /* If we have more, resolve them too. */ tal_arr_remove(&utxos, 0); diff --git a/wallet/walletrpc.c b/wallet/walletrpc.c index 0644c7826..8fdaf84a7 100644 --- a/wallet/walletrpc.c +++ b/wallet/walletrpc.c @@ -843,6 +843,19 @@ static const struct json_command listaddrs_command = { }; AUTODATA(json_command, &listaddrs_command); +bool is_reserved(const struct utxo *utxo, u32 current_height) +{ + if (utxo->status != output_state_reserved) + return false; + + /* FIXME: Eventually this will always be set! */ + if (!utxo->reserved_til) + return true; + + return *utxo->reserved_til > current_height; +} + + static void json_add_utxo(struct json_stream *response, const char *fieldname, struct wallet *wallet, @@ -899,7 +912,8 @@ static void json_add_utxo(struct json_stream *response, json_add_string(response, "status", "unconfirmed"); json_add_bool(response, "reserved", - utxo->status == output_state_reserved); + is_reserved(utxo, + get_block_height(wallet->ld->topology))); json_object_end(response); } @@ -1315,9 +1329,8 @@ static struct command_result *match_psbt_inputs_to_utxos(struct command *cmd, if (!utxo) continue; - /* Oops we haven't reserved this utxo yet. - * Let's just go ahead and reserve it now. */ - if (utxo->status != output_state_reserved) + /* Oops we haven't reserved this utxo yet! */ + if (!is_reserved(utxo, get_block_height(cmd->ld->topology))) return command_fail(cmd, LIGHTNINGD, "Aborting PSBT signing. UTXO %s:%u is not reserved", type_to_string(tmpctx, struct bitcoin_txid, diff --git a/wallet/walletrpc.h b/wallet/walletrpc.h index 3fc0b53f8..d9717baec 100644 --- a/wallet/walletrpc.h +++ b/wallet/walletrpc.h @@ -9,4 +9,7 @@ void json_add_utxos(struct json_stream *response, struct wallet *wallet, struct utxo **utxos); +/* We evaluate reserved timeouts lazily, so use this. */ +bool is_reserved(const struct utxo *utxo, u32 current_height); + #endif /* LIGHTNING_WALLET_WALLETRPC_H */