github.com/ZuluSpl0it/Sia@v1.3.7/modules/wallet/money.go (about) 1 package wallet 2 3 import ( 4 "errors" 5 6 "github.com/NebulousLabs/Sia/build" 7 "github.com/NebulousLabs/Sia/modules" 8 "github.com/NebulousLabs/Sia/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.FundSiacoins(amount.Add(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 txnBuilder.AddMinerFee(tpoolFee) 147 txnBuilder.AddSiacoinOutput(output) 148 txnSet, err := txnBuilder.Sign(true) 149 if err != nil { 150 w.log.Println("Attempt to send coins has failed - failed to sign transaction:", err) 151 return nil, build.ExtendErr("unable to sign transaction", err) 152 } 153 if w.deps.Disrupt("SendSiacoinsInterrupted") { 154 return nil, errors.New("failed to accept transaction set (SendSiacoinsInterrupted)") 155 } 156 err = w.tpool.AcceptTransactionSet(txnSet) 157 if err != nil { 158 w.log.Println("Attempt to send coins has failed - transaction pool rejected transaction:", err) 159 return nil, build.ExtendErr("unable to get transaction accepted", err) 160 } 161 w.log.Println("Submitted a siacoin transfer transaction set for value", amount.HumanString(), "with fees", tpoolFee.HumanString(), "IDs:") 162 for _, txn := range txnSet { 163 w.log.Println("\t", txn.ID()) 164 } 165 return txnSet, nil 166 } 167 168 // SendSiacoinsMulti creates a transaction that includes the specified 169 // outputs. The transaction is submitted to the transaction pool and is also 170 // returned. 171 func (w *Wallet) SendSiacoinsMulti(outputs []types.SiacoinOutput) (txns []types.Transaction, err error) { 172 w.log.Println("Beginning call to SendSiacoinsMulti") 173 if err := w.tg.Add(); err != nil { 174 err = modules.ErrWalletShutdown 175 return nil, err 176 } 177 defer w.tg.Done() 178 w.mu.RLock() 179 unlocked := w.unlocked 180 w.mu.RUnlock() 181 if !unlocked { 182 w.log.Println("Attempt to send coins has failed - wallet is locked") 183 return nil, modules.ErrLockedWallet 184 } 185 186 txnBuilder, err := w.StartTransaction() 187 if err != nil { 188 return nil, err 189 } 190 defer func() { 191 if err != nil { 192 txnBuilder.Drop() 193 } 194 }() 195 196 // Add estimated transaction fee. 197 _, tpoolFee := w.tpool.FeeEstimation() 198 tpoolFee = tpoolFee.Mul64(2) // We don't want send-to-many transactions to fail. 199 tpoolFee = tpoolFee.Mul64(1000 + 60*uint64(len(outputs))) // Estimated transaction size in bytes 200 txnBuilder.AddMinerFee(tpoolFee) 201 202 // Calculate total cost to wallet. 203 // 204 // NOTE: we only want to call FundSiacoins once; that way, it will 205 // (ideally) fund the entire transaction with a single input, instead of 206 // many smaller ones. 207 totalCost := tpoolFee 208 for _, sco := range outputs { 209 totalCost = totalCost.Add(sco.Value) 210 } 211 err = txnBuilder.FundSiacoins(totalCost) 212 if err != nil { 213 return nil, build.ExtendErr("unable to fund transaction", err) 214 } 215 216 for _, sco := range outputs { 217 txnBuilder.AddSiacoinOutput(sco) 218 } 219 220 txnSet, err := txnBuilder.Sign(true) 221 if err != nil { 222 w.log.Println("Attempt to send coins has failed - failed to sign transaction:", err) 223 return nil, build.ExtendErr("unable to sign transaction", err) 224 } 225 if w.deps.Disrupt("SendSiacoinsInterrupted") { 226 return nil, errors.New("failed to accept transaction set (SendSiacoinsInterrupted)") 227 } 228 w.log.Println("Attempting to broadcast a multi-send over the network") 229 err = w.tpool.AcceptTransactionSet(txnSet) 230 if err != nil { 231 w.log.Println("Attempt to send coins has failed - transaction pool rejected transaction:", err) 232 return nil, build.ExtendErr("unable to get transaction accepted", err) 233 } 234 235 // Log the success. 236 var outputList string 237 for _, output := range outputs { 238 outputList = outputList + "\n\tAddress: " + output.UnlockHash.String() + "\n\tValue: " + output.Value.HumanString() + "\n" 239 } 240 w.log.Printf("Successfully broadcast transaction with id %v, fee %v, and the following outputs: %v", txnSet[len(txnSet)-1].ID(), tpoolFee.HumanString(), outputList) 241 return txnSet, nil 242 } 243 244 // SendSiafunds creates a transaction sending 'amount' to 'dest'. The transaction 245 // is submitted to the transaction pool and is also returned. 246 func (w *Wallet) SendSiafunds(amount types.Currency, dest types.UnlockHash) (txns []types.Transaction, err error) { 247 if err := w.tg.Add(); err != nil { 248 err = modules.ErrWalletShutdown 249 return nil, err 250 } 251 defer w.tg.Done() 252 w.mu.RLock() 253 unlocked := w.unlocked 254 w.mu.RUnlock() 255 if !unlocked { 256 return nil, modules.ErrLockedWallet 257 } 258 259 _, tpoolFee := w.tpool.FeeEstimation() 260 tpoolFee = tpoolFee.Mul64(750) // Estimated transaction size in bytes 261 tpoolFee = tpoolFee.Mul64(5) // use large fee to ensure siafund transactions are selected by miners 262 output := types.SiafundOutput{ 263 Value: amount, 264 UnlockHash: dest, 265 } 266 267 txnBuilder, err := w.StartTransaction() 268 if err != nil { 269 return nil, err 270 } 271 defer func() { 272 if err != nil { 273 txnBuilder.Drop() 274 } 275 }() 276 err = txnBuilder.FundSiacoins(tpoolFee) 277 if err != nil { 278 return nil, err 279 } 280 err = txnBuilder.FundSiafunds(amount) 281 if err != nil { 282 return nil, err 283 } 284 txnBuilder.AddMinerFee(tpoolFee) 285 txnBuilder.AddSiafundOutput(output) 286 txnSet, err := txnBuilder.Sign(true) 287 if err != nil { 288 return nil, err 289 } 290 err = w.tpool.AcceptTransactionSet(txnSet) 291 if err != nil { 292 return nil, err 293 } 294 w.log.Println("Submitted a siafund transfer transaction set for value", amount.HumanString(), "with fees", tpoolFee.HumanString(), "IDs:") 295 for _, txn := range txnSet { 296 w.log.Println("\t", txn.ID()) 297 } 298 return txnSet, nil 299 } 300 301 // Len returns the number of elements in the sortedOutputs struct. 302 func (so sortedOutputs) Len() int { 303 if build.DEBUG && len(so.ids) != len(so.outputs) { 304 panic("sortedOutputs object is corrupt") 305 } 306 return len(so.ids) 307 } 308 309 // Less returns whether element 'i' is less than element 'j'. The currency 310 // value of each output is used for comparison. 311 func (so sortedOutputs) Less(i, j int) bool { 312 return so.outputs[i].Value.Cmp(so.outputs[j].Value) < 0 313 } 314 315 // Swap swaps two elements in the sortedOutputs set. 316 func (so sortedOutputs) Swap(i, j int) { 317 so.ids[i], so.ids[j] = so.ids[j], so.ids[i] 318 so.outputs[i], so.outputs[j] = so.outputs[j], so.outputs[i] 319 }