diff --git a/go.sum b/go.sum index 7c1715a..b06c554 100644 --- a/go.sum +++ b/go.sum @@ -27,6 +27,7 @@ github.com/coreos/bbolt v1.3.3 h1:n6AiVyVRKQFNb6mJlwESEvvLoDyiTzXX7ORAUlkeBdY= github.com/coreos/bbolt v1.3.3/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk= github.com/davecgh/go-spew v0.0.0-20171005155431-ecdeabc65495 h1:6IyqGr3fnd0tM3YxipK27TUskaOVUjU2nG45yzwcQKY= github.com/davecgh/go-spew v0.0.0-20171005155431-ecdeabc65495/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I= @@ -59,6 +60,8 @@ github.com/lightninglabs/gozmq v0.0.0-20191113021534-d20a764486bf h1:HZKvJUHlcXI github.com/lightninglabs/gozmq v0.0.0-20191113021534-d20a764486bf/go.mod h1:vxmQPeIQxPf6Jf9rM8R+B4rKBqLA2AjttNxkFBL2Plk= github.com/lightninglabs/neutrino v0.11.0 h1:lPpYFCtsfJX2W5zI4pWycPmbbBdr7zU+BafYdLoD6k0= github.com/lightninglabs/neutrino v0.11.0/go.mod h1:CuhF0iuzg9Sp2HO6ZgXgayviFTn1QHdSTJlMncK80wg= +github.com/lightningnetwork/lnd/clock v1.0.1 h1:QQod8+m3KgqHdvVMV+2DRNNZS1GRFir8mHZYA+Z2hFo= +github.com/lightningnetwork/lnd/clock v1.0.1/go.mod h1:KnQudQ6w0IAMZi1SgvecLZQZ43ra2vpDNj7H/aasemg= github.com/lightningnetwork/lnd/queue v1.0.1 h1:jzJKcTy3Nj5lQrooJ3aaw9Lau3I0IwvQR5sqtjdv2R0= github.com/lightningnetwork/lnd/queue v1.0.1/go.mod h1:vaQwexir73flPW43Mrm7JOgJHmcEFBWWSl9HlyASoms= github.com/lightningnetwork/lnd/ticker v1.0.0 h1:S1b60TEGoTtCe2A0yeB+ecoj/kkS4qpwh6l+AkQEZwU= @@ -71,6 +74,9 @@ github.com/onsi/gomega v1.4.1 h1:PZSj/UFNaVp3KxrzHOcS7oyuWA7LoOY/77yCTEFu21U= github.com/onsi/gomega v1.4.1/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA= github.com/onsi/gomega v1.4.3 h1:RE1xgDvH7imwFD45h+u2SgIfERHlS2yNG4DObb5BSKU= github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= go.etcd.io/bbolt v1.3.3 h1:MUGmc65QhB3pIlaQ5bB4LwqSj6GIonVJXpZiaKNyaKk= go.etcd.io/bbolt v1.3.3/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= golang.org/x/crypto v0.0.0-20170930174604-9419663f5a44 h1:9lP3x0pW80sDI6t1UMSLA4to18W7R7imwAI/sWS9S8Q= diff --git a/wallet/wallet.go b/wallet/wallet.go index ad16ab9..6cd55df 100644 --- a/wallet/wallet.go +++ b/wallet/wallet.go @@ -315,9 +315,9 @@ func (w *Wallet) SetChainSynced(synced bool) { // activeData returns the currently-active receiving addresses and all unspent // outputs. This is primarely intended to provide the parameters for a // rescan request. -func (w *Wallet) activeData(dbtx walletdb.ReadTx) ([]btcutil.Address, []wtxmgr.Credit, error) { +func (w *Wallet) activeData(dbtx walletdb.ReadWriteTx) ([]btcutil.Address, []wtxmgr.Credit, error) { addrmgrNs := dbtx.ReadBucket(waddrmgrNamespaceKey) - txmgrNs := dbtx.ReadBucket(wtxmgrNamespaceKey) + txmgrNs := dbtx.ReadWriteBucket(wtxmgrNamespaceKey) var addrs []btcutil.Address err := w.Manager.ForEachRelevantActiveAddress( @@ -329,6 +329,16 @@ func (w *Wallet) activeData(dbtx walletdb.ReadTx) ([]btcutil.Address, []wtxmgr.C if err != nil { return nil, nil, err } + + // Before requesting the list of spendable UTXOs, we'll delete any + // expired output locks. + err = w.TxStore.DeleteExpiredLockedOutputs( + dbtx.ReadWriteBucket(wtxmgrNamespaceKey), + ) + if err != nil { + return nil, nil, err + } + unspent, err := w.TxStore.UnspentOutputs(txmgrNs) return addrs, unspent, err } @@ -508,7 +518,7 @@ func (w *Wallet) syncWithChain(birthdayStamp *waddrmgr.BlockStamp) error { addrs []btcutil.Address unspent []wtxmgr.Credit ) - err = walletdb.View(w.db, func(dbtx walletdb.ReadTx) error { + err = walletdb.Update(w.db, func(dbtx walletdb.ReadWriteTx) error { addrs, unspent, err = w.activeData(dbtx) return err }) @@ -2854,6 +2864,42 @@ func (w *Wallet) LockedOutpoints() []btcjson.TransactionInput { return locked } +// LeaseOutput locks an output to the given ID, preventing it from being +// available for coin selection. The absolute time of the lock's expiration is +// returned. The expiration of the lock can be extended by successive +// invocations of this call. +// +// Outputs can be unlocked before their expiration through `UnlockOutput`. +// Otherwise, they are unlocked lazily through calls which iterate through all +// known outputs, e.g., `CalculateBalance`, `ListUnspent`. +// +// If the output is not known, ErrUnknownOutput is returned. If the output has +// already been locked to a different ID, then ErrOutputAlreadyLocked is +// returned. +// +// NOTE: This differs from LockOutpoint in that outputs are locked for a limited +// amount of time and their locks are persisted to disk. +func (w *Wallet) LeaseOutput(id wtxmgr.LockID, op wire.OutPoint) (time.Time, error) { + var expiry time.Time + err := walletdb.Update(w.db, func(tx walletdb.ReadWriteTx) error { + ns := tx.ReadWriteBucket(wtxmgrNamespaceKey) + var err error + expiry, err = w.TxStore.LockOutput(ns, id, op) + return err + }) + return expiry, err +} + +// ReleaseOutput unlocks an output, allowing it to be available for coin +// selection if it remains unspent. The ID should match the one used to +// originally lock the output. +func (w *Wallet) ReleaseOutput(id wtxmgr.LockID, op wire.OutPoint) error { + return walletdb.Update(w.db, func(tx walletdb.ReadWriteTx) error { + ns := tx.ReadWriteBucket(wtxmgrNamespaceKey) + return w.TxStore.UnlockOutput(ns, id, op) + }) +} + // resendUnminedTxs iterates through all transactions that spend from wallet // credits that are not known to have been mined into a block, and attempts // to send each to the chain server for relay.