Skip to content
This repository has been archived by the owner on Nov 2, 2018. It is now read-only.

Wallet implicit defrag #2692

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions modules/wallet/consts.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,12 @@ const (
// defragThreshold is the number of outputs a wallet is allowed before it is
// defragmented.
defragThreshold = 50

// naturalDefragThreshold is the number of outputs a wallet is allowed
// before it starts defragging using its normal spending behavior. This
// threshold is a bit lower than the defragThreshold to avoid starting the
// thread.
naturalDefragThreshold = defragThreshold - 10
)

var (
Expand Down
50 changes: 50 additions & 0 deletions modules/wallet/defrag_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -277,3 +277,53 @@ func TestDefragInterrupted(t *testing.T) {
}

}

// TestDefragWalletSendCoins mines many blocks and checks that the wallet's outputs are
// consolidated after spending some coins.
func TestDefragWalletSendCoins(t *testing.T) {
if testing.Short() {
t.SkipNow()
}
t.Parallel()
wt, err := createWalletTester(t.Name(), &ProductionDependencies{})
if err != nil {
t.Fatal(err)
}
defer wt.closeWt()

// mine defragThreshold blocks, resulting in defragThreshold outputs
for i := 0; i < naturalDefragThreshold; i++ {
_, err := wt.miner.AddBlock()
if err != nil {
t.Fatal(err)
}
}

// send coins to trigger a defrag
uc, err := wt.wallet.NextAddress()
if err != nil {
t.Fatal(err)
}
_, err = wt.wallet.SendSiacoins(types.SiacoinPrecision, uc.UnlockHash())
if err != nil {
t.Fatal(err)
}

// allow some time for the defrag transaction to occur, then mine another block
time.Sleep(time.Second * 5)

_, err = wt.miner.AddBlock()
if err != nil {
t.Fatal(err)
}

// defrag should keep the outputs below the threshold
wt.wallet.mu.Lock()
// force a sync because bucket stats may not be reliable until commit
wt.wallet.syncDB()
siacoinOutputs := wt.wallet.dbTx.Bucket(bucketSiacoinOutputs).Stats().KeyN
wt.wallet.mu.Unlock()
if siacoinOutputs > defragThreshold {
t.Fatalf("defrag should result in fewer than defragThreshold outputs, got %v wanted %v\n", siacoinOutputs, defragThreshold)
}
}
22 changes: 20 additions & 2 deletions modules/wallet/transactionbuilder.go
Original file line number Diff line number Diff line change
Expand Up @@ -149,7 +149,20 @@ func (tb *transactionBuilder) FundSiacoins(amount types.Currency) error {
so.outputs = append(so.outputs, sco)
}
}
sort.Sort(sort.Reverse(so))

// If we have too many unspent transactions we might as well do some
// defragging since we create a setup transaction anyway
defrag := tb.wallet.defragDisabled && len(so.ids) >= naturalDefragThreshold
if defrag {
// If we defrag we start with the smallest output and skip the
// defragStartIndex last ones to make sure the wallet still has a
// minimum amount of large outputs to spend.
sort.Sort(so)
so.ids = so.ids[:len(so.ids)-defragStartIndex]
so.outputs = so.outputs[:len(so.outputs)-defragStartIndex]
} else {
sort.Sort(sort.Reverse(so))
}

// Create and fund a parent transaction that will add the correct amount of
// siacoins to the transaction.
Expand Down Expand Up @@ -183,7 +196,12 @@ func (tb *transactionBuilder) FundSiacoins(amount types.Currency) error {
// Add the output to the total fund
fund = fund.Add(sco.Value)
potentialFund = potentialFund.Add(sco.Value)
if fund.Cmp(amount) >= 0 {
if defrag && len(spentScoids) >= defragBatchSize {
// If we defrag we don't stop once we have gathered enough money
// but if we reach the batch size.
break
} else if !defrag && fund.Cmp(amount) >= 0 {
// If we don't defrag we stop once we have gathered enough money.
break
}
}
Expand Down