github.com/Synthesix/Sia@v1.3.3-0.20180413141344-f863baeed3ca/modules/wallet/defrag.go (about)

     1  package wallet
     2  
     3  import (
     4  	"errors"
     5  	"sort"
     6  
     7  	"github.com/Synthesix/Sia/crypto"
     8  	"github.com/Synthesix/Sia/types"
     9  )
    10  
    11  var (
    12  	errDefragNotNeeded = errors.New("defragging not needed, wallet is already sufficiently defragged")
    13  )
    14  
    15  // managedCreateDefragTransaction creates a transaction that spends multiple existing
    16  // wallet outputs into a single new address.
    17  func (w *Wallet) managedCreateDefragTransaction() ([]types.Transaction, error) {
    18  	// dustThreshold and minFee have to be obtained separate from the lock
    19  	dustThreshold := w.DustThreshold()
    20  	minFee, _ := w.tpool.FeeEstimation()
    21  
    22  	w.mu.Lock()
    23  	defer w.mu.Unlock()
    24  
    25  	consensusHeight, err := dbGetConsensusHeight(w.dbTx)
    26  	if err != nil {
    27  		return nil, err
    28  	}
    29  
    30  	// Collect a value-sorted set of siacoin outputs.
    31  	var so sortedOutputs
    32  	err = dbForEachSiacoinOutput(w.dbTx, func(scoid types.SiacoinOutputID, sco types.SiacoinOutput) {
    33  		if w.checkOutput(w.dbTx, consensusHeight, scoid, sco, dustThreshold) == nil {
    34  			so.ids = append(so.ids, scoid)
    35  			so.outputs = append(so.outputs, sco)
    36  		}
    37  	})
    38  	if err != nil {
    39  		return nil, err
    40  	}
    41  	sort.Sort(sort.Reverse(so))
    42  
    43  	// Only defrag if there are enough outputs to merit defragging.
    44  	if len(so.ids) <= defragThreshold {
    45  		return nil, errDefragNotNeeded
    46  	}
    47  
    48  	// Skip over the 'defragStartIndex' largest outputs, so that the user can
    49  	// still reasonably use their wallet while the defrag is happening.
    50  	var amount types.Currency
    51  	var parentTxn types.Transaction
    52  	var spentScoids []types.SiacoinOutputID
    53  	for i := defragStartIndex; i < defragStartIndex+defragBatchSize; i++ {
    54  		scoid := so.ids[i]
    55  		sco := so.outputs[i]
    56  
    57  		// Add a siacoin input for this output.
    58  		outputUnlockConditions := w.keys[sco.UnlockHash].UnlockConditions
    59  		sci := types.SiacoinInput{
    60  			ParentID:         scoid,
    61  			UnlockConditions: outputUnlockConditions,
    62  		}
    63  		parentTxn.SiacoinInputs = append(parentTxn.SiacoinInputs, sci)
    64  		spentScoids = append(spentScoids, scoid)
    65  
    66  		// Add the output to the total fund
    67  		amount = amount.Add(sco.Value)
    68  	}
    69  
    70  	// Create and add the output that will be used to fund the defrag
    71  	// transaction.
    72  	parentUnlockConditions, err := w.nextPrimarySeedAddress(w.dbTx)
    73  	if err != nil {
    74  		return nil, err
    75  	}
    76  	exactOutput := types.SiacoinOutput{
    77  		Value:      amount,
    78  		UnlockHash: parentUnlockConditions.UnlockHash(),
    79  	}
    80  	parentTxn.SiacoinOutputs = append(parentTxn.SiacoinOutputs, exactOutput)
    81  
    82  	// Sign all of the inputs to the parent transaction.
    83  	for _, sci := range parentTxn.SiacoinInputs {
    84  		addSignatures(&parentTxn, types.FullCoveredFields, sci.UnlockConditions, crypto.Hash(sci.ParentID), w.keys[sci.UnlockConditions.UnlockHash()])
    85  	}
    86  
    87  	// Create the defrag transaction.
    88  	refundAddr, err := w.nextPrimarySeedAddress(w.dbTx)
    89  	if err != nil {
    90  		return nil, err
    91  	}
    92  
    93  	// compute the transaction fee.
    94  	sizeAvgOutput := uint64(250)
    95  	fee := minFee.Mul64(sizeAvgOutput * defragBatchSize)
    96  
    97  	txn := types.Transaction{
    98  		SiacoinInputs: []types.SiacoinInput{{
    99  			ParentID:         parentTxn.SiacoinOutputID(0),
   100  			UnlockConditions: parentUnlockConditions,
   101  		}},
   102  		SiacoinOutputs: []types.SiacoinOutput{{
   103  			Value:      amount.Sub(fee),
   104  			UnlockHash: refundAddr.UnlockHash(),
   105  		}},
   106  		MinerFees: []types.Currency{fee},
   107  	}
   108  	addSignatures(&txn, types.FullCoveredFields, parentUnlockConditions, crypto.Hash(parentTxn.SiacoinOutputID(0)), w.keys[parentUnlockConditions.UnlockHash()])
   109  
   110  	// Mark all outputs that were spent as spent.
   111  	for _, scoid := range spentScoids {
   112  		if err = dbPutSpentOutput(w.dbTx, types.OutputID(scoid), consensusHeight); err != nil {
   113  			return nil, err
   114  		}
   115  	}
   116  	// Mark the parent output as spent. Must be done after the transaction is
   117  	// finished because otherwise the txid and output id will change.
   118  	if err = dbPutSpentOutput(w.dbTx, types.OutputID(parentTxn.SiacoinOutputID(0)), consensusHeight); err != nil {
   119  		return nil, err
   120  	}
   121  
   122  	// Construct the final transaction set
   123  	return []types.Transaction{parentTxn, txn}, nil
   124  }
   125  
   126  // threadedDefragWallet computes the sum of the 15 largest outputs in the wallet and
   127  // sends that sum to itself, effectively defragmenting the wallet. This defrag
   128  // operation is only performed if the wallet has greater than defragThreshold
   129  // outputs.
   130  func (w *Wallet) threadedDefragWallet() {
   131  	// Don't defrag if it was disabled
   132  	if w.defragDisabled {
   133  		return
   134  	}
   135  
   136  	err := w.tg.Add()
   137  	if err != nil {
   138  		return
   139  	}
   140  	defer w.tg.Done()
   141  
   142  	// Check that a defrag makes sense.
   143  	w.mu.RLock()
   144  	unlocked := w.unlocked
   145  	w.mu.RUnlock()
   146  	if !unlocked {
   147  		// Can't defrag if the wallet is locked.
   148  		return
   149  	}
   150  
   151  	// Create the defrag transaction.
   152  	txnSet, err := w.managedCreateDefragTransaction()
   153  	defer func() {
   154  		if err == nil {
   155  			return
   156  		}
   157  		w.mu.Lock()
   158  		defer w.mu.Unlock()
   159  		for _, txn := range txnSet {
   160  			for _, sci := range txn.SiacoinInputs {
   161  				dbDeleteSpentOutput(w.dbTx, types.OutputID(sci.ParentID))
   162  			}
   163  		}
   164  	}()
   165  	if err == errDefragNotNeeded {
   166  		// begin
   167  		return
   168  	} else if err != nil {
   169  		w.log.Println("WARN: couldn't create defrag transaction:", err)
   170  		return
   171  	}
   172  
   173  	if w.deps.Disrupt("DefragInterrupted") {
   174  		err = errors.New("defrag was interrupted (DefragInterrupted)")
   175  		return
   176  	}
   177  	// Submit the defrag to the transaction pool.
   178  	err = w.tpool.AcceptTransactionSet(txnSet)
   179  	if err != nil {
   180  		w.log.Println("WARN: defrag transaction was rejected:", err)
   181  		return
   182  	}
   183  	w.log.Println("Submitting a transaction set to defragment the wallet's outputs, IDs:")
   184  	for _, txn := range txnSet {
   185  		w.log.Println("Wallet defrag: \t", txn.ID())
   186  	}
   187  }