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 }