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

wallet: add asset coin locking #431

Merged
merged 12 commits into from
Aug 16, 2023
Merged

wallet: add asset coin locking #431

merged 12 commits into from
Aug 16, 2023

Conversation

guggero
Copy link
Member

@guggero guggero commented Aug 8, 2023

Depends on #429.

Fixes #261.

This PR introduces two new "coin locks":

  • One on the wallet level (similar to btcwallet's WithCoinSelectLock) that allows multiple DB calls to be made while holding an exclusive lock (e.g. do coin selection then "lease" a specific coin to spend, those cannot be done in a single DB transaction the way the code is structured)
  • One on the managed_utxo level, which is called a "lease" (to reflect the terminology used in lnd: An application can "lease" (=lock/reserve) a bitcoin level UTXO to be spent in a transaction. So all assets committed to that BTC output are not available for other RPC users as long as the UTXO is being leased.

TODOs (might decide to put those into a second PR):

  • Allow releasing a lease (unlock coin) through RPC
  • Allow filtering assets by leased=true/false

@guggero guggero requested review from Roasbeef and ffranr August 8, 2023 18:13
@guggero guggero added the v0.3 label Aug 8, 2023
@Neil-LL Neil-LL assigned Neil-LL and guggero and unassigned Neil-LL Aug 8, 2023
Copy link
Member

@Roasbeef Roasbeef left a comment

Choose a reason for hiding this comment

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

Great work! Changes look to be headed in the right direction at a high level. I think my major review comment relates to exactly where the coin selection lock should live it. Right now it lives at the bottom of the abstraction layer in the db, which also means we now have a mutex in the way of db code. In the past (eg lnd), we've run into issues with user space mutexes on top of what the db is already doing. I think we should try to lift the mutex up to the highest level, understanding that some calls may just want a snapshot of the db, and don't necessarily need the same level of contextual locking as needed when trying to perform a transfer.

Also as is, it looks like an output can be locked twice. If the lease are a new table, and then we use an outpoint as the primary key, we can reject such behavior as the unique conflict then fails. Finally, I didn't see where the leases are expired, seems to be sort of implicit rn?

// valid and hasn't expired.
owner := sprout.AnchorLeaseOwner
expiry := sprout.AnchorLeaseExpiry
if len(owner) > 0 && expiry.Valid &&
Copy link
Member

Choose a reason for hiding this comment

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

How/when are leases to be cleaned up?

Copy link
Member Author

Choose a reason for hiding this comment

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

This isn't currently done. Not sure what the best approach would be. In btcwallet I think we check the leases on certain actions. We could do the same here as well, e.g. whenever we list the coins or insert a new lease.
Or we could do it with a timed job. Going to see what makes most sense while addressing the other comments.

Copy link
Member

Choose a reason for hiding this comment

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

Ah yeah, we went with the lazy approach in btcwallet. One disadvantage I can think of for the lazy approach is that then every time you want to go fetch just the set of leases, you also need to have that be a write transaction, as you might end up deleting them. This may also impact the decision to keep the leases in the ChainAsset struct as well.

@guggero guggero force-pushed the asset-coin-locking branch from a3a9d35 to 210ca4b Compare August 10, 2023 13:11
@guggero guggero force-pushed the asset-coin-locking branch from 210ca4b to 814ea4b Compare August 10, 2023 15:02
@guggero
Copy link
Member Author

guggero commented Aug 10, 2023

Latest push addresses most comments. Though I think I should address the following TODOs before a full re-review:

  • Add more unit tests around the leases and the CoinSelect struct
  • Periodically (or upon certain requests) remove old leases
  • Allow filtering assets by lease status on the RPC, assert leases in itest
  • Update lease to not expire after anchoring TX has been published

Copy link
Contributor

@ffranr ffranr left a comment

Choose a reason for hiding this comment

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

Nice work! Looks good to me, but I don't think I've reviewed the very latest changes. I don't have too much to add beyond Laolu's comments.

I think we should probably add an integration test to ensure that the send process fails if all candidate UTXOs are locked.

@guggero
Copy link
Member Author

guggero commented Aug 10, 2023

I think we should probably add an integration test to ensure that the send process fails if all candidate UTXOs are locked.

We do that here:

// Before we mine the next block, we'll make sure that we get a proper

@guggero guggero force-pushed the asset-coin-locking branch 2 times, most recently from 5c8c75c to 2691375 Compare August 14, 2023 14:47
@guggero
Copy link
Member Author

guggero commented Aug 14, 2023

Only the extended unit tests missing now, re-requesting review to make sure this is what you had in mind, @Roasbeef.

@guggero guggero requested review from Roasbeef and ffranr August 14, 2023 14:49
Copy link
Member

@Roasbeef Roasbeef left a comment

Choose a reason for hiding this comment

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

The change set is looking pretty good! Will pull it down and mess around with some of the new functionality on the CLI to see how intuitive/surprising it is

var assetFilter QueryAssetFilters
assetFilter := QueryAssetFilters{
Now: sql.NullTime{
Time: time.Now().UTC(),
Copy link
Member

Choose a reason for hiding this comment

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

Not blocking at all here, but I think we'll want to eventually start to use clock.Clock (from lnd) here so we can eventually mock out the time for unit tests.

Copy link
Member Author

Choose a reason for hiding this comment

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

Cool, makes sense. Added a commit in the beginning.

@guggero guggero force-pushed the asset-coin-locking branch from 2691375 to c3d8874 Compare August 16, 2023 13:54
@guggero guggero force-pushed the asset-coin-locking branch from c3d8874 to a3884b4 Compare August 16, 2023 13:54
@guggero guggero force-pushed the asset-coin-locking branch from a3884b4 to 93a8a8d Compare August 16, 2023 13:57
To avoid an issue that we sometimes ran into in lnd where a UTXO isn't
locked/leased after a spend transaction is published, we now lease any
spent outputs for a whole year, right before we publish the spend TX.
@guggero guggero force-pushed the asset-coin-locking branch from 93a8a8d to 91c3848 Compare August 16, 2023 14:07
@guggero guggero requested a review from Roasbeef August 16, 2023 15:14
@guggero
Copy link
Member Author

guggero commented Aug 16, 2023

Added all unit tests that I wanted to add, ready for another round of review.

Copy link
Contributor

@ffranr ffranr left a comment

Choose a reason for hiding this comment

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

I think I've managed to review the changes since my last review. Everything looks good to me. Nice addition of the clock to different db stores. And a test which ensures that locking an outpoint affects each asset anchored at that outpoint 👍

owner := sprout.AnchorLeaseOwner
expiry := sprout.AnchorLeaseExpiry
if len(owner) > 0 && expiry.Valid &&
expiry.Time.UTC().After(a.clock.Now().UTC()) {
Copy link
Contributor

Choose a reason for hiding this comment

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

nit: the if condition is getting complicated here.

I would consider evaluating a bool variable before the if clause:

var (
    hasOwner = len(owner) > 0
    isExpired = expiry.Valid && expiry.Time.UTC().After(a.clock.Now().UTC())
)

if hasOwner && isExpired {

Or maybe not, it's a style.

Comment on lines +1827 to +1828
spend *tapfreighter.OutboundParcel, finalLeaseOwner [32]byte,
finalLeaseExpiry time.Time) error {
Copy link
Contributor

Choose a reason for hiding this comment

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

nit: I don't understand why we need the final prefix here in finalLeaseOwner and finalLeaseExpiry. Consider removing?

Comment on lines +786 to +787
// Now we lease the first asset for 1 hour. This will cause the second
// one also to be leased, since it's on the same anchor transaction.
Copy link
Contributor

Choose a reason for hiding this comment

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

👍

Copy link
Member

@Roasbeef Roasbeef left a comment

Choose a reason for hiding this comment

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

LGTM 🦆

@Roasbeef Roasbeef added this pull request to the merge queue Aug 16, 2023
@Roasbeef Roasbeef removed this pull request from the merge queue due to a manual request Aug 16, 2023
@Roasbeef Roasbeef merged commit 6bbe56a into main Aug 16, 2023
@guggero guggero deleted the asset-coin-locking branch August 17, 2023 08:04
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
Status: ✅ Done
Development

Successfully merging this pull request may close these issues.

asset wallet: introduce coin locking and coin selection mutex
4 participants