github.com/fozzysec/SiaPrime@v0.0.0-20190612043147-66c8e8d11fe3/modules/wallet/money.go (about) 1 package wallet 2 3 import ( 4 "errors" 5 6 "SiaPrime/build" 7 "SiaPrime/modules" 8 "SiaPrime/types" 9 ) 10 11 // sortedOutputs is a struct containing a slice of siacoin outputs and their 12 // corresponding ids. sortedOutputs can be sorted using the sort package. 13 type sortedOutputs struct { 14 ids []types.SiacoinOutputID 15 outputs []types.SiacoinOutput 16 } 17 18 // DustThreshold returns the quantity per byte below which a Currency is 19 // considered to be Dust. 20 func (w *Wallet) DustThreshold() (types.Currency, error) { 21 if err := w.tg.Add(); err != nil { 22 return types.Currency{}, modules.ErrWalletShutdown 23 } 24 defer w.tg.Done() 25 26 minFee, _ := w.tpool.FeeEstimation() 27 return minFee.Mul64(3), nil 28 } 29 30 // ConfirmedBalance returns the balance of the wallet according to all of the 31 // confirmed transactions. 32 func (w *Wallet) ConfirmedBalance() (siacoinBalance types.Currency, siafundBalance types.Currency, siafundClaimBalance types.Currency, err error) { 33 if err := w.tg.Add(); err != nil { 34 return types.ZeroCurrency, types.ZeroCurrency, types.ZeroCurrency, modules.ErrWalletShutdown 35 } 36 defer w.tg.Done() 37 38 // dustThreshold has to be obtained separate from the lock 39 dustThreshold, err := w.DustThreshold() 40 if err != nil { 41 return types.ZeroCurrency, types.ZeroCurrency, types.ZeroCurrency, modules.ErrWalletShutdown 42 } 43 44 w.mu.Lock() 45 defer w.mu.Unlock() 46 47 // ensure durability of reported balance 48 if err = w.syncDB(); err != nil { 49 return 50 } 51 52 dbForEachSiacoinOutput(w.dbTx, func(_ types.SiacoinOutputID, sco types.SiacoinOutput) { 53 if sco.Value.Cmp(dustThreshold) > 0 { 54 siacoinBalance = siacoinBalance.Add(sco.Value) 55 } 56 }) 57 58 siafundPool, err := dbGetSiafundPool(w.dbTx) 59 if err != nil { 60 return 61 } 62 dbForEachSiafundOutput(w.dbTx, func(_ types.SiafundOutputID, sfo types.SiafundOutput) { 63 siafundBalance = siafundBalance.Add(sfo.Value) 64 if sfo.ClaimStart.Cmp(siafundPool) > 0 { 65 // Skip claims larger than the siafund pool. This should only 66 // occur if the siafund pool has not been initialized yet. 67 w.log.Debugf("skipping claim with start value %v because siafund pool is only %v", sfo.ClaimStart, siafundPool) 68 return 69 } 70 siafundClaimBalance = siafundClaimBalance.Add(siafundPool.Sub(sfo.ClaimStart).Mul(sfo.Value).Div(types.SiafundCount)) 71 }) 72 return 73 } 74 75 // UnconfirmedBalance returns the number of outgoing and incoming siacoins in 76 // the unconfirmed transaction set. Refund outputs are included in this 77 // reporting. 78 func (w *Wallet) UnconfirmedBalance() (outgoingSiacoins types.Currency, incomingSiacoins types.Currency, err error) { 79 if err := w.tg.Add(); err != nil { 80 return types.ZeroCurrency, types.ZeroCurrency, modules.ErrWalletShutdown 81 } 82 defer w.tg.Done() 83 84 // dustThreshold has to be obtained separate from the lock 85 dustThreshold, err := w.DustThreshold() 86 if err != nil { 87 return types.ZeroCurrency, types.ZeroCurrency, modules.ErrWalletShutdown 88 } 89 90 w.mu.Lock() 91 defer w.mu.Unlock() 92 93 for _, upt := range w.unconfirmedProcessedTransactions { 94 for _, input := range upt.Inputs { 95 if input.FundType == types.SpecifierSiacoinInput && input.WalletAddress { 96 outgoingSiacoins = outgoingSiacoins.Add(input.Value) 97 } 98 } 99 for _, output := range upt.Outputs { 100 if output.FundType == types.SpecifierSiacoinOutput && output.WalletAddress && output.Value.Cmp(dustThreshold) > 0 { 101 incomingSiacoins = incomingSiacoins.Add(output.Value) 102 } 103 } 104 } 105 return 106 } 107 108 // SendSiacoins creates a transaction sending 'amount' to 'dest'. The transaction 109 // is submitted to the transaction pool and is also returned. 110 func (w *Wallet) SendSiacoins(amount types.Currency, dest types.UnlockHash) (txns []types.Transaction, err error) { 111 if err := w.tg.Add(); err != nil { 112 err = modules.ErrWalletShutdown 113 return nil, err 114 } 115 defer w.tg.Done() 116 117 w.mu.RLock() 118 unlocked := w.unlocked 119 w.mu.RUnlock() 120 if !unlocked { 121 w.log.Println("Attempt to send coins has failed - wallet is locked") 122 return nil, modules.ErrLockedWallet 123 } 124 125 _, tpoolFee := w.tpool.FeeEstimation() 126 tpoolFee = tpoolFee.Mul64(750) // Estimated transaction size in bytes 127 output := types.SiacoinOutput{ 128 Value: amount, 129 UnlockHash: dest, 130 } 131 132 txnBuilder, err := w.StartTransaction() 133 if err != nil { 134 return nil, err 135 } 136 defer func() { 137 if err != nil { 138 txnBuilder.Drop() 139 } 140 }() 141 err = txnBuilder.FundSiacoinsForOutputs([]types.SiacoinOutput{output}, tpoolFee) 142 if err != nil { 143 w.log.Println("Attempt to send coins has failed - failed to fund transaction:", err) 144 return nil, build.ExtendErr("unable to fund transaction", err) 145 } 146 txnSet, err := txnBuilder.Sign(true) 147 if err != nil { 148 w.log.Println("Attempt to send coins has failed - failed to sign transaction:", err) 149 return nil, build.ExtendErr("unable to sign transaction", err) 150 } 151 if w.deps.Disrupt("SendSiacoinsInterrupted") { 152 return nil, errors.New("failed to accept transaction set (SendSiacoinsInterrupted)") 153 } 154 err = w.tpool.AcceptTransactionSet(txnSet) 155 if err != nil { 156 w.log.Println("Attempt to send coins has failed - transaction pool rejected transaction:", err) 157 return nil, build.ExtendErr("unable to get transaction accepted", err) 158 } 159 w.log.Println("Submitted a siacoin transfer transaction set for value", amount.HumanString(), "with fees", tpoolFee.HumanString(), "IDs:") 160 for _, txn := range txnSet { 161 w.log.Println("\t", txn.ID()) 162 } 163 return txnSet, nil 164 } 165 166 // SendSiacoinsMulti creates a transaction that includes the specified 167 // outputs. The transaction is submitted to the transaction pool and is also 168 // returned. 169 func (w *Wallet) SendSiacoinsMulti(outputs []types.SiacoinOutput) (txns []types.Transaction, err error) { 170 w.log.Println("Beginning call to SendSiacoinsMulti") 171 if err := w.tg.Add(); err != nil { 172 err = modules.ErrWalletShutdown 173 return nil, err 174 } 175 defer w.tg.Done() 176 w.mu.RLock() 177 unlocked := w.unlocked 178 w.mu.RUnlock() 179 if !unlocked { 180 w.log.Println("Attempt to send coins has failed - wallet is locked") 181 return nil, modules.ErrLockedWallet 182 } 183 184 txnBuilder, err := w.StartTransaction() 185 if err != nil { 186 return nil, err 187 } 188 defer func() { 189 if err != nil { 190 txnBuilder.Drop() 191 } 192 }() 193 194 // Add estimated transaction fee. 195 _, tpoolFee := w.tpool.FeeEstimation() 196 tpoolFee = tpoolFee.Mul64(2) // We don't want send-to-many transactions to fail. 197 tpoolFee = tpoolFee.Mul64(1000 + 60*uint64(len(outputs))) // Estimated transaction size in bytes 198 199 err = txnBuilder.FundSiacoinsForOutputs(outputs, tpoolFee) 200 if err != nil { 201 return nil, build.ExtendErr("unable to fund transaction", err) 202 } 203 204 txnSet, err := txnBuilder.Sign(true) 205 if err != nil { 206 w.log.Println("Attempt to send coins has failed - failed to sign transaction:", err) 207 return nil, build.ExtendErr("unable to sign transaction", err) 208 } 209 if w.deps.Disrupt("SendSiacoinsInterrupted") { 210 return nil, errors.New("failed to accept transaction set (SendSiacoinsInterrupted)") 211 } 212 w.log.Println("Attempting to broadcast a multi-send over the network") 213 err = w.tpool.AcceptTransactionSet(txnSet) 214 if err != nil { 215 w.log.Println("Attempt to send coins has failed - transaction pool rejected transaction:", err) 216 return nil, build.ExtendErr("unable to get transaction accepted", err) 217 } 218 219 // Log the success. 220 var outputList string 221 for _, output := range outputs { 222 outputList = outputList + "\n\tAddress: " + output.UnlockHash.String() + "\n\tValue: " + output.Value.HumanString() + "\n" 223 } 224 w.log.Printf("Successfully broadcast transaction with id %v, fee %v, and the following outputs: %v", txnSet[len(txnSet)-1].ID(), tpoolFee.HumanString(), outputList) 225 return txnSet, nil 226 } 227 228 // SendSiafunds creates a transaction sending 'amount' to 'dest'. The transaction 229 // is submitted to the transaction pool and is also returned. 230 func (w *Wallet) SendSiafunds(amount types.Currency, dest types.UnlockHash) (txns []types.Transaction, err error) { 231 if err := w.tg.Add(); err != nil { 232 err = modules.ErrWalletShutdown 233 return nil, err 234 } 235 defer w.tg.Done() 236 w.mu.RLock() 237 unlocked := w.unlocked 238 w.mu.RUnlock() 239 if !unlocked { 240 return nil, modules.ErrLockedWallet 241 } 242 243 _, tpoolFee := w.tpool.FeeEstimation() 244 tpoolFee = tpoolFee.Mul64(750) // Estimated transaction size in bytes 245 tpoolFee = tpoolFee.Mul64(5) // use large fee to ensure siafund transactions are selected by miners 246 output := types.SiafundOutput{ 247 Value: amount, 248 UnlockHash: dest, 249 } 250 251 txnBuilder, err := w.StartTransaction() 252 if err != nil { 253 return nil, err 254 } 255 defer func() { 256 if err != nil { 257 txnBuilder.Drop() 258 } 259 }() 260 err = txnBuilder.FundSiacoins(tpoolFee) 261 if err != nil { 262 return nil, err 263 } 264 err = txnBuilder.FundSiafunds(amount) 265 if err != nil { 266 return nil, err 267 } 268 txnBuilder.AddMinerFee(tpoolFee) 269 txnBuilder.AddSiafundOutput(output) 270 txnSet, err := txnBuilder.Sign(true) 271 if err != nil { 272 return nil, err 273 } 274 err = w.tpool.AcceptTransactionSet(txnSet) 275 if err != nil { 276 return nil, err 277 } 278 w.log.Println("Submitted a siafund transfer transaction set for value", amount.HumanString(), "with fees", tpoolFee.HumanString(), "IDs:") 279 for _, txn := range txnSet { 280 w.log.Println("\t", txn.ID()) 281 } 282 return txnSet, nil 283 } 284 285 // Len returns the number of elements in the sortedOutputs struct. 286 func (so sortedOutputs) Len() int { 287 if build.DEBUG && len(so.ids) != len(so.outputs) { 288 panic("sortedOutputs object is corrupt") 289 } 290 return len(so.ids) 291 } 292 293 // Less returns whether element 'i' is less than element 'j'. The currency 294 // value of each output is used for comparison. 295 func (so sortedOutputs) Less(i, j int) bool { 296 return so.outputs[i].Value.Cmp(so.outputs[j].Value) < 0 297 } 298 299 // Swap swaps two elements in the sortedOutputs set. 300 func (so sortedOutputs) Swap(i, j int) { 301 so.ids[i], so.ids[j] = so.ids[j], so.ids[i] 302 so.outputs[i], so.outputs[j] = so.outputs[j], so.outputs[i] 303 }