Skip to content
Snippets Groups Projects
Select Git revision
  • master default protected
  • add-host-rpc-debug
  • pj/debug-host-rpc
  • pj/update-log-lib
  • upnp
  • fix-no-workers-hardening
  • pj/download-debug
  • chris/refactor-contract-maintenance
  • fil/fix-windows-paths
  • pj/fix-prod-persistence
  • chris/registry-stats-debug
  • remove-packing
  • replace-host-wal
  • antfarm-tests
  • chris/import-skykey
  • sevey/accounting
  • chris/fix-subscription-race
  • dl-archive-debug
  • sevey/skyfile-batch
  • chris/debug-ndfs
  • v1.5.6
  • v1.5.6-rc1
  • v1.5.5
  • v1.5.5-rc2
  • v1.5.5-rc1
  • v1.5.4
  • v1.5.4-rc3
  • v1.5.4-rc2
  • v1.5.3
  • v1.5.2
  • v1.5.1
  • v1.5.1-rc2
  • v1.4.7-antfarm
  • v1.4.8-antfarm
  • v1.4.10-antfarm
  • v1.4.11-antfarm
  • v1.4.4-antfarm
  • v1.4.5-antfarm
  • v1.4.6-antfarm
  • v1.5.0
40 results

defrag.go

defrag.go 5.53 KiB
package wallet

import (
	"sort"

	"gitlab.com/NebulousLabs/Sia/crypto"
	"gitlab.com/NebulousLabs/Sia/types"
	"gitlab.com/NebulousLabs/errors"
)

var (
	errDefragNotNeeded = errors.New("defragging not needed, wallet is already sufficiently defragged")
)

// managedCreateDefragTransaction creates a transaction that spends multiple existing
// wallet outputs into a single new address.
func (w *Wallet) managedCreateDefragTransaction() ([]types.Transaction, error) {
	// dustThreshold and minFee have to be obtained separate from the lock
	dustThreshold, err := w.DustThreshold()
	if err != nil {
		return nil, err
	}
	minFee, _ := w.tpool.FeeEstimation()

	w.mu.Lock()
	defer w.mu.Unlock()

	consensusHeight, err := dbGetConsensusHeight(w.dbTx)
	if err != nil {
		return nil, err
	}

	// Collect a value-sorted set of siacoin outputs.
	var so sortedOutputs
	err = dbForEachSiacoinOutput(w.dbTx, func(scoid types.SiacoinOutputID, sco types.SiacoinOutput) {
		if w.checkOutput(w.dbTx, consensusHeight, scoid, sco, dustThreshold) == nil {
			so.ids = append(so.ids, scoid)
			so.outputs = append(so.outputs, sco)
		}
	})
	if err != nil {
		return nil, err
	}
	sort.Sort(sort.Reverse(so))

	// Only defrag if there are enough outputs to merit defragging.
	if len(so.ids) <= defragThreshold {
		return nil, errDefragNotNeeded
	}

	// Skip over the 'defragStartIndex' largest outputs, so that the user can
	// still reasonably use their wallet while the defrag is happening.
	var amount types.Currency
	var parentTxn types.Transaction
	var spentScoids []types.SiacoinOutputID
	for i := defragStartIndex; i < defragStartIndex+defragBatchSize; i++ {
		scoid := so.ids[i]
		sco := so.outputs[i]

		// Add a siacoin input for this output.
		outputUnlockConditions := w.keys[sco.UnlockHash].UnlockConditions
		sci := types.SiacoinInput{
			ParentID:         scoid,
			UnlockConditions: outputUnlockConditions,
		}
		parentTxn.SiacoinInputs = append(parentTxn.SiacoinInputs, sci)
		spentScoids = append(spentScoids, scoid)

		// Add the output to the total fund
		amount = amount.Add(sco.Value)
	}

	// Create and add the output that will be used to fund the defrag
	// transaction.
	parentUnlockConditions, err := w.nextPrimarySeedAddress(w.dbTx)
	if err != nil {
		return nil, err
	}
	exactOutput := types.SiacoinOutput{
		Value:      amount,
		UnlockHash: parentUnlockConditions.UnlockHash(),
	}
	parentTxn.SiacoinOutputs = append(parentTxn.SiacoinOutputs, exactOutput)

	// Sign all of the inputs to the parent transaction.
	for _, sci := range parentTxn.SiacoinInputs {
		addSignatures(&parentTxn, types.FullCoveredFields, sci.UnlockConditions, crypto.Hash(sci.ParentID), w.keys[sci.UnlockConditions.UnlockHash()], consensusHeight)
	}

	// Create the defrag transaction.
	refundAddr, err := w.nextPrimarySeedAddress(w.dbTx)
	if err != nil {
		return nil, err
	}

	// compute the transaction fee.
	sizeAvgOutput := uint64(250)
	fee := minFee.Mul64(sizeAvgOutput * defragBatchSize)

	txn := types.Transaction{
		SiacoinInputs: []types.SiacoinInput{{
			ParentID:         parentTxn.SiacoinOutputID(0),
			UnlockConditions: parentUnlockConditions,
		}},
		SiacoinOutputs: []types.SiacoinOutput{{
			Value:      amount.Sub(fee),
			UnlockHash: refundAddr.UnlockHash(),
		}},
		MinerFees: []types.Currency{fee},
	}
	addSignatures(&txn, types.FullCoveredFields, parentUnlockConditions, crypto.Hash(parentTxn.SiacoinOutputID(0)), w.keys[parentUnlockConditions.UnlockHash()], consensusHeight)

	// Mark all outputs that were spent as spent.
	for _, scoid := range spentScoids {
		if err = dbPutSpentOutput(w.dbTx, types.OutputID(scoid), consensusHeight); err != nil {
			return nil, err
		}
	}
	// Mark the parent output as spent. Must be done after the transaction is
	// finished because otherwise the txid and output id will change.
	if err = dbPutSpentOutput(w.dbTx, types.OutputID(parentTxn.SiacoinOutputID(0)), consensusHeight); err != nil {
		return nil, err
	}

	// Construct the final transaction set
	return []types.Transaction{parentTxn, txn}, nil
}

// threadedDefragWallet computes the sum of the 15 largest outputs in the wallet and
// sends that sum to itself, effectively defragmenting the wallet. This defrag
// operation is only performed if the wallet has greater than defragThreshold
// outputs.
func (w *Wallet) threadedDefragWallet() {
	// Don't defrag if it was disabled
	w.mu.RLock()
	disabled := w.defragDisabled
	w.mu.RUnlock()
	if disabled {
		return
	}

	err := w.tg.Add()
	if err != nil {
		return
	}
	defer w.tg.Done()

	// Check that a defrag makes sense.
	w.mu.RLock()
	unlocked := w.unlocked
	w.mu.RUnlock()
	if !unlocked {
		// Can't defrag if the wallet is locked.
		return
	}

	// Create the defrag transaction.
	txnSet, err := w.managedCreateDefragTransaction()
	defer func() {
		if err == nil {
			return
		}
		w.mu.Lock()
		defer w.mu.Unlock()
		for _, txn := range txnSet {
			for _, sci := range txn.SiacoinInputs {
				dbDeleteSpentOutput(w.dbTx, types.OutputID(sci.ParentID))
			}
		}
	}()
	if errors.Contains(err, errDefragNotNeeded) {
		// begin
		return
	} else if err != nil {
		w.log.Println("WARN: couldn't create defrag transaction:", err)
		return
	}

	if w.deps.Disrupt("DefragInterrupted") {
		err = errors.New("defrag was interrupted (DefragInterrupted)")
		return
	}
	// Submit the defrag to the transaction pool.
	err = w.tpool.AcceptTransactionSet(txnSet)
	if err != nil {
		w.log.Println("WARN: defrag transaction was rejected:", err)
		return
	}
	w.log.Println("Submitting a transaction set to defragment the wallet's outputs, IDs:")
	for _, txn := range txnSet {
		w.log.Println("Wallet defrag: \t", txn.ID())
	}
}