Skip to content

Commit

Permalink
Merge pull request #150 from SiaFoundation/chris/fix-locking
Browse files Browse the repository at this point in the history
Avoid locking when interacting with the database
  • Loading branch information
lukechampine authored Jan 15, 2025
2 parents 549d02f + fa9d759 commit dcc73ae
Show file tree
Hide file tree
Showing 3 changed files with 41 additions and 26 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
default: patch
---

# Improve locking in SingleAddressWallet by avoiding acquiring the mutex before a db transaction
10 changes: 4 additions & 6 deletions wallet/update.go
Original file line number Diff line number Diff line change
Expand Up @@ -290,9 +290,6 @@ func appliedEvents(cau chain.ApplyUpdate, walletAddress types.Address) (events [

// applyChainUpdate atomically applies a chain update
func (sw *SingleAddressWallet) applyChainUpdate(tx UpdateTx, address types.Address, cau chain.ApplyUpdate) error {
sw.mu.Lock()
defer sw.mu.Unlock()

// update current state elements
if err := tx.UpdateWalletSiacoinElementProofs(cau); err != nil {
return fmt.Errorf("failed to update state elements: %w", err)
Expand All @@ -319,15 +316,14 @@ func (sw *SingleAddressWallet) applyChainUpdate(tx UpdateTx, address types.Addre
if err := tx.WalletApplyIndex(cau.State.Index, createdUTXOs, spentUTXOs, appliedEvents(cau, address), cau.Block.Timestamp); err != nil {
return fmt.Errorf("failed to apply index: %w", err)
}
sw.mu.Lock()
sw.tip = cau.State.Index
sw.mu.Unlock()
return nil
}

// revertChainUpdate atomically reverts a chain update from a wallet
func (sw *SingleAddressWallet) revertChainUpdate(tx UpdateTx, revertedIndex types.ChainIndex, address types.Address, cru chain.RevertUpdate) error {
sw.mu.Lock()
defer sw.mu.Unlock()

var removedUTXOs, unspentUTXOs []types.SiacoinElement
cru.ForEachSiacoinElement(func(se types.SiacoinElement, created, spent bool) {
switch {
Expand Down Expand Up @@ -355,7 +351,9 @@ func (sw *SingleAddressWallet) revertChainUpdate(tx UpdateTx, revertedIndex type
if err := tx.UpdateWalletSiacoinElementProofs(cru); err != nil {
return fmt.Errorf("failed to update state elements: %w", err)
}
sw.mu.Lock()
sw.tip = revertedIndex
sw.mu.Unlock()
return nil
}

Expand Down
52 changes: 32 additions & 20 deletions wallet/wallet.go
Original file line number Diff line number Diff line change
Expand Up @@ -218,16 +218,11 @@ func (sw *SingleAddressWallet) SpendableOutputs() ([]types.SiacoinElement, error
return unspent, nil
}

func (sw *SingleAddressWallet) selectUTXOs(amount types.Currency, inputs int, useUnconfirmed bool) ([]types.SiacoinElement, types.Currency, error) {
func (sw *SingleAddressWallet) selectUTXOs(amount types.Currency, inputs int, useUnconfirmed bool, elements []types.SiacoinElement) ([]types.SiacoinElement, types.Currency, error) {
if amount.IsZero() {
return nil, types.ZeroCurrency, nil
}

elements, err := sw.store.UnspentSiacoinElements()
if err != nil {
return nil, types.ZeroCurrency, err
}

tpoolSpent := make(map[types.SiacoinOutputID]bool)
tpoolUtxos := make(map[types.SiacoinOutputID]types.SiacoinElement)
for _, txn := range sw.cm.PoolTransactions() {
Expand Down Expand Up @@ -353,10 +348,15 @@ func (sw *SingleAddressWallet) FundTransaction(txn *types.Transaction, amount ty
return nil, nil
}

elements, err := sw.store.UnspentSiacoinElements()
if err != nil {
return nil, err
}

sw.mu.Lock()
defer sw.mu.Unlock()

selected, inputSum, err := sw.selectUTXOs(amount, len(txn.SiacoinInputs), useUnconfirmed)
selected, inputSum, err := sw.selectUTXOs(amount, len(txn.SiacoinInputs), useUnconfirmed, elements)
if err != nil {
return nil, err
}
Expand Down Expand Up @@ -413,14 +413,20 @@ func (sw *SingleAddressWallet) SignTransaction(txn *types.Transaction, toSign []
//
// The returned index should be used as the basis for AddV2PoolTransactions.
func (sw *SingleAddressWallet) FundV2Transaction(txn *types.V2Transaction, amount types.Currency, useUnconfirmed bool) (types.ChainIndex, []int, error) {
sw.mu.Lock()
defer sw.mu.Unlock()

if amount.IsZero() {
return sw.tip, nil, nil
}

selected, inputSum, err := sw.selectUTXOs(amount, len(txn.SiacoinInputs), useUnconfirmed)
// fetch outputs from the store
elements, err := sw.store.UnspentSiacoinElements()
if err != nil {
return types.ChainIndex{}, nil, err
}

sw.mu.Lock()
defer sw.mu.Unlock()

selected, inputSum, err := sw.selectUTXOs(amount, len(txn.SiacoinInputs), useUnconfirmed, elements)
if err != nil {
return types.ChainIndex{}, nil, err
}
Expand Down Expand Up @@ -581,13 +587,7 @@ func (sw *SingleAddressWallet) UnconfirmedEvents() (annotated []Event, err error
return annotated, nil
}

func (sw *SingleAddressWallet) selectRedistributeUTXOs(bh uint64, outputs int, amount types.Currency) ([]types.SiacoinElement, int, error) {
// fetch outputs from the store
elements, err := sw.store.UnspentSiacoinElements()
if err != nil {
return nil, 0, err
}

func (sw *SingleAddressWallet) selectRedistributeUTXOs(bh uint64, outputs int, amount types.Currency, elements []types.SiacoinElement) ([]types.SiacoinElement, int, error) {
// fetch outputs currently in the pool
inPool := make(map[types.SiacoinOutputID]bool)
for _, txn := range sw.cm.PoolTransactions() {
Expand Down Expand Up @@ -631,10 +631,16 @@ func (sw *SingleAddressWallet) selectRedistributeUTXOs(bh uint64, outputs int, a
// outputs. It also returns a list of output IDs that need to be signed.
func (sw *SingleAddressWallet) Redistribute(outputs int, amount, feePerByte types.Currency) (txns []types.Transaction, toSign []types.Hash256, err error) {
state := sw.cm.TipState()

elements, err := sw.store.UnspentSiacoinElements()
if err != nil {
return nil, nil, err
}

sw.mu.Lock()
defer sw.mu.Unlock()

utxos, outputs, err := sw.selectRedistributeUTXOs(state.Index.Height, outputs, amount)
utxos, outputs, err := sw.selectRedistributeUTXOs(state.Index.Height, outputs, amount, elements)
if err != nil {
return nil, nil, err
}
Expand Down Expand Up @@ -724,10 +730,16 @@ func (sw *SingleAddressWallet) Redistribute(outputs int, amount, feePerByte type
// outputs. It also returns a list of output IDs that need to be signed.
func (sw *SingleAddressWallet) RedistributeV2(outputs int, amount, feePerByte types.Currency) (txns []types.V2Transaction, toSign [][]int, err error) {
state := sw.cm.TipState()

elements, err := sw.store.UnspentSiacoinElements()
if err != nil {
return nil, nil, err
}

sw.mu.Lock()
defer sw.mu.Unlock()

utxos, outputs, err := sw.selectRedistributeUTXOs(state.Index.Height, outputs, amount)
utxos, outputs, err := sw.selectRedistributeUTXOs(state.Index.Height, outputs, amount, elements)
if err != nil {
return nil, nil, err
}
Expand Down

0 comments on commit dcc73ae

Please sign in to comment.