-
Notifications
You must be signed in to change notification settings - Fork 123
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
Conversation
There was a problem hiding this 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 && |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
a3a9d35
to
210ca4b
Compare
210ca4b
to
814ea4b
Compare
Latest push addresses most comments. Though I think I should address the following TODOs before a full re-review:
|
There was a problem hiding this 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.
We do that here: taproot-assets/itest/send_test.go Line 766 in 814ea4b
|
5c8c75c
to
2691375
Compare
Only the extended unit tests missing now, re-requesting review to make sure this is what you had in mind, @Roasbeef. |
There was a problem hiding this 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
tapdb/assets_store.go
Outdated
var assetFilter QueryAssetFilters | ||
assetFilter := QueryAssetFilters{ | ||
Now: sql.NullTime{ | ||
Time: time.Now().UTC(), |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
2691375
to
c3d8874
Compare
In this commit we introduce a clock to each database that uses time.Now() to simplify mocking out those timestamps in the future.
c3d8874
to
a3884b4
Compare
a3884b4
to
93a8a8d
Compare
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.
93a8a8d
to
91c3848
Compare
Added all unit tests that I wanted to add, ready for another round of review. |
There was a problem hiding this 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()) { |
There was a problem hiding this comment.
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.
spend *tapfreighter.OutboundParcel, finalLeaseOwner [32]byte, | ||
finalLeaseExpiry time.Time) error { |
There was a problem hiding this comment.
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?
// 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. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
👍
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM 🦆
Depends on #429.Fixes #261.
This PR introduces two new "coin locks":
btcwallet
'sWithCoinSelectLock
) 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)managed_utxo
level, which is called a "lease" (to reflect the terminology used inlnd
: 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 RPCAllow filtering assets byleased=true/false