github.com/Synthesix/Sia@v1.3.3-0.20180413141344-f863baeed3ca/modules/wallet/transactionbuilder.go (about)

     1  package wallet
     2  
     3  import (
     4  	"bytes"
     5  	"errors"
     6  	"sort"
     7  
     8  	"github.com/Synthesix/Sia/crypto"
     9  	"github.com/Synthesix/Sia/encoding"
    10  	"github.com/Synthesix/Sia/modules"
    11  	"github.com/Synthesix/Sia/types"
    12  
    13  	"github.com/coreos/bbolt"
    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) (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)
    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  // FundSiacoins will add a siacoin input of exactly 'amount' to the
   116  // transaction. A parent transaction may be needed to achieve an input with the
   117  // correct value. The siacoin input will not be signed until 'Sign' is called
   118  // on the transaction builder.
   119  func (tb *transactionBuilder) FundSiacoins(amount types.Currency) error {
   120  	// dustThreshold has to be obtained separate from the lock
   121  	dustThreshold := tb.wallet.DustThreshold()
   122  
   123  	tb.wallet.mu.Lock()
   124  	defer tb.wallet.mu.Unlock()
   125  
   126  	consensusHeight, err := dbGetConsensusHeight(tb.wallet.dbTx)
   127  	if err != nil {
   128  		return err
   129  	}
   130  
   131  	// Collect a value-sorted set of siacoin outputs.
   132  	var so sortedOutputs
   133  	err = dbForEachSiacoinOutput(tb.wallet.dbTx, func(scoid types.SiacoinOutputID, sco types.SiacoinOutput) {
   134  		so.ids = append(so.ids, scoid)
   135  		so.outputs = append(so.outputs, sco)
   136  	})
   137  	if err != nil {
   138  		return err
   139  	}
   140  	// Add all of the unconfirmed outputs as well.
   141  	for _, upt := range tb.wallet.unconfirmedProcessedTransactions {
   142  		for i, sco := range upt.Transaction.SiacoinOutputs {
   143  			// Determine if the output belongs to the wallet.
   144  			_, exists := tb.wallet.keys[sco.UnlockHash]
   145  			if !exists {
   146  				continue
   147  			}
   148  			so.ids = append(so.ids, upt.Transaction.SiacoinOutputID(uint64(i)))
   149  			so.outputs = append(so.outputs, sco)
   150  		}
   151  	}
   152  	sort.Sort(sort.Reverse(so))
   153  
   154  	// Create and fund a parent transaction that will add the correct amount of
   155  	// siacoins to the transaction.
   156  	var fund types.Currency
   157  	// potentialFund tracks the balance of the wallet including outputs that
   158  	// have been spent in other unconfirmed transactions recently. This is to
   159  	// provide the user with a more useful error message in the event that they
   160  	// are overspending.
   161  	var potentialFund types.Currency
   162  	parentTxn := types.Transaction{}
   163  	var spentScoids []types.SiacoinOutputID
   164  	for i := range so.ids {
   165  		scoid := so.ids[i]
   166  		sco := so.outputs[i]
   167  		// Check that the output can be spent.
   168  		if err := tb.wallet.checkOutput(tb.wallet.dbTx, consensusHeight, scoid, sco, dustThreshold); err != nil {
   169  			if err == errSpendHeightTooHigh {
   170  				potentialFund = potentialFund.Add(sco.Value)
   171  			}
   172  			continue
   173  		}
   174  
   175  		// Add a siacoin input for this output.
   176  		sci := types.SiacoinInput{
   177  			ParentID:         scoid,
   178  			UnlockConditions: tb.wallet.keys[sco.UnlockHash].UnlockConditions,
   179  		}
   180  		parentTxn.SiacoinInputs = append(parentTxn.SiacoinInputs, sci)
   181  		spentScoids = append(spentScoids, scoid)
   182  
   183  		// Add the output to the total fund
   184  		fund = fund.Add(sco.Value)
   185  		potentialFund = potentialFund.Add(sco.Value)
   186  		if fund.Cmp(amount) >= 0 {
   187  			break
   188  		}
   189  	}
   190  	if potentialFund.Cmp(amount) >= 0 && fund.Cmp(amount) < 0 {
   191  		return modules.ErrIncompleteTransactions
   192  	}
   193  	if fund.Cmp(amount) < 0 {
   194  		return modules.ErrLowBalance
   195  	}
   196  
   197  	// Create and add the output that will be used to fund the standard
   198  	// transaction.
   199  	parentUnlockConditions, err := tb.wallet.nextPrimarySeedAddress(tb.wallet.dbTx)
   200  	if err != nil {
   201  		return err
   202  	}
   203  
   204  	exactOutput := types.SiacoinOutput{
   205  		Value:      amount,
   206  		UnlockHash: parentUnlockConditions.UnlockHash(),
   207  	}
   208  	parentTxn.SiacoinOutputs = append(parentTxn.SiacoinOutputs, exactOutput)
   209  
   210  	// Create a refund output if needed.
   211  	if !amount.Equals(fund) {
   212  		refundUnlockConditions, err := tb.wallet.nextPrimarySeedAddress(tb.wallet.dbTx)
   213  		if err != nil {
   214  			return err
   215  		}
   216  		refundOutput := types.SiacoinOutput{
   217  			Value:      fund.Sub(amount),
   218  			UnlockHash: refundUnlockConditions.UnlockHash(),
   219  		}
   220  		parentTxn.SiacoinOutputs = append(parentTxn.SiacoinOutputs, refundOutput)
   221  	}
   222  
   223  	// Sign all of the inputs to the parent transaction.
   224  	for _, sci := range parentTxn.SiacoinInputs {
   225  		addSignatures(&parentTxn, types.FullCoveredFields, sci.UnlockConditions, crypto.Hash(sci.ParentID), tb.wallet.keys[sci.UnlockConditions.UnlockHash()])
   226  	}
   227  	// Mark the parent output as spent. Must be done after the transaction is
   228  	// finished because otherwise the txid and output id will change.
   229  	err = dbPutSpentOutput(tb.wallet.dbTx, types.OutputID(parentTxn.SiacoinOutputID(0)), consensusHeight)
   230  	if err != nil {
   231  		return err
   232  	}
   233  
   234  	// Add the exact output.
   235  	newInput := types.SiacoinInput{
   236  		ParentID:         parentTxn.SiacoinOutputID(0),
   237  		UnlockConditions: parentUnlockConditions,
   238  	}
   239  	tb.newParents = append(tb.newParents, len(tb.parents))
   240  	tb.parents = append(tb.parents, parentTxn)
   241  	tb.siacoinInputs = append(tb.siacoinInputs, len(tb.transaction.SiacoinInputs))
   242  	tb.transaction.SiacoinInputs = append(tb.transaction.SiacoinInputs, newInput)
   243  
   244  	// Mark all outputs that were spent as spent.
   245  	for _, scoid := range spentScoids {
   246  		err = dbPutSpentOutput(tb.wallet.dbTx, types.OutputID(scoid), consensusHeight)
   247  		if err != nil {
   248  			return err
   249  		}
   250  	}
   251  	return nil
   252  }
   253  
   254  // FundSiafunds will add a siafund input of exactly 'amount' to the
   255  // transaction. A parent transaction may be needed to achieve an input with the
   256  // correct value. The siafund input will not be signed until 'Sign' is called
   257  // on the transaction builder.
   258  func (tb *transactionBuilder) FundSiafunds(amount types.Currency) error {
   259  	tb.wallet.mu.Lock()
   260  	defer tb.wallet.mu.Unlock()
   261  
   262  	consensusHeight, err := dbGetConsensusHeight(tb.wallet.dbTx)
   263  	if err != nil {
   264  		return err
   265  	}
   266  
   267  	// Create and fund a parent transaction that will add the correct amount of
   268  	// siafunds to the transaction.
   269  	var fund types.Currency
   270  	var potentialFund types.Currency
   271  	parentTxn := types.Transaction{}
   272  	var spentSfoids []types.SiafundOutputID
   273  	c := tb.wallet.dbTx.Bucket(bucketSiafundOutputs).Cursor()
   274  	for idBytes, sfoBytes := c.First(); idBytes != nil; idBytes, sfoBytes = c.Next() {
   275  		var sfoid types.SiafundOutputID
   276  		var sfo types.SiafundOutput
   277  		if err := encoding.Unmarshal(idBytes, &sfoid); err != nil {
   278  			return err
   279  		} else if err := encoding.Unmarshal(sfoBytes, &sfo); err != nil {
   280  			return err
   281  		}
   282  
   283  		// Check that this output has not recently been spent by the wallet.
   284  		spendHeight, err := dbGetSpentOutput(tb.wallet.dbTx, types.OutputID(sfoid))
   285  		if err != nil {
   286  			// mimic map behavior: no entry means zero value
   287  			spendHeight = 0
   288  		}
   289  		// Prevent an underflow error.
   290  		allowedHeight := consensusHeight - RespendTimeout
   291  		if consensusHeight < RespendTimeout {
   292  			allowedHeight = 0
   293  		}
   294  		if spendHeight > allowedHeight {
   295  			potentialFund = potentialFund.Add(sfo.Value)
   296  			continue
   297  		}
   298  		outputUnlockConditions := tb.wallet.keys[sfo.UnlockHash].UnlockConditions
   299  		if consensusHeight < outputUnlockConditions.Timelock {
   300  			continue
   301  		}
   302  
   303  		// Add a siafund input for this output.
   304  		parentClaimUnlockConditions, err := tb.wallet.nextPrimarySeedAddress(tb.wallet.dbTx)
   305  		if err != nil {
   306  			return err
   307  		}
   308  		sfi := types.SiafundInput{
   309  			ParentID:         sfoid,
   310  			UnlockConditions: outputUnlockConditions,
   311  			ClaimUnlockHash:  parentClaimUnlockConditions.UnlockHash(),
   312  		}
   313  		parentTxn.SiafundInputs = append(parentTxn.SiafundInputs, sfi)
   314  		spentSfoids = append(spentSfoids, sfoid)
   315  
   316  		// Add the output to the total fund
   317  		fund = fund.Add(sfo.Value)
   318  		potentialFund = potentialFund.Add(sfo.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  	exactOutput := types.SiafundOutput{
   337  		Value:      amount,
   338  		UnlockHash: parentUnlockConditions.UnlockHash(),
   339  	}
   340  	parentTxn.SiafundOutputs = append(parentTxn.SiafundOutputs, exactOutput)
   341  
   342  	// Create a refund output if needed.
   343  	if !amount.Equals(fund) {
   344  		refundUnlockConditions, err := tb.wallet.nextPrimarySeedAddress(tb.wallet.dbTx)
   345  		if err != nil {
   346  			return err
   347  		}
   348  		refundOutput := types.SiafundOutput{
   349  			Value:      fund.Sub(amount),
   350  			UnlockHash: refundUnlockConditions.UnlockHash(),
   351  		}
   352  		parentTxn.SiafundOutputs = append(parentTxn.SiafundOutputs, refundOutput)
   353  	}
   354  
   355  	// Sign all of the inputs to the parent transaction.
   356  	for _, sfi := range parentTxn.SiafundInputs {
   357  		addSignatures(&parentTxn, types.FullCoveredFields, sfi.UnlockConditions, crypto.Hash(sfi.ParentID), tb.wallet.keys[sfi.UnlockConditions.UnlockHash()])
   358  	}
   359  
   360  	// Add the exact output.
   361  	claimUnlockConditions, err := tb.wallet.nextPrimarySeedAddress(tb.wallet.dbTx)
   362  	if err != nil {
   363  		return err
   364  	}
   365  	newInput := types.SiafundInput{
   366  		ParentID:         parentTxn.SiafundOutputID(0),
   367  		UnlockConditions: parentUnlockConditions,
   368  		ClaimUnlockHash:  claimUnlockConditions.UnlockHash(),
   369  	}
   370  	tb.newParents = append(tb.newParents, len(tb.parents))
   371  	tb.parents = append(tb.parents, parentTxn)
   372  	tb.siafundInputs = append(tb.siafundInputs, len(tb.transaction.SiafundInputs))
   373  	tb.transaction.SiafundInputs = append(tb.transaction.SiafundInputs, newInput)
   374  
   375  	// Mark all outputs that were spent as spent.
   376  	for _, sfoid := range spentSfoids {
   377  		err = dbPutSpentOutput(tb.wallet.dbTx, types.OutputID(sfoid), consensusHeight)
   378  		if err != nil {
   379  			return err
   380  		}
   381  	}
   382  	return nil
   383  }
   384  
   385  // AddParents adds a set of parents to the transaction.
   386  func (tb *transactionBuilder) AddParents(newParents []types.Transaction) {
   387  	tb.parents = append(tb.parents, newParents...)
   388  }
   389  
   390  // AddMinerFee adds a miner fee to the transaction, returning the index of the
   391  // miner fee within the transaction.
   392  func (tb *transactionBuilder) AddMinerFee(fee types.Currency) uint64 {
   393  	tb.transaction.MinerFees = append(tb.transaction.MinerFees, fee)
   394  	return uint64(len(tb.transaction.MinerFees) - 1)
   395  }
   396  
   397  // AddSiacoinInput adds a siacoin input to the transaction, returning the index
   398  // of the siacoin input within the transaction. When 'Sign' gets called, this
   399  // input will be left unsigned.
   400  func (tb *transactionBuilder) AddSiacoinInput(input types.SiacoinInput) uint64 {
   401  	tb.transaction.SiacoinInputs = append(tb.transaction.SiacoinInputs, input)
   402  	return uint64(len(tb.transaction.SiacoinInputs) - 1)
   403  }
   404  
   405  // AddSiacoinOutput adds a siacoin output to the transaction, returning the
   406  // index of the siacoin output within the transaction.
   407  func (tb *transactionBuilder) AddSiacoinOutput(output types.SiacoinOutput) uint64 {
   408  	tb.transaction.SiacoinOutputs = append(tb.transaction.SiacoinOutputs, output)
   409  	return uint64(len(tb.transaction.SiacoinOutputs) - 1)
   410  }
   411  
   412  // AddFileContract adds a file contract to the transaction, returning the index
   413  // of the file contract within the transaction.
   414  func (tb *transactionBuilder) AddFileContract(fc types.FileContract) uint64 {
   415  	tb.transaction.FileContracts = append(tb.transaction.FileContracts, fc)
   416  	return uint64(len(tb.transaction.FileContracts) - 1)
   417  }
   418  
   419  // AddFileContractRevision adds a file contract revision to the transaction,
   420  // returning the index of the file contract revision within the transaction.
   421  // When 'Sign' gets called, this revision will be left unsigned.
   422  func (tb *transactionBuilder) AddFileContractRevision(fcr types.FileContractRevision) uint64 {
   423  	tb.transaction.FileContractRevisions = append(tb.transaction.FileContractRevisions, fcr)
   424  	return uint64(len(tb.transaction.FileContractRevisions) - 1)
   425  }
   426  
   427  // AddStorageProof adds a storage proof to the transaction, returning the index
   428  // of the storage proof within the transaction.
   429  func (tb *transactionBuilder) AddStorageProof(sp types.StorageProof) uint64 {
   430  	tb.transaction.StorageProofs = append(tb.transaction.StorageProofs, sp)
   431  	return uint64(len(tb.transaction.StorageProofs) - 1)
   432  }
   433  
   434  // AddSiafundInput adds a siafund input to the transaction, returning the index
   435  // of the siafund input within the transaction. When 'Sign' is called, this
   436  // input will be left unsigned.
   437  func (tb *transactionBuilder) AddSiafundInput(input types.SiafundInput) uint64 {
   438  	tb.transaction.SiafundInputs = append(tb.transaction.SiafundInputs, input)
   439  	return uint64(len(tb.transaction.SiafundInputs) - 1)
   440  }
   441  
   442  // AddSiafundOutput adds a siafund output to the transaction, returning the
   443  // index of the siafund output within the transaction.
   444  func (tb *transactionBuilder) AddSiafundOutput(output types.SiafundOutput) uint64 {
   445  	tb.transaction.SiafundOutputs = append(tb.transaction.SiafundOutputs, output)
   446  	return uint64(len(tb.transaction.SiafundOutputs) - 1)
   447  }
   448  
   449  // AddArbitraryData adds arbitrary data to the transaction, returning the index
   450  // of the data within the transaction.
   451  func (tb *transactionBuilder) AddArbitraryData(arb []byte) uint64 {
   452  	tb.transaction.ArbitraryData = append(tb.transaction.ArbitraryData, arb)
   453  	return uint64(len(tb.transaction.ArbitraryData) - 1)
   454  }
   455  
   456  // AddTransactionSignature adds a transaction signature to the transaction,
   457  // returning the index of the signature within the transaction. The signature
   458  // should already be valid, and shouldn't sign any of the inputs that were
   459  // added by calling 'FundSiacoins' or 'FundSiafunds'.
   460  func (tb *transactionBuilder) AddTransactionSignature(sig types.TransactionSignature) uint64 {
   461  	tb.transaction.TransactionSignatures = append(tb.transaction.TransactionSignatures, sig)
   462  	return uint64(len(tb.transaction.TransactionSignatures) - 1)
   463  }
   464  
   465  // Drop discards all of the outputs in a transaction, returning them to the
   466  // pool so that other transactions may use them. 'Drop' should only be called
   467  // if a transaction is both unsigned and will not be used any further.
   468  func (tb *transactionBuilder) Drop() {
   469  	tb.wallet.mu.Lock()
   470  	defer tb.wallet.mu.Unlock()
   471  
   472  	// Iterate through all parents and the transaction itself and restore all
   473  	// outputs to the list of available outputs.
   474  	txns := append(tb.parents, tb.transaction)
   475  	for _, txn := range txns {
   476  		for _, sci := range txn.SiacoinInputs {
   477  			dbDeleteSpentOutput(tb.wallet.dbTx, types.OutputID(sci.ParentID))
   478  		}
   479  	}
   480  
   481  	tb.parents = nil
   482  	tb.signed = false
   483  	tb.transaction = types.Transaction{}
   484  
   485  	tb.newParents = nil
   486  	tb.siacoinInputs = nil
   487  	tb.siafundInputs = nil
   488  	tb.transactionSignatures = nil
   489  }
   490  
   491  // Sign will sign any inputs added by 'FundSiacoins' or 'FundSiafunds' and
   492  // return a transaction set that contains all parents prepended to the
   493  // transaction. If more fields need to be added, a new transaction builder will
   494  // need to be created.
   495  //
   496  // If the whole transaction flag is set to true, then the whole transaction
   497  // flag will be set in the covered fields object. If the whole transaction flag
   498  // is set to false, then the covered fields object will cover all fields that
   499  // have already been added to the transaction, but will also leave room for
   500  // more fields to be added.
   501  //
   502  // Sign should not be called more than once. If, for some reason, there is an
   503  // error while calling Sign, the builder should be dropped.
   504  func (tb *transactionBuilder) Sign(wholeTransaction bool) ([]types.Transaction, error) {
   505  	if tb.signed {
   506  		return nil, errBuilderAlreadySigned
   507  	}
   508  
   509  	// Create the coveredfields struct.
   510  	var coveredFields types.CoveredFields
   511  	if wholeTransaction {
   512  		coveredFields = types.CoveredFields{WholeTransaction: true}
   513  	} else {
   514  		for i := range tb.transaction.MinerFees {
   515  			coveredFields.MinerFees = append(coveredFields.MinerFees, uint64(i))
   516  		}
   517  		for i := range tb.transaction.SiacoinInputs {
   518  			coveredFields.SiacoinInputs = append(coveredFields.SiacoinInputs, uint64(i))
   519  		}
   520  		for i := range tb.transaction.SiacoinOutputs {
   521  			coveredFields.SiacoinOutputs = append(coveredFields.SiacoinOutputs, uint64(i))
   522  		}
   523  		for i := range tb.transaction.FileContracts {
   524  			coveredFields.FileContracts = append(coveredFields.FileContracts, uint64(i))
   525  		}
   526  		for i := range tb.transaction.FileContractRevisions {
   527  			coveredFields.FileContractRevisions = append(coveredFields.FileContractRevisions, uint64(i))
   528  		}
   529  		for i := range tb.transaction.StorageProofs {
   530  			coveredFields.StorageProofs = append(coveredFields.StorageProofs, uint64(i))
   531  		}
   532  		for i := range tb.transaction.SiafundInputs {
   533  			coveredFields.SiafundInputs = append(coveredFields.SiafundInputs, uint64(i))
   534  		}
   535  		for i := range tb.transaction.SiafundOutputs {
   536  			coveredFields.SiafundOutputs = append(coveredFields.SiafundOutputs, uint64(i))
   537  		}
   538  		for i := range tb.transaction.ArbitraryData {
   539  			coveredFields.ArbitraryData = append(coveredFields.ArbitraryData, uint64(i))
   540  		}
   541  	}
   542  	// TransactionSignatures don't get covered by the 'WholeTransaction' flag,
   543  	// and must be covered manually.
   544  	for i := range tb.transaction.TransactionSignatures {
   545  		coveredFields.TransactionSignatures = append(coveredFields.TransactionSignatures, uint64(i))
   546  	}
   547  
   548  	// For each siacoin input in the transaction that we added, provide a
   549  	// signature.
   550  	tb.wallet.mu.RLock()
   551  	defer tb.wallet.mu.RUnlock()
   552  	for _, inputIndex := range tb.siacoinInputs {
   553  		input := tb.transaction.SiacoinInputs[inputIndex]
   554  		key, ok := tb.wallet.keys[input.UnlockConditions.UnlockHash()]
   555  		if !ok {
   556  			return nil, errors.New("transaction builder added an input that it cannot sign")
   557  		}
   558  		newSigIndices := addSignatures(&tb.transaction, coveredFields, input.UnlockConditions, crypto.Hash(input.ParentID), key)
   559  		tb.transactionSignatures = append(tb.transactionSignatures, newSigIndices...)
   560  		tb.signed = true // Signed is set to true after one successful signature to indicate that future signings can cause issues.
   561  	}
   562  	for _, inputIndex := range tb.siafundInputs {
   563  		input := tb.transaction.SiafundInputs[inputIndex]
   564  		key, ok := tb.wallet.keys[input.UnlockConditions.UnlockHash()]
   565  		if !ok {
   566  			return nil, errors.New("transaction builder added an input that it cannot sign")
   567  		}
   568  		newSigIndices := addSignatures(&tb.transaction, coveredFields, input.UnlockConditions, crypto.Hash(input.ParentID), key)
   569  		tb.transactionSignatures = append(tb.transactionSignatures, newSigIndices...)
   570  		tb.signed = true // Signed is set to true after one successful signature to indicate that future signings can cause issues.
   571  	}
   572  
   573  	// Get the transaction set and delete the transaction from the registry.
   574  	txnSet := append(tb.parents, tb.transaction)
   575  	return txnSet, nil
   576  }
   577  
   578  // ViewTransaction returns a transaction-in-progress along with all of its
   579  // parents, specified by id. An error is returned if the id is invalid.  Note
   580  // that ids become invalid for a transaction after 'SignTransaction' has been
   581  // called because the transaction gets deleted.
   582  func (tb *transactionBuilder) View() (types.Transaction, []types.Transaction) {
   583  	return tb.transaction, tb.parents
   584  }
   585  
   586  // ViewAdded returns all of the siacoin inputs, siafund inputs, and parent
   587  // transactions that have been automatically added by the builder.
   588  func (tb *transactionBuilder) ViewAdded() (newParents, siacoinInputs, siafundInputs, transactionSignatures []int) {
   589  	return tb.newParents, tb.siacoinInputs, tb.siafundInputs, tb.transactionSignatures
   590  }
   591  
   592  // registerTransaction takes a transaction and its parents and returns a
   593  // wallet.TransactionBuilder which can be used to expand the transaction. The
   594  // most typical call is 'RegisterTransaction(types.Transaction{}, nil)', which
   595  // registers a new transaction without parents.
   596  func (w *Wallet) registerTransaction(t types.Transaction, parents []types.Transaction) *transactionBuilder {
   597  	// Create a deep copy of the transaction and parents by encoding them. A
   598  	// deep copy ensures that there are no pointer or slice related errors -
   599  	// the builder will be working directly on the transaction, and the
   600  	// transaction may be in use elsewhere (in this case, the host is using the
   601  	// transaction.
   602  	pBytes := encoding.Marshal(parents)
   603  	var pCopy []types.Transaction
   604  	err := encoding.Unmarshal(pBytes, &pCopy)
   605  	if err != nil {
   606  		panic(err)
   607  	}
   608  	tBytes := encoding.Marshal(t)
   609  	var tCopy types.Transaction
   610  	err = encoding.Unmarshal(tBytes, &tCopy)
   611  	if err != nil {
   612  		panic(err)
   613  	}
   614  	return &transactionBuilder{
   615  		parents:     pCopy,
   616  		transaction: tCopy,
   617  
   618  		wallet: w,
   619  	}
   620  }
   621  
   622  // RegisterTransaction takes a transaction and its parents and returns a
   623  // modules.TransactionBuilder which can be used to expand the transaction. The
   624  // most typical call is 'RegisterTransaction(types.Transaction{}, nil)', which
   625  // registers a new transaction without parents.
   626  func (w *Wallet) RegisterTransaction(t types.Transaction, parents []types.Transaction) modules.TransactionBuilder {
   627  	w.mu.Lock()
   628  	defer w.mu.Unlock()
   629  	return w.registerTransaction(t, parents)
   630  }
   631  
   632  // StartTransaction is a convenience function that calls
   633  // RegisterTransaction(types.Transaction{}, nil).
   634  func (w *Wallet) StartTransaction() modules.TransactionBuilder {
   635  	return w.RegisterTransaction(types.Transaction{}, nil)
   636  }