github.com/fozzysec/SiaPrime@v0.0.0-20190612043147-66c8e8d11fe3/modules/wallet/transactionbuilder.go (about)

     1  package wallet
     2  
     3  import (
     4  	"bytes"
     5  	"errors"
     6  	"sort"
     7  
     8  	"SiaPrime/crypto"
     9  	"SiaPrime/encoding"
    10  	"SiaPrime/modules"
    11  	"SiaPrime/types"
    12  
    13  	"gitlab.com/NebulousLabs/bolt"
    14  )
    15  
    16  var (
    17  	// errBuilderAlreadySigned indicates that the transaction builder has
    18  	// already added at least one successful signature to the transaction,
    19  	// meaning that future calls to Sign will result in an invalid transaction.
    20  	errBuilderAlreadySigned = errors.New("sign has already been called on this transaction builder, multiple calls can cause issues")
    21  
    22  	// errDustOutput indicates an output is not spendable because it is dust.
    23  	errDustOutput = errors.New("output is too small")
    24  
    25  	// errOutputTimelock indicates an output's timelock is still active.
    26  	errOutputTimelock = errors.New("wallet consensus set height is lower than the output timelock")
    27  
    28  	// errSpendHeightTooHigh indicates an output's spend height is greater than
    29  	// the allowed height.
    30  	errSpendHeightTooHigh = errors.New("output spend height exceeds the allowed height")
    31  )
    32  
    33  // transactionBuilder allows transactions to be manually constructed, including
    34  // the ability to fund transactions with siacoins and siafunds from the wallet.
    35  type transactionBuilder struct {
    36  	// 'signed' indicates that at least one transaction signature has been
    37  	// added to the wallet, meaning that future calls to 'Sign' will fail.
    38  	parents     []types.Transaction
    39  	signed      bool
    40  	transaction types.Transaction
    41  
    42  	newParents            []int
    43  	siacoinInputs         []int
    44  	siafundInputs         []int
    45  	transactionSignatures []int
    46  
    47  	wallet *Wallet
    48  }
    49  
    50  // addSignatures will sign a transaction using a spendable key, with support
    51  // for multisig spendable keys. Because of the restricted input, the function
    52  // is compatible with both siacoin inputs and siafund inputs.
    53  func addSignatures(txn *types.Transaction, cf types.CoveredFields, uc types.UnlockConditions, parentID crypto.Hash, spendKey spendableKey, height types.BlockHeight) (newSigIndices []int) {
    54  	// Try to find the matching secret key for each public key - some public
    55  	// keys may not have a match. Some secret keys may be used multiple times,
    56  	// which is why public keys are used as the outer loop.
    57  	totalSignatures := uint64(0)
    58  	for i, siaPubKey := range uc.PublicKeys {
    59  		// Search for the matching secret key to the public key.
    60  		for j := range spendKey.SecretKeys {
    61  			pubKey := spendKey.SecretKeys[j].PublicKey()
    62  			if !bytes.Equal(siaPubKey.Key, pubKey[:]) {
    63  				continue
    64  			}
    65  
    66  			// Found the right secret key, add a signature.
    67  			sig := types.TransactionSignature{
    68  				ParentID:       parentID,
    69  				CoveredFields:  cf,
    70  				PublicKeyIndex: uint64(i),
    71  			}
    72  			newSigIndices = append(newSigIndices, len(txn.TransactionSignatures))
    73  			txn.TransactionSignatures = append(txn.TransactionSignatures, sig)
    74  			sigIndex := len(txn.TransactionSignatures) - 1
    75  			sigHash := txn.SigHash(sigIndex, height)
    76  			encodedSig := crypto.SignHash(sigHash, spendKey.SecretKeys[j])
    77  			txn.TransactionSignatures[sigIndex].Signature = encodedSig[:]
    78  
    79  			// Count that the signature has been added, and break out of the
    80  			// secret key loop.
    81  			totalSignatures++
    82  			break
    83  		}
    84  
    85  		// If there are enough signatures to satisfy the unlock conditions,
    86  		// break out of the outer loop.
    87  		if totalSignatures == uc.SignaturesRequired {
    88  			break
    89  		}
    90  	}
    91  	return newSigIndices
    92  }
    93  
    94  // checkOutput is a helper function used to determine if an output is usable.
    95  func (w *Wallet) checkOutput(tx *bolt.Tx, currentHeight types.BlockHeight, id types.SiacoinOutputID, output types.SiacoinOutput, dustThreshold types.Currency) error {
    96  	// Check that an output is not dust
    97  	if output.Value.Cmp(dustThreshold) < 0 {
    98  		return errDustOutput
    99  	}
   100  	// Check that this output has not recently been spent by the wallet.
   101  	spendHeight, err := dbGetSpentOutput(tx, types.OutputID(id))
   102  	if err == nil {
   103  		if spendHeight+RespendTimeout > currentHeight {
   104  			return errSpendHeightTooHigh
   105  		}
   106  	}
   107  	outputUnlockConditions := w.keys[output.UnlockHash].UnlockConditions
   108  	if currentHeight < outputUnlockConditions.Timelock {
   109  		return errOutputTimelock
   110  	}
   111  
   112  	return nil
   113  }
   114  
   115  // FundSiacoinsForOutputs will add enough inputs to cover the outputs to be
   116  // sent in the transaction. In contrast to FundSiacoins, FundSiacoinsForOutputs
   117  // does not aggregate inputs into one output equaling 'amount' - with a refund,
   118  // potentially - for later use by an output or other transaction fee. Rather,
   119  // it aggregates enough inputs to cover the outputs, adds the inputs and outputs
   120  // to the transaction, and also generates a refund output if necessary. A miner
   121  // fee of 0 or greater is also taken into account in the input aggregation and
   122  // added to the transaction if necessary.
   123  func (tb *transactionBuilder) FundSiacoinsForOutputs(outputs []types.SiacoinOutput, fee types.Currency) error {
   124  	// dustThreshold has to be obtained separate from the lock
   125  	dustThreshold, err := tb.wallet.DustThreshold()
   126  	if err != nil {
   127  		return err
   128  	}
   129  
   130  	tb.wallet.mu.Lock()
   131  	defer tb.wallet.mu.Unlock()
   132  
   133  	consensusHeight, err := dbGetConsensusHeight(tb.wallet.dbTx)
   134  	if err != nil {
   135  		return err
   136  	}
   137  
   138  	// Calculate the total amount we need to send
   139  	var amount types.Currency
   140  	for i := range outputs {
   141  		output := outputs[i]
   142  		amount = amount.Add(output.Value)
   143  	}
   144  
   145  	// Add a miner fee if the passed fee was greater than 0. The fee also
   146  	// needs to be added to the input amount we need to aggregate.
   147  	if fee.Cmp64(0) > 0 {
   148  		tb.transaction.MinerFees = append(tb.transaction.MinerFees, fee)
   149  		amount = amount.Add(fee)
   150  	}
   151  
   152  	// Collect a value-sorted set of siacoin outputs.
   153  	var so sortedOutputs
   154  	err = dbForEachSiacoinOutput(tb.wallet.dbTx, func(scoid types.SiacoinOutputID, sco types.SiacoinOutput) {
   155  		so.ids = append(so.ids, scoid)
   156  		so.outputs = append(so.outputs, sco)
   157  	})
   158  	if err != nil {
   159  		return err
   160  	}
   161  	// Add all of the unconfirmed outputs as well.
   162  	for _, upt := range tb.wallet.unconfirmedProcessedTransactions {
   163  		for i, sco := range upt.Transaction.SiacoinOutputs {
   164  			// Determine if the output belongs to the wallet.
   165  			_, exists := tb.wallet.keys[sco.UnlockHash]
   166  			if !exists {
   167  				continue
   168  			}
   169  			so.ids = append(so.ids, upt.Transaction.SiacoinOutputID(uint64(i)))
   170  			so.outputs = append(so.outputs, sco)
   171  		}
   172  	}
   173  	sort.Sort(sort.Reverse(so))
   174  
   175  	var fund types.Currency
   176  	// potentialFund tracks the balance of the wallet including outputs that
   177  	// have been spent in other unconfirmed transactions recently. This is to
   178  	// provide the user with a more useful error message in the event that they
   179  	// are overspending.
   180  	var potentialFund types.Currency
   181  	var spentScoids []types.SiacoinOutputID
   182  	for i := range so.ids {
   183  		scoid := so.ids[i]
   184  		sco := so.outputs[i]
   185  		// Check that the output can be spent.
   186  		if err := tb.wallet.checkOutput(tb.wallet.dbTx, consensusHeight, scoid, sco, dustThreshold); err != nil {
   187  			if err == errSpendHeightTooHigh {
   188  				potentialFund = potentialFund.Add(sco.Value)
   189  			}
   190  			continue
   191  		}
   192  
   193  		// Add a siacoin input for this output.
   194  		sci := types.SiacoinInput{
   195  			ParentID:         scoid,
   196  			UnlockConditions: tb.wallet.keys[sco.UnlockHash].UnlockConditions,
   197  		}
   198  		tb.siacoinInputs = append(tb.siacoinInputs, len(tb.transaction.SiacoinInputs))
   199  		tb.transaction.SiacoinInputs = append(tb.transaction.SiacoinInputs, sci)
   200  		spentScoids = append(spentScoids, scoid)
   201  
   202  		// Add the output to the total fund
   203  		fund = fund.Add(sco.Value)
   204  		potentialFund = potentialFund.Add(sco.Value)
   205  		if fund.Cmp(amount) >= 0 {
   206  			break
   207  		}
   208  	}
   209  	if potentialFund.Cmp(amount) >= 0 && fund.Cmp(amount) < 0 {
   210  		return modules.ErrIncompleteTransactions
   211  	}
   212  	if fund.Cmp(amount) < 0 {
   213  		return modules.ErrLowBalance
   214  	}
   215  
   216  	// Add the outputs to the transaction
   217  	for i := range outputs {
   218  		output := outputs[i]
   219  		tb.transaction.SiacoinOutputs = append(tb.transaction.SiacoinOutputs, output)
   220  	}
   221  
   222  	// Create a refund output if needed.
   223  	if !amount.Equals(fund) {
   224  		refundUnlockConditions, err := tb.wallet.nextPrimarySeedAddress(tb.wallet.dbTx)
   225  		if err != nil {
   226  			return err
   227  		}
   228  		refundOutput := types.SiacoinOutput{
   229  			Value:      fund.Sub(amount),
   230  			UnlockHash: refundUnlockConditions.UnlockHash(),
   231  		}
   232  		tb.transaction.SiacoinOutputs = append(tb.transaction.SiacoinOutputs, refundOutput)
   233  	}
   234  
   235  	// Mark all outputs that were spent as spent.
   236  	for _, scoid := range spentScoids {
   237  		err = dbPutSpentOutput(tb.wallet.dbTx, types.OutputID(scoid), consensusHeight)
   238  		if err != nil {
   239  			return err
   240  		}
   241  	}
   242  	return nil
   243  }
   244  
   245  // FundSiacoins will add a siacoin input of exactly 'amount' to the
   246  // transaction. A parent transaction may be needed to achieve an input with the
   247  // correct value. The siacoin input will not be signed until 'Sign' is called
   248  // on the transaction builder.
   249  func (tb *transactionBuilder) FundSiacoins(amount types.Currency) error {
   250  	// dustThreshold has to be obtained separate from the lock
   251  	dustThreshold, err := tb.wallet.DustThreshold()
   252  	if err != nil {
   253  		return err
   254  	}
   255  
   256  	tb.wallet.mu.Lock()
   257  	defer tb.wallet.mu.Unlock()
   258  
   259  	consensusHeight, err := dbGetConsensusHeight(tb.wallet.dbTx)
   260  	if err != nil {
   261  		return err
   262  	}
   263  
   264  	// Collect a value-sorted set of siacoin outputs.
   265  	var so sortedOutputs
   266  	err = dbForEachSiacoinOutput(tb.wallet.dbTx, func(scoid types.SiacoinOutputID, sco types.SiacoinOutput) {
   267  		so.ids = append(so.ids, scoid)
   268  		so.outputs = append(so.outputs, sco)
   269  	})
   270  	if err != nil {
   271  		return err
   272  	}
   273  	// Add all of the unconfirmed outputs as well.
   274  	for _, upt := range tb.wallet.unconfirmedProcessedTransactions {
   275  		for i, sco := range upt.Transaction.SiacoinOutputs {
   276  			// Determine if the output belongs to the wallet.
   277  			_, exists := tb.wallet.keys[sco.UnlockHash]
   278  			if !exists {
   279  				continue
   280  			}
   281  			so.ids = append(so.ids, upt.Transaction.SiacoinOutputID(uint64(i)))
   282  			so.outputs = append(so.outputs, sco)
   283  		}
   284  	}
   285  	sort.Sort(sort.Reverse(so))
   286  
   287  	// Create and fund a parent transaction that will add the correct amount of
   288  	// siacoins to the transaction.
   289  	var fund types.Currency
   290  	// potentialFund tracks the balance of the wallet including outputs that
   291  	// have been spent in other unconfirmed transactions recently. This is to
   292  	// provide the user with a more useful error message in the event that they
   293  	// are overspending.
   294  	var potentialFund types.Currency
   295  	parentTxn := types.Transaction{}
   296  	var spentScoids []types.SiacoinOutputID
   297  	for i := range so.ids {
   298  		scoid := so.ids[i]
   299  		sco := so.outputs[i]
   300  		// Check that the output can be spent.
   301  		if err := tb.wallet.checkOutput(tb.wallet.dbTx, consensusHeight, scoid, sco, dustThreshold); err != nil {
   302  			if err == errSpendHeightTooHigh {
   303  				potentialFund = potentialFund.Add(sco.Value)
   304  			}
   305  			continue
   306  		}
   307  
   308  		// Add a siacoin input for this output.
   309  		sci := types.SiacoinInput{
   310  			ParentID:         scoid,
   311  			UnlockConditions: tb.wallet.keys[sco.UnlockHash].UnlockConditions,
   312  		}
   313  		parentTxn.SiacoinInputs = append(parentTxn.SiacoinInputs, sci)
   314  		spentScoids = append(spentScoids, scoid)
   315  
   316  		// Add the output to the total fund
   317  		fund = fund.Add(sco.Value)
   318  		potentialFund = potentialFund.Add(sco.Value)
   319  		if fund.Cmp(amount) >= 0 {
   320  			break
   321  		}
   322  	}
   323  	if potentialFund.Cmp(amount) >= 0 && fund.Cmp(amount) < 0 {
   324  		return modules.ErrIncompleteTransactions
   325  	}
   326  	if fund.Cmp(amount) < 0 {
   327  		return modules.ErrLowBalance
   328  	}
   329  
   330  	// Create and add the output that will be used to fund the standard
   331  	// transaction.
   332  	parentUnlockConditions, err := tb.wallet.nextPrimarySeedAddress(tb.wallet.dbTx)
   333  	if err != nil {
   334  		return err
   335  	}
   336  
   337  	exactOutput := types.SiacoinOutput{
   338  		Value:      amount,
   339  		UnlockHash: parentUnlockConditions.UnlockHash(),
   340  	}
   341  	parentTxn.SiacoinOutputs = append(parentTxn.SiacoinOutputs, exactOutput)
   342  
   343  	// Create a refund output if needed.
   344  	if !amount.Equals(fund) {
   345  		refundUnlockConditions, err := tb.wallet.nextPrimarySeedAddress(tb.wallet.dbTx)
   346  		if err != nil {
   347  			return err
   348  		}
   349  		refundOutput := types.SiacoinOutput{
   350  			Value:      fund.Sub(amount),
   351  			UnlockHash: refundUnlockConditions.UnlockHash(),
   352  		}
   353  		parentTxn.SiacoinOutputs = append(parentTxn.SiacoinOutputs, refundOutput)
   354  	}
   355  
   356  	// Sign all of the inputs to the parent transaction.
   357  	for _, sci := range parentTxn.SiacoinInputs {
   358  		addSignatures(&parentTxn, types.FullCoveredFields, sci.UnlockConditions, crypto.Hash(sci.ParentID), tb.wallet.keys[sci.UnlockConditions.UnlockHash()], consensusHeight)
   359  	}
   360  	// Mark the parent output as spent. Must be done after the transaction is
   361  	// finished because otherwise the txid and output id will change.
   362  	err = dbPutSpentOutput(tb.wallet.dbTx, types.OutputID(parentTxn.SiacoinOutputID(0)), consensusHeight)
   363  	if err != nil {
   364  		return err
   365  	}
   366  
   367  	// Add the exact output.
   368  	newInput := types.SiacoinInput{
   369  		ParentID:         parentTxn.SiacoinOutputID(0),
   370  		UnlockConditions: parentUnlockConditions,
   371  	}
   372  	tb.newParents = append(tb.newParents, len(tb.parents))
   373  	tb.parents = append(tb.parents, parentTxn)
   374  	tb.siacoinInputs = append(tb.siacoinInputs, len(tb.transaction.SiacoinInputs))
   375  	tb.transaction.SiacoinInputs = append(tb.transaction.SiacoinInputs, newInput)
   376  
   377  	// Mark all outputs that were spent as spent.
   378  	for _, scoid := range spentScoids {
   379  		err = dbPutSpentOutput(tb.wallet.dbTx, types.OutputID(scoid), consensusHeight)
   380  		if err != nil {
   381  			return err
   382  		}
   383  	}
   384  	return nil
   385  }
   386  
   387  // FundSiafunds will add a siafund input of exactly 'amount' to the
   388  // transaction. A parent transaction may be needed to achieve an input with the
   389  // correct value. The siafund input will not be signed until 'Sign' is called
   390  // on the transaction builder.
   391  func (tb *transactionBuilder) FundSiafunds(amount types.Currency) error {
   392  	tb.wallet.mu.Lock()
   393  	defer tb.wallet.mu.Unlock()
   394  
   395  	consensusHeight, err := dbGetConsensusHeight(tb.wallet.dbTx)
   396  	if err != nil {
   397  		return err
   398  	}
   399  
   400  	// Create and fund a parent transaction that will add the correct amount of
   401  	// siafunds to the transaction.
   402  	var fund types.Currency
   403  	var potentialFund types.Currency
   404  	parentTxn := types.Transaction{}
   405  	var spentSfoids []types.SiafundOutputID
   406  	c := tb.wallet.dbTx.Bucket(bucketSiafundOutputs).Cursor()
   407  	for idBytes, sfoBytes := c.First(); idBytes != nil; idBytes, sfoBytes = c.Next() {
   408  		var sfoid types.SiafundOutputID
   409  		var sfo types.SiafundOutput
   410  		if err := encoding.Unmarshal(idBytes, &sfoid); err != nil {
   411  			return err
   412  		} else if err := encoding.Unmarshal(sfoBytes, &sfo); err != nil {
   413  			return err
   414  		}
   415  
   416  		// Check that this output has not recently been spent by the wallet.
   417  		spendHeight, err := dbGetSpentOutput(tb.wallet.dbTx, types.OutputID(sfoid))
   418  		if err != nil {
   419  			// mimic map behavior: no entry means zero value
   420  			spendHeight = 0
   421  		}
   422  		// Prevent an underflow error.
   423  		allowedHeight := consensusHeight - RespendTimeout
   424  		if consensusHeight < RespendTimeout {
   425  			allowedHeight = 0
   426  		}
   427  		if spendHeight > allowedHeight {
   428  			potentialFund = potentialFund.Add(sfo.Value)
   429  			continue
   430  		}
   431  		outputUnlockConditions := tb.wallet.keys[sfo.UnlockHash].UnlockConditions
   432  		if consensusHeight < outputUnlockConditions.Timelock {
   433  			continue
   434  		}
   435  
   436  		// Add a siafund input for this output.
   437  		parentClaimUnlockConditions, err := tb.wallet.nextPrimarySeedAddress(tb.wallet.dbTx)
   438  		if err != nil {
   439  			return err
   440  		}
   441  		sfi := types.SiafundInput{
   442  			ParentID:         sfoid,
   443  			UnlockConditions: outputUnlockConditions,
   444  			ClaimUnlockHash:  parentClaimUnlockConditions.UnlockHash(),
   445  		}
   446  		parentTxn.SiafundInputs = append(parentTxn.SiafundInputs, sfi)
   447  		spentSfoids = append(spentSfoids, sfoid)
   448  
   449  		// Add the output to the total fund
   450  		fund = fund.Add(sfo.Value)
   451  		potentialFund = potentialFund.Add(sfo.Value)
   452  		if fund.Cmp(amount) >= 0 {
   453  			break
   454  		}
   455  	}
   456  	if potentialFund.Cmp(amount) >= 0 && fund.Cmp(amount) < 0 {
   457  		return modules.ErrIncompleteTransactions
   458  	}
   459  	if fund.Cmp(amount) < 0 {
   460  		return modules.ErrLowBalance
   461  	}
   462  
   463  	// Create and add the output that will be used to fund the standard
   464  	// transaction.
   465  	parentUnlockConditions, err := tb.wallet.nextPrimarySeedAddress(tb.wallet.dbTx)
   466  	if err != nil {
   467  		return err
   468  	}
   469  	exactOutput := types.SiafundOutput{
   470  		Value:      amount,
   471  		UnlockHash: parentUnlockConditions.UnlockHash(),
   472  	}
   473  	parentTxn.SiafundOutputs = append(parentTxn.SiafundOutputs, exactOutput)
   474  
   475  	// Create a refund output if needed.
   476  	if !amount.Equals(fund) {
   477  		refundUnlockConditions, err := tb.wallet.nextPrimarySeedAddress(tb.wallet.dbTx)
   478  		if err != nil {
   479  			return err
   480  		}
   481  		refundOutput := types.SiafundOutput{
   482  			Value:      fund.Sub(amount),
   483  			UnlockHash: refundUnlockConditions.UnlockHash(),
   484  		}
   485  		parentTxn.SiafundOutputs = append(parentTxn.SiafundOutputs, refundOutput)
   486  	}
   487  
   488  	// Sign all of the inputs to the parent transaction.
   489  	for _, sfi := range parentTxn.SiafundInputs {
   490  		addSignatures(&parentTxn, types.FullCoveredFields, sfi.UnlockConditions, crypto.Hash(sfi.ParentID), tb.wallet.keys[sfi.UnlockConditions.UnlockHash()], consensusHeight)
   491  	}
   492  
   493  	// Add the exact output.
   494  	claimUnlockConditions, err := tb.wallet.nextPrimarySeedAddress(tb.wallet.dbTx)
   495  	if err != nil {
   496  		return err
   497  	}
   498  	newInput := types.SiafundInput{
   499  		ParentID:         parentTxn.SiafundOutputID(0),
   500  		UnlockConditions: parentUnlockConditions,
   501  		ClaimUnlockHash:  claimUnlockConditions.UnlockHash(),
   502  	}
   503  	tb.newParents = append(tb.newParents, len(tb.parents))
   504  	tb.parents = append(tb.parents, parentTxn)
   505  	tb.siafundInputs = append(tb.siafundInputs, len(tb.transaction.SiafundInputs))
   506  	tb.transaction.SiafundInputs = append(tb.transaction.SiafundInputs, newInput)
   507  
   508  	// Mark all outputs that were spent as spent.
   509  	for _, sfoid := range spentSfoids {
   510  		err = dbPutSpentOutput(tb.wallet.dbTx, types.OutputID(sfoid), consensusHeight)
   511  		if err != nil {
   512  			return err
   513  		}
   514  	}
   515  	return nil
   516  }
   517  
   518  // UnconfirmedParents returns the unconfirmed parents of the transaction set
   519  // that is being constructed by the transaction builder.
   520  func (tb *transactionBuilder) UnconfirmedParents() (parents []types.Transaction, err error) {
   521  	// Currently we don't need to call UnconfirmedParents after the transaction
   522  	// was signed so we don't allow doing that. If for some reason our
   523  	// requirements change, we can remove this check. The only downside is,
   524  	// that it might lead to transactions being returned that are not actually
   525  	// parents in case the signed transaction already has child transactions.
   526  	if tb.signed {
   527  		return nil, errBuilderAlreadySigned
   528  	}
   529  	addedParents := make(map[types.TransactionID]struct{})
   530  	for _, p := range tb.parents {
   531  		for _, sci := range p.SiacoinInputs {
   532  			tSet := tb.wallet.tpool.TransactionSet(crypto.Hash(sci.ParentID))
   533  			for _, txn := range tSet {
   534  				// Add the transaction to the parents.
   535  				txnID := txn.ID()
   536  				if _, exists := addedParents[txnID]; exists {
   537  					continue
   538  				}
   539  				addedParents[txnID] = struct{}{}
   540  				parents = append(parents, txn)
   541  
   542  				// When we found the transaction that contains the output that
   543  				// is spent by sci we stop to avoid adding child transactions.
   544  				for i := range txn.SiacoinOutputs {
   545  					if txn.SiacoinOutputID(uint64(i)) == sci.ParentID {
   546  						break
   547  					}
   548  				}
   549  			}
   550  		}
   551  	}
   552  	return
   553  }
   554  
   555  // AddParents adds a set of parents to the transaction.
   556  func (tb *transactionBuilder) AddParents(newParents []types.Transaction) {
   557  	tb.parents = append(tb.parents, newParents...)
   558  }
   559  
   560  // AddMinerFee adds a miner fee to the transaction, returning the index of the
   561  // miner fee within the transaction.
   562  func (tb *transactionBuilder) AddMinerFee(fee types.Currency) uint64 {
   563  	tb.transaction.MinerFees = append(tb.transaction.MinerFees, fee)
   564  	return uint64(len(tb.transaction.MinerFees) - 1)
   565  }
   566  
   567  // AddSiacoinInput adds a siacoin input to the transaction, returning the index
   568  // of the siacoin input within the transaction. When 'Sign' gets called, this
   569  // input will be left unsigned.
   570  func (tb *transactionBuilder) AddSiacoinInput(input types.SiacoinInput) uint64 {
   571  	tb.transaction.SiacoinInputs = append(tb.transaction.SiacoinInputs, input)
   572  	return uint64(len(tb.transaction.SiacoinInputs) - 1)
   573  }
   574  
   575  // AddSiacoinOutput adds a siacoin output to the transaction, returning the
   576  // index of the siacoin output within the transaction.
   577  func (tb *transactionBuilder) AddSiacoinOutput(output types.SiacoinOutput) uint64 {
   578  	tb.transaction.SiacoinOutputs = append(tb.transaction.SiacoinOutputs, output)
   579  	return uint64(len(tb.transaction.SiacoinOutputs) - 1)
   580  }
   581  
   582  // AddFileContract adds a file contract to the transaction, returning the index
   583  // of the file contract within the transaction.
   584  func (tb *transactionBuilder) AddFileContract(fc types.FileContract) uint64 {
   585  	tb.transaction.FileContracts = append(tb.transaction.FileContracts, fc)
   586  	return uint64(len(tb.transaction.FileContracts) - 1)
   587  }
   588  
   589  // AddFileContractRevision adds a file contract revision to the transaction,
   590  // returning the index of the file contract revision within the transaction.
   591  // When 'Sign' gets called, this revision will be left unsigned.
   592  func (tb *transactionBuilder) AddFileContractRevision(fcr types.FileContractRevision) uint64 {
   593  	tb.transaction.FileContractRevisions = append(tb.transaction.FileContractRevisions, fcr)
   594  	return uint64(len(tb.transaction.FileContractRevisions) - 1)
   595  }
   596  
   597  // AddStorageProof adds a storage proof to the transaction, returning the index
   598  // of the storage proof within the transaction.
   599  func (tb *transactionBuilder) AddStorageProof(sp types.StorageProof) uint64 {
   600  	tb.transaction.StorageProofs = append(tb.transaction.StorageProofs, sp)
   601  	return uint64(len(tb.transaction.StorageProofs) - 1)
   602  }
   603  
   604  // AddSiafundInput adds a siafund input to the transaction, returning the index
   605  // of the siafund input within the transaction. When 'Sign' is called, this
   606  // input will be left unsigned.
   607  func (tb *transactionBuilder) AddSiafundInput(input types.SiafundInput) uint64 {
   608  	tb.transaction.SiafundInputs = append(tb.transaction.SiafundInputs, input)
   609  	return uint64(len(tb.transaction.SiafundInputs) - 1)
   610  }
   611  
   612  // AddSiafundOutput adds a siafund output to the transaction, returning the
   613  // index of the siafund output within the transaction.
   614  func (tb *transactionBuilder) AddSiafundOutput(output types.SiafundOutput) uint64 {
   615  	tb.transaction.SiafundOutputs = append(tb.transaction.SiafundOutputs, output)
   616  	return uint64(len(tb.transaction.SiafundOutputs) - 1)
   617  }
   618  
   619  // AddArbitraryData adds arbitrary data to the transaction, returning the index
   620  // of the data within the transaction.
   621  func (tb *transactionBuilder) AddArbitraryData(arb []byte) uint64 {
   622  	tb.transaction.ArbitraryData = append(tb.transaction.ArbitraryData, arb)
   623  	return uint64(len(tb.transaction.ArbitraryData) - 1)
   624  }
   625  
   626  // AddTransactionSignature adds a transaction signature to the transaction,
   627  // returning the index of the signature within the transaction. The signature
   628  // should already be valid, and shouldn't sign any of the inputs that were
   629  // added by calling 'FundSiacoins' or 'FundSiafunds'.
   630  func (tb *transactionBuilder) AddTransactionSignature(sig types.TransactionSignature) uint64 {
   631  	tb.transaction.TransactionSignatures = append(tb.transaction.TransactionSignatures, sig)
   632  	return uint64(len(tb.transaction.TransactionSignatures) - 1)
   633  }
   634  
   635  // Drop discards all of the outputs in a transaction, returning them to the
   636  // pool so that other transactions may use them. 'Drop' should only be called
   637  // if a transaction is both unsigned and will not be used any further.
   638  func (tb *transactionBuilder) Drop() {
   639  	tb.wallet.mu.Lock()
   640  	defer tb.wallet.mu.Unlock()
   641  
   642  	// Iterate through all parents and the transaction itself and restore all
   643  	// outputs to the list of available outputs.
   644  	txns := append(tb.parents, tb.transaction)
   645  	for _, txn := range txns {
   646  		for _, sci := range txn.SiacoinInputs {
   647  			dbDeleteSpentOutput(tb.wallet.dbTx, types.OutputID(sci.ParentID))
   648  		}
   649  	}
   650  
   651  	tb.parents = nil
   652  	tb.signed = false
   653  	tb.transaction = types.Transaction{}
   654  
   655  	tb.newParents = nil
   656  	tb.siacoinInputs = nil
   657  	tb.siafundInputs = nil
   658  	tb.transactionSignatures = nil
   659  }
   660  
   661  // Sign will sign any inputs added by 'FundSiacoins' or 'FundSiafunds' and
   662  // return a transaction set that contains all parents prepended to the
   663  // transaction. If more fields need to be added, a new transaction builder will
   664  // need to be created.
   665  //
   666  // If the whole transaction flag is set to true, then the whole transaction
   667  // flag will be set in the covered fields object. If the whole transaction flag
   668  // is set to false, then the covered fields object will cover all fields that
   669  // have already been added to the transaction, but will also leave room for
   670  // more fields to be added.
   671  //
   672  // Sign should not be called more than once. If, for some reason, there is an
   673  // error while calling Sign, the builder should be dropped.
   674  func (tb *transactionBuilder) Sign(wholeTransaction bool) ([]types.Transaction, error) {
   675  	if tb.signed {
   676  		return nil, errBuilderAlreadySigned
   677  	}
   678  
   679  	tb.wallet.mu.Lock()
   680  	consensusHeight, err := dbGetConsensusHeight(tb.wallet.dbTx)
   681  	tb.wallet.mu.Unlock()
   682  	if err != nil {
   683  		return nil, err
   684  	}
   685  
   686  	// Create the coveredfields struct.
   687  	var coveredFields types.CoveredFields
   688  	if wholeTransaction {
   689  		coveredFields = types.CoveredFields{WholeTransaction: true}
   690  	} else {
   691  		for i := range tb.transaction.MinerFees {
   692  			coveredFields.MinerFees = append(coveredFields.MinerFees, uint64(i))
   693  		}
   694  		for i := range tb.transaction.SiacoinInputs {
   695  			coveredFields.SiacoinInputs = append(coveredFields.SiacoinInputs, uint64(i))
   696  		}
   697  		for i := range tb.transaction.SiacoinOutputs {
   698  			coveredFields.SiacoinOutputs = append(coveredFields.SiacoinOutputs, uint64(i))
   699  		}
   700  		for i := range tb.transaction.FileContracts {
   701  			coveredFields.FileContracts = append(coveredFields.FileContracts, uint64(i))
   702  		}
   703  		for i := range tb.transaction.FileContractRevisions {
   704  			coveredFields.FileContractRevisions = append(coveredFields.FileContractRevisions, uint64(i))
   705  		}
   706  		for i := range tb.transaction.StorageProofs {
   707  			coveredFields.StorageProofs = append(coveredFields.StorageProofs, uint64(i))
   708  		}
   709  		for i := range tb.transaction.SiafundInputs {
   710  			coveredFields.SiafundInputs = append(coveredFields.SiafundInputs, uint64(i))
   711  		}
   712  		for i := range tb.transaction.SiafundOutputs {
   713  			coveredFields.SiafundOutputs = append(coveredFields.SiafundOutputs, uint64(i))
   714  		}
   715  		for i := range tb.transaction.ArbitraryData {
   716  			coveredFields.ArbitraryData = append(coveredFields.ArbitraryData, uint64(i))
   717  		}
   718  	}
   719  	// TransactionSignatures don't get covered by the 'WholeTransaction' flag,
   720  	// and must be covered manually.
   721  	for i := range tb.transaction.TransactionSignatures {
   722  		coveredFields.TransactionSignatures = append(coveredFields.TransactionSignatures, uint64(i))
   723  	}
   724  
   725  	// For each siacoin input in the transaction that we added, provide a
   726  	// signature.
   727  	tb.wallet.mu.RLock()
   728  	defer tb.wallet.mu.RUnlock()
   729  	for _, inputIndex := range tb.siacoinInputs {
   730  		input := tb.transaction.SiacoinInputs[inputIndex]
   731  		key, ok := tb.wallet.keys[input.UnlockConditions.UnlockHash()]
   732  		if !ok {
   733  			return nil, errors.New("transaction builder added an input that it cannot sign")
   734  		}
   735  		newSigIndices := addSignatures(&tb.transaction, coveredFields, input.UnlockConditions, crypto.Hash(input.ParentID), key, consensusHeight)
   736  		tb.transactionSignatures = append(tb.transactionSignatures, newSigIndices...)
   737  		tb.signed = true // Signed is set to true after one successful signature to indicate that future signings can cause issues.
   738  	}
   739  	for _, inputIndex := range tb.siafundInputs {
   740  		input := tb.transaction.SiafundInputs[inputIndex]
   741  		key, ok := tb.wallet.keys[input.UnlockConditions.UnlockHash()]
   742  		if !ok {
   743  			return nil, errors.New("transaction builder added an input that it cannot sign")
   744  		}
   745  		newSigIndices := addSignatures(&tb.transaction, coveredFields, input.UnlockConditions, crypto.Hash(input.ParentID), key, consensusHeight)
   746  		tb.transactionSignatures = append(tb.transactionSignatures, newSigIndices...)
   747  		tb.signed = true // Signed is set to true after one successful signature to indicate that future signings can cause issues.
   748  	}
   749  
   750  	// Get the transaction set and delete the transaction from the registry.
   751  	txnSet := append(tb.parents, tb.transaction)
   752  	return txnSet, nil
   753  }
   754  
   755  // ViewTransaction returns a transaction-in-progress along with all of its
   756  // parents, specified by id. An error is returned if the id is invalid.  Note
   757  // that ids become invalid for a transaction after 'SignTransaction' has been
   758  // called because the transaction gets deleted.
   759  func (tb *transactionBuilder) View() (types.Transaction, []types.Transaction) {
   760  	return tb.transaction, tb.parents
   761  }
   762  
   763  // ViewAdded returns all of the siacoin inputs, siafund inputs, and parent
   764  // transactions that have been automatically added by the builder.
   765  func (tb *transactionBuilder) ViewAdded() (newParents, siacoinInputs, siafundInputs, transactionSignatures []int) {
   766  	return tb.newParents, tb.siacoinInputs, tb.siafundInputs, tb.transactionSignatures
   767  }
   768  
   769  // registerTransaction takes a transaction and its parents and returns a
   770  // wallet.TransactionBuilder which can be used to expand the transaction. The
   771  // most typical call is 'RegisterTransaction(types.Transaction{}, nil)', which
   772  // registers a new transaction without parents.
   773  func (w *Wallet) registerTransaction(t types.Transaction, parents []types.Transaction) *transactionBuilder {
   774  	// Create a deep copy of the transaction and parents by encoding them. A
   775  	// deep copy ensures that there are no pointer or slice related errors -
   776  	// the builder will be working directly on the transaction, and the
   777  	// transaction may be in use elsewhere (in this case, the host is using the
   778  	// transaction.
   779  	pBytes := encoding.Marshal(parents)
   780  	var pCopy []types.Transaction
   781  	err := encoding.Unmarshal(pBytes, &pCopy)
   782  	if err != nil {
   783  		panic(err)
   784  	}
   785  	tBytes := encoding.Marshal(t)
   786  	var tCopy types.Transaction
   787  	err = encoding.Unmarshal(tBytes, &tCopy)
   788  	if err != nil {
   789  		panic(err)
   790  	}
   791  	return &transactionBuilder{
   792  		parents:     pCopy,
   793  		transaction: tCopy,
   794  
   795  		wallet: w,
   796  	}
   797  }
   798  
   799  // RegisterTransaction takes a transaction and its parents and returns a
   800  // modules.TransactionBuilder which can be used to expand the transaction. The
   801  // most typical call is 'RegisterTransaction(types.Transaction{}, nil)', which
   802  // registers a new transaction without parents.
   803  func (w *Wallet) RegisterTransaction(t types.Transaction, parents []types.Transaction) (modules.TransactionBuilder, error) {
   804  	if err := w.tg.Add(); err != nil {
   805  		return nil, err
   806  	}
   807  	defer w.tg.Done()
   808  	w.mu.Lock()
   809  	defer w.mu.Unlock()
   810  	return w.registerTransaction(t, parents), nil
   811  }
   812  
   813  // StartTransaction is a convenience function that calls
   814  // RegisterTransaction(types.Transaction{}, nil).
   815  func (w *Wallet) StartTransaction() (modules.TransactionBuilder, error) {
   816  	if err := w.tg.Add(); err != nil {
   817  		return nil, err
   818  	}
   819  	defer w.tg.Done()
   820  	return w.RegisterTransaction(types.Transaction{}, nil)
   821  }