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  }