From 5ecef0b787d0a2f5bddb9b07c42cb5d1abb07bab Mon Sep 17 00:00:00 2001 From: Christopher Schinnerl Date: Fri, 26 Jan 2018 15:06:53 -0500 Subject: [PATCH 1/2] Wallet starts defragging implicitly when sending coins once it reaches a certain threshold of unspent outputs --- modules/wallet/consts.go | 6 ++++ modules/wallet/defrag_test.go | 50 ++++++++++++++++++++++++++++ modules/wallet/transactionbuilder.go | 18 ++++++++-- 3 files changed, 72 insertions(+), 2 deletions(-) diff --git a/modules/wallet/consts.go b/modules/wallet/consts.go index 4bdd51fb91..0e187174b0 100644 --- a/modules/wallet/consts.go +++ b/modules/wallet/consts.go @@ -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 ( diff --git a/modules/wallet/defrag_test.go b/modules/wallet/defrag_test.go index 32e81c5bec..59161c5985 100644 --- a/modules/wallet/defrag_test.go +++ b/modules/wallet/defrag_test.go @@ -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) + } +} diff --git a/modules/wallet/transactionbuilder.go b/modules/wallet/transactionbuilder.go index c4e0d78cb6..6bfb9ced1c 100644 --- a/modules/wallet/transactionbuilder.go +++ b/modules/wallet/transactionbuilder.go @@ -149,7 +149,16 @@ func (tb *transactionBuilder) FundSiacoins(amount types.Currency) error { so.outputs = append(so.outputs, sco) } } - sort.Sort(sort.Reverse(so)) + + // If we have too man 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 + sort.Sort(so) + } else { + sort.Sort(sort.Reverse(so)) + } // Create and fund a parent transaction that will add the correct amount of // siacoins to the transaction. @@ -183,7 +192,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 } } From ff027784cd60ccd5cf82b71f197151c81970726d Mon Sep 17 00:00:00 2001 From: Christopher Schinnerl Date: Mon, 5 Feb 2018 10:16:20 -0500 Subject: [PATCH 2/2] skip larget outputs and fix comment --- modules/wallet/transactionbuilder.go | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/modules/wallet/transactionbuilder.go b/modules/wallet/transactionbuilder.go index 6bfb9ced1c..e382ec62d3 100644 --- a/modules/wallet/transactionbuilder.go +++ b/modules/wallet/transactionbuilder.go @@ -150,12 +150,16 @@ func (tb *transactionBuilder) FundSiacoins(amount types.Currency) error { } } - // If we have too man unspent transactions we might as well do some + // 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 + // 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)) }