Merge pull request #736 from bottlepay/wtxmgr-leases

wallet: list leases and parameterize duration
This commit is contained in:
Olaoluwa Osuntokun 2021-03-12 15:29:44 -08:00 committed by GitHub
commit 4ec908df93
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
3 changed files with 59 additions and 16 deletions

View file

@ -2659,6 +2659,19 @@ func (w *Wallet) ListUnspent(minconf, maxconf int32,
return results, err return results, err
} }
// ListLeasedOutputs returns a list of objects representing the currently locked
// utxos.
func (w *Wallet) ListLeasedOutputs() ([]*wtxmgr.LockedOutput, error) {
var outputs []*wtxmgr.LockedOutput
err := walletdb.View(w.db, func(tx walletdb.ReadTx) error {
ns := tx.ReadBucket(wtxmgrNamespaceKey)
var err error
outputs, err = w.TxStore.ListLockedOutputs(ns)
return err
})
return outputs, err
}
// DumpPrivKeys returns the WIF-encoded private keys for all addresses with // DumpPrivKeys returns the WIF-encoded private keys for all addresses with
// private keys in a wallet. // private keys in a wallet.
func (w *Wallet) DumpPrivKeys() ([]string, error) { func (w *Wallet) DumpPrivKeys() ([]string, error) {
@ -2895,12 +2908,14 @@ func (w *Wallet) LockedOutpoints() []btcjson.TransactionInput {
// //
// NOTE: This differs from LockOutpoint in that outputs are locked for a limited // NOTE: This differs from LockOutpoint in that outputs are locked for a limited
// amount of time and their locks are persisted to disk. // amount of time and their locks are persisted to disk.
func (w *Wallet) LeaseOutput(id wtxmgr.LockID, op wire.OutPoint) (time.Time, error) { func (w *Wallet) LeaseOutput(id wtxmgr.LockID, op wire.OutPoint,
duration time.Duration) (time.Time, error) {
var expiry time.Time var expiry time.Time
err := walletdb.Update(w.db, func(tx walletdb.ReadWriteTx) error { err := walletdb.Update(w.db, func(tx walletdb.ReadWriteTx) error {
ns := tx.ReadWriteBucket(wtxmgrNamespaceKey) ns := tx.ReadWriteBucket(wtxmgrNamespaceKey)
var err error var err error
expiry, err = w.TxStore.LockOutput(ns, id, op) expiry, err = w.TxStore.LockOutput(ns, id, op, duration)
return err return err
}) })
return expiry, err return expiry, err

View file

@ -24,9 +24,6 @@ import (
const ( const (
// TxLabelLimit is the length limit we impose on transaction labels. // TxLabelLimit is the length limit we impose on transaction labels.
TxLabelLimit = 500 TxLabelLimit = 500
// DefaultLockDuration is the default duration used to lock outputs.
DefaultLockDuration = 10 * time.Minute
) )
var ( var (
@ -125,6 +122,14 @@ type TxRecord struct {
SerializedTx []byte // Optional: may be nil SerializedTx []byte // Optional: may be nil
} }
// LockedOutput is a type that contains an outpoint of an UTXO and its lock
// lease information.
type LockedOutput struct {
Outpoint wire.OutPoint
LockID LockID
Expiration time.Time
}
// NewTxRecord creates a new transaction record that may be inserted into the // NewTxRecord creates a new transaction record that may be inserted into the
// store. It uses memoization to save the transaction hash and the serialized // store. It uses memoization to save the transaction hash and the serialized
// transaction. // transaction.
@ -1154,7 +1159,7 @@ func isKnownOutput(ns walletdb.ReadWriteBucket, op wire.OutPoint) bool {
// already been locked to a different ID, then ErrOutputAlreadyLocked is // already been locked to a different ID, then ErrOutputAlreadyLocked is
// returned. // returned.
func (s *Store) LockOutput(ns walletdb.ReadWriteBucket, id LockID, func (s *Store) LockOutput(ns walletdb.ReadWriteBucket, id LockID,
op wire.OutPoint) (time.Time, error) { op wire.OutPoint, duration time.Duration) (time.Time, error) {
// Make sure the output is known. // Make sure the output is known.
if !isKnownOutput(ns, op) { if !isKnownOutput(ns, op) {
@ -1167,7 +1172,7 @@ func (s *Store) LockOutput(ns walletdb.ReadWriteBucket, id LockID,
return time.Time{}, ErrOutputAlreadyLocked return time.Time{}, ErrOutputAlreadyLocked
} }
expiry := s.clock.Now().Add(DefaultLockDuration) expiry := s.clock.Now().Add(duration)
if err := lockOutput(ns, id, op, expiry); err != nil { if err := lockOutput(ns, id, op, expiry); err != nil {
return time.Time{}, err return time.Time{}, err
} }
@ -1226,3 +1231,25 @@ func (s *Store) DeleteExpiredLockedOutputs(ns walletdb.ReadWriteBucket) error {
return nil return nil
} }
// ListLockedOutputs returns a list of objects representing the currently locked
// utxos.
func (s *Store) ListLockedOutputs(ns walletdb.ReadBucket) ([]*LockedOutput,
error) {
var outputs []*LockedOutput
err := forEachLockedOutput(
ns, func(op wire.OutPoint, id LockID, expiration time.Time) {
outputs = append(outputs, &LockedOutput{
Outpoint: op,
LockID: id,
Expiration: expiration,
})
},
)
if err != nil {
return nil, err
}
return outputs, nil
}

View file

@ -2425,19 +2425,20 @@ func assertOutputLocksExist(t *testing.T, s *Store, ns walletdb.ReadBucket,
t.Helper() t.Helper()
var found []wire.OutPoint outputs, err := s.ListLockedOutputs(ns)
forEachLockedOutput(ns, func(op wire.OutPoint, _ LockID, _ time.Time) { if err != nil {
found = append(found, op) t.Fatal(err)
}) }
if len(found) != len(exp) {
if len(outputs) != len(exp) {
t.Fatalf("expected to find %v locked output(s), found %v", t.Fatalf("expected to find %v locked output(s), found %v",
len(exp), len(found)) len(exp), len(outputs))
} }
for _, expOp := range exp { for _, expOp := range exp {
exists := false exists := false
for _, foundOp := range found { for _, found := range outputs {
if expOp == foundOp { if expOp == found.Outpoint {
exists = true exists = true
break break
} }
@ -2453,7 +2454,7 @@ func lock(t *testing.T, s *Store, ns walletdb.ReadWriteBucket,
t.Helper() t.Helper()
expiry, err := s.LockOutput(ns, id, op) expiry, err := s.LockOutput(ns, id, op, 10*time.Minute)
if err != exp { if err != exp {
t.Fatalf("expected err %q, got %q", exp, err) t.Fatalf("expected err %q, got %q", exp, err)
} }