Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add a 'reservation expiration' mechanism #3789

Closed
wants to merge 11 commits into from

Conversation

niftynei
Copy link
Collaborator

It's useful to be able to 'retire' reservations on utxos after a certain time lapse. It's also useful to be able to RBF transactions. This PR lets us do both now, by adding two new parameters to reserveinputs.

  • expire_after which will functionally 'unreserve' a utxo after that many blocks have passed, and
  • allow_rbf which if set will create a psbt that has all of its inputs set to be rbf'able.

RBF'ability forced a few not so nice state breaks in the output status (you have to be able to select utxos that are marked as 'spent' but not in a block yet, etc). But hey, RBF, here it is!

Built ontop of the as of yet unmerged #3775 (new commits start at 37ba229)

@niftynei niftynei requested a review from cdecker as a code owner June 25, 2020 00:12
@niftynei niftynei mentioned this pull request Jun 25, 2020
wallet/wallet.c Show resolved Hide resolved
" WHERE status = ?"
" AND reserved_til <= ?"));
db_bind_int(stmt, 0, output_status_in_db(output_state_reserved));
db_bind_int(stmt, 1, w->ld->topology->tip->height);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would it make sense to pass in the current blockheight explicitly? Could make it a bit more explicit that expirations are blockheight based.

Comment on lines +498 to +503
reserved = expired_reserved_utxos_get(ctx, w);
available = wallet_get_utxos(ctx, w, output_state_available);

rcount = tal_count(reserved);
tal_resize(&reserved, rcount + tal_count(available));
for (size_t i = 0; i < tal_count(available); i++)
reserved[rcount + i] = tal_steal(reserved, available[i]);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shouldn't we rather just expire the reserved ones, and then query them directly as output_state_available? This way we keep the reservation but are also returning them as available.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yes but then we'd have to clean them up on every new block. Another option would be to remove the 'output_state_available'/ status altogether and just use the reserved_til as a marker of whether or not the utxo is still reserved.

* or 24 blocks */
u32 expires_at = cmd->ld->topology->tip->height + 6 * 4;
u32 expires_at = cmd->ld->topology->tip->height + 6 * 24;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The commit message needs an update if we change this default value.

wallet/walletrpc.c Show resolved Hide resolved
Comment on lines 1280 to 1287
for (size_t i = 0; i < tal_count(utx->wtx->utxos); i++)
wallet_output_reservation_update(cmd->ld->wallet,
utx->wtx->utxos[i],
*expire_at);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Seems to me like this uses the relative expiry as absolute blockheight. Should probably be something like the following:

Suggested change
for (size_t i = 0; i < tal_count(utx->wtx->utxos); i++)
wallet_output_reservation_update(cmd->ld->wallet,
utx->wtx->utxos[i],
*expire_at);
for (size_t i = 0; i < tal_count(utx->wtx->utxos); i++)
wallet_output_reservation_update(cmd->ld->wallet,
utx->wtx->utxos[i],
cmd->ld->topology->tip->height + *expire_at);

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah yes, good spot.

common/withdraw_tx.c Show resolved Hide resolved
Comment on lines -94 to -98
if (!*utxos)
return command_fail(cmd, JSONRPC2_INVALID_PARAMS,
"Could not decode all of the outpoints. The utxos"
" should be specified as an array of "
" 'txid:output_index'.");
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This looks like an unrelated change.

unreserve_psbt = bitcoind.rpc.createpsbt(unreserve_utxos, [])
unres = l1.rpc.unreserveinputs(unreserve_psbt)

# let's not set the fee high enough, should explodes
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
# let's not set the fee high enough, should explodes
# let's not set the fee high enough, should explode

Comment on lines +638 to +639
l1.rpc.sendpsbt(signed_psbt)
assert bitcoind.rpc.getmempoolinfo()['size'] == 1
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we check that the new tx is in the mempool? Just checking for the size of the mempool might pass despite the old tx still being in there. bitcoind.rpc.getrawmempool() will give you the txids.

@rustyrussell
Copy link
Contributor

Note: after discussing with @niftynei I agree with this concept, but she didn't take it far enough.

Because we didn't expire utxo reservations, we had multiple kludges:

  1. On startup, we unreserve all of them.
  2. We reserve them, but use a destructor to unreserve them.
  3. We mark them spent before we see the transaction in the blockchain, leading to missing funds and rescan if it never confirms.

On digging further, I noticed that with a simple change, we can move utxo.h entirely into the wallet dir. This further allows us to simplify our utxo handling.

So I've cherry picked part of the "reservations expire" commit and started ripping things apart...

bitcoin/psbt.c Outdated
Comment on lines 146 to 151
tmp_in = tx->inputs[tx->num_inputs - 1];
MAKE_ROOM(tx->inputs, insert_at, tx->num_inputs);

tx->inputs[insert_at] = tmp_in;


if (psbt->inputs_allocation_len < tx->num_inputs) {
struct wally_psbt_input *p = tal_arr(psbt, struct wally_psbt_input, tx->num_inputs);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Commit cf4054f is empty except this whitespace change, remove

wallet/wallet.c Outdated
Comment on lines 200 to 203
if (!db_column_is_null(stmt, 12)) {
reserved_til = tal(utxo, u32);
*reserved_til = db_column_int(stmt, 12);
utxo->reserved_til = reserved_til;
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would do this as:

 u32 reserved_til = db_column_int(stmt, 12);
 utxo->reserved_til = tal_dup(utxo, u32, &reserved_til);

Allow a utxo to be reserved until explicitly unreserved or until a timer
runs out. Currently unused
Check that the reservations stick around after a node restarts
If a utxo is unspent, don't remove the reservation unless it's expired
(or no expiration was set on it).
we don't pro-actively 'update' the state of a reserved utxo to available
when its lease expired, instead we compute whether or not it's now
considered eligible for spending, which is what the 'reserve' flag here
really is asking
if you haven't used this lease in 4 hours, it'll be eligible to be used
in a different utxo.
Now `reserveinputs` can parse its params on its own, which will let us
add more specific params for this command
Changelog-Added: JSON-API: `reserveinputs` has a new parameter, `expire_after`, which can be used to adjust or disable the reservation expiration mechansim (setting it to zero disables it)
Changelog-Added: JSON-API: `reserveinputs` has a new parameter, `allow_rbf`, which will make a RBF'able tx. Defaults to true
Adds an RBF test plus associated utxo state tracking fixes needed to
make this possible.

Mostly, you *must* specify which inputs you're spending when you attempt
to RBF (makes sense) -- these inputs are allowed to be marked as 'spent'
(which means we've broadcast a tx spending them) but must not be
included in a block yet (which would be indicated by the spendheight)
being populated.

There's a few other places that 'spending' already spent tx's is
problematic, e.g. when we mark a utxo as spent we now allow it to be coming
from 'any' status -> spent.

We also allow psbtsign to pull up any 'already spent' utxos to be signed
again
@niftynei
Copy link
Collaborator Author

This will largely be subsumed by work @rustyrussell is doing on the utxo infrastructure, but updated with comments for good measure.

@cdecker
Copy link
Member

cdecker commented Jul 7, 2020

This will largely be subsumed by work @rustyrussell is doing on the utxo infrastructure, but updated with comments for good measure.

Thanks for the heads up 👍

@ZmnSCPxj
Copy link
Collaborator

ZmnSCPxj commented Jul 7, 2020

This will largely be subsumed by work @rustyrussell is doing on the utxo infrastructure, but updated with comments for good measure.

Thanks for the heads up 👍

I thought this was #3798 , which was withdrawn? The nearest equivalent is #3821, which does not look like it has reservation expiration. What is the status? How does this affect #3763 which apparently need to be blocked for the new UTXO things?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants