github.com/avahowell/sia@v0.5.1-beta.0.20160524050156-83dcc3d37c94/modules/wallet/transactionbuilder.go (about)

     1  package wallet
     2  
     3  import (
     4  	"bytes"
     5  	"errors"
     6  	"sort"
     7  
     8  	"github.com/NebulousLabs/Sia/crypto"
     9  	"github.com/NebulousLabs/Sia/encoding"
    10  	"github.com/NebulousLabs/Sia/modules"
    11  	"github.com/NebulousLabs/Sia/types"
    12  )
    13  
    14  var (
    15  	// errBuilderAlreadySigned indicates that the transaction builder has
    16  	// already added at least one successful signature to the transaction,
    17  	// meaning that future calls to Sign will result in an invalid transaction.
    18  	errBuilderAlreadySigned = errors.New("sign has already been called on this transaction builder, multiple calls can cause issues")
    19  )
    20  
    21  // transactionBuilder allows transactions to be manually constructed, including
    22  // the ability to fund transactions with siacoins and siafunds from the wallet.
    23  type transactionBuilder struct {
    24  	// 'signed' indicates that at least one transaction signature has been
    25  	// added to the wallet, meaning that future calls to 'Sign' will fail.
    26  	parents     []types.Transaction
    27  	signed      bool
    28  	transaction types.Transaction
    29  
    30  	newParents            []int
    31  	siacoinInputs         []int
    32  	siafundInputs         []int
    33  	transactionSignatures []int
    34  
    35  	wallet *Wallet
    36  }
    37  
    38  // addSignatures will sign a transaction using a spendable key, with support
    39  // for multisig spendable keys. Because of the restricted input, the function
    40  // is compatible with both siacoin inputs and siafund inputs.
    41  func addSignatures(txn *types.Transaction, cf types.CoveredFields, uc types.UnlockConditions, parentID crypto.Hash, spendKey spendableKey) (newSigIndices []int, err error) {
    42  	// Try to find the matching secret key for each public key - some public
    43  	// keys may not have a match. Some secret keys may be used multiple times,
    44  	// which is why public keys are used as the outer loop.
    45  	totalSignatures := uint64(0)
    46  	for i, siaPubKey := range uc.PublicKeys {
    47  		// Search for the matching secret key to the public key.
    48  		for j := range spendKey.SecretKeys {
    49  			pubKey := spendKey.SecretKeys[j].PublicKey()
    50  			if bytes.Compare(siaPubKey.Key, pubKey[:]) != 0 {
    51  				continue
    52  			}
    53  
    54  			// Found the right secret key, add a signature.
    55  			sig := types.TransactionSignature{
    56  				ParentID:       parentID,
    57  				CoveredFields:  cf,
    58  				PublicKeyIndex: uint64(i),
    59  			}
    60  			newSigIndices = append(newSigIndices, len(txn.TransactionSignatures))
    61  			txn.TransactionSignatures = append(txn.TransactionSignatures, sig)
    62  			sigIndex := len(txn.TransactionSignatures) - 1
    63  			sigHash := txn.SigHash(sigIndex)
    64  			encodedSig, err := crypto.SignHash(sigHash, spendKey.SecretKeys[j])
    65  			if err != nil {
    66  				return nil, err
    67  			}
    68  			txn.TransactionSignatures[sigIndex].Signature = encodedSig[:]
    69  
    70  			// Count that the signature has been added, and break out of the
    71  			// secret key loop.
    72  			totalSignatures++
    73  			break
    74  		}
    75  
    76  		// If there are enough signatures to satisfy the unlock conditions,
    77  		// break out of the outer loop.
    78  		if totalSignatures == uc.SignaturesRequired {
    79  			break
    80  		}
    81  	}
    82  	return newSigIndices, nil
    83  }
    84  
    85  // FundSiacoins will add a siacoin input of exaclty 'amount' to the
    86  // transaction. A parent transaction may be needed to achieve an input with the
    87  // correct value. The siacoin input will not be signed until 'Sign' is called
    88  // on the transaction builder.
    89  func (tb *transactionBuilder) FundSiacoins(amount types.Currency) error {
    90  	tb.wallet.mu.Lock()
    91  	defer tb.wallet.mu.Unlock()
    92  
    93  	// Collect a value-sorted set of siacoin outputs.
    94  	var so sortedOutputs
    95  	for scoid, sco := range tb.wallet.siacoinOutputs {
    96  		so.ids = append(so.ids, scoid)
    97  		so.outputs = append(so.outputs, sco)
    98  	}
    99  	// Add all of the unconfirmed outputs as well.
   100  	for _, upt := range tb.wallet.unconfirmedProcessedTransactions {
   101  		for i, sco := range upt.Transaction.SiacoinOutputs {
   102  			// Determine if the output belongs to the wallet.
   103  			_, exists := tb.wallet.keys[sco.UnlockHash]
   104  			if !exists {
   105  				continue
   106  			}
   107  			so.ids = append(so.ids, upt.Transaction.SiacoinOutputID(uint64(i)))
   108  			so.outputs = append(so.outputs, sco)
   109  		}
   110  	}
   111  	sort.Sort(sort.Reverse(so))
   112  
   113  	// Create and fund a parent transaction that will add the correct amount of
   114  	// siacoins to the transaction.
   115  	var fund types.Currency
   116  	// potentialFund tracks the balance of the wallet including outputs that
   117  	// have been spent in other unconfirmed transactions recently. This is to
   118  	// provide the user with a more useful error message in the event that they
   119  	// are overspending.
   120  	var potentialFund types.Currency
   121  	parentTxn := types.Transaction{}
   122  	var spentScoids []types.SiacoinOutputID
   123  	for i := range so.ids {
   124  		scoid := so.ids[i]
   125  		sco := so.outputs[i]
   126  		// Check that this output has not recently been spent by the wallet.
   127  		spendHeight := tb.wallet.spentOutputs[types.OutputID(scoid)]
   128  		// Prevent an underflow error.
   129  		allowedHeight := tb.wallet.consensusSetHeight - RespendTimeout
   130  		if tb.wallet.consensusSetHeight < RespendTimeout {
   131  			allowedHeight = 0
   132  		}
   133  		if spendHeight > allowedHeight {
   134  			potentialFund = potentialFund.Add(sco.Value)
   135  			continue
   136  		}
   137  		outputUnlockConditions := tb.wallet.keys[sco.UnlockHash].UnlockConditions
   138  		if tb.wallet.consensusSetHeight < outputUnlockConditions.Timelock {
   139  			continue
   140  		}
   141  
   142  		// Add a siacoin input for this output.
   143  		sci := types.SiacoinInput{
   144  			ParentID:         scoid,
   145  			UnlockConditions: outputUnlockConditions,
   146  		}
   147  		parentTxn.SiacoinInputs = append(parentTxn.SiacoinInputs, sci)
   148  		spentScoids = append(spentScoids, scoid)
   149  
   150  		// Add the output to the total fund
   151  		fund = fund.Add(sco.Value)
   152  		potentialFund = potentialFund.Add(sco.Value)
   153  		if fund.Cmp(amount) >= 0 {
   154  			break
   155  		}
   156  	}
   157  	if potentialFund.Cmp(amount) >= 0 && fund.Cmp(amount) < 0 {
   158  		return modules.ErrPotentialDoubleSpend
   159  	}
   160  	if fund.Cmp(amount) < 0 {
   161  		return modules.ErrLowBalance
   162  	}
   163  
   164  	// Create and add the output that will be used to fund the standard
   165  	// transaction.
   166  	parentUnlockConditions, err := tb.wallet.nextPrimarySeedAddress()
   167  	if err != nil {
   168  		return err
   169  	}
   170  	exactOutput := types.SiacoinOutput{
   171  		Value:      amount,
   172  		UnlockHash: parentUnlockConditions.UnlockHash(),
   173  	}
   174  	parentTxn.SiacoinOutputs = append(parentTxn.SiacoinOutputs, exactOutput)
   175  
   176  	// Create a refund output if needed.
   177  	if amount.Cmp(fund) != 0 {
   178  		refundUnlockConditions, err := tb.wallet.nextPrimarySeedAddress()
   179  		if err != nil {
   180  			return err
   181  		}
   182  		refundOutput := types.SiacoinOutput{
   183  			Value:      fund.Sub(amount),
   184  			UnlockHash: refundUnlockConditions.UnlockHash(),
   185  		}
   186  		parentTxn.SiacoinOutputs = append(parentTxn.SiacoinOutputs, refundOutput)
   187  	}
   188  
   189  	// Sign all of the inputs to the parent trancstion.
   190  	for _, sci := range parentTxn.SiacoinInputs {
   191  		_, err := addSignatures(&parentTxn, types.FullCoveredFields, sci.UnlockConditions, crypto.Hash(sci.ParentID), tb.wallet.keys[sci.UnlockConditions.UnlockHash()])
   192  		if err != nil {
   193  			return err
   194  		}
   195  	}
   196  	// Mark the parent output as spent. Must be done after the transaction is
   197  	// finished because otherwise the txid and output id will change.
   198  	tb.wallet.spentOutputs[types.OutputID(parentTxn.SiacoinOutputID(0))] = tb.wallet.consensusSetHeight
   199  
   200  	// Add the exact output.
   201  	newInput := types.SiacoinInput{
   202  		ParentID:         parentTxn.SiacoinOutputID(0),
   203  		UnlockConditions: parentUnlockConditions,
   204  	}
   205  	tb.newParents = append(tb.newParents, len(tb.parents))
   206  	tb.parents = append(tb.parents, parentTxn)
   207  	tb.siacoinInputs = append(tb.siacoinInputs, len(tb.transaction.SiacoinInputs))
   208  	tb.transaction.SiacoinInputs = append(tb.transaction.SiacoinInputs, newInput)
   209  
   210  	// Mark all outputs that were spent as spent.
   211  	for _, scoid := range spentScoids {
   212  		tb.wallet.spentOutputs[types.OutputID(scoid)] = tb.wallet.consensusSetHeight
   213  	}
   214  	return nil
   215  }
   216  
   217  // FundSiafunds will add a siafund input of exaclty 'amount' to the
   218  // transaction. A parent transaction may be needed to achieve an input with the
   219  // correct value. The siafund input will not be signed until 'Sign' is called
   220  // on the transaction builder.
   221  func (tb *transactionBuilder) FundSiafunds(amount types.Currency) error {
   222  	tb.wallet.mu.Lock()
   223  	defer tb.wallet.mu.Unlock()
   224  
   225  	// Create and fund a parent transaction that will add the correct amount of
   226  	// siafunds to the transaction.
   227  	var fund types.Currency
   228  	var potentialFund types.Currency
   229  	parentTxn := types.Transaction{}
   230  	var spentSfoids []types.SiafundOutputID
   231  	for sfoid, sfo := range tb.wallet.siafundOutputs {
   232  		// Check that this output has not recently been spent by the wallet.
   233  		spendHeight := tb.wallet.spentOutputs[types.OutputID(sfoid)]
   234  		// Prevent an underflow error.
   235  		allowedHeight := tb.wallet.consensusSetHeight - RespendTimeout
   236  		if tb.wallet.consensusSetHeight < RespendTimeout {
   237  			allowedHeight = 0
   238  		}
   239  		if spendHeight > allowedHeight {
   240  			potentialFund = potentialFund.Add(sfo.Value)
   241  			continue
   242  		}
   243  		outputUnlockConditions := tb.wallet.keys[sfo.UnlockHash].UnlockConditions
   244  		if tb.wallet.consensusSetHeight < outputUnlockConditions.Timelock {
   245  			continue
   246  		}
   247  
   248  		// Add a siafund input for this output.
   249  		parentClaimUnlockConditions, err := tb.wallet.nextPrimarySeedAddress()
   250  		if err != nil {
   251  			return err
   252  		}
   253  		sfi := types.SiafundInput{
   254  			ParentID:         sfoid,
   255  			UnlockConditions: outputUnlockConditions,
   256  			ClaimUnlockHash:  parentClaimUnlockConditions.UnlockHash(),
   257  		}
   258  		parentTxn.SiafundInputs = append(parentTxn.SiafundInputs, sfi)
   259  		spentSfoids = append(spentSfoids, sfoid)
   260  
   261  		// Add the output to the total fund
   262  		fund = fund.Add(sfo.Value)
   263  		potentialFund = potentialFund.Add(sfo.Value)
   264  		if fund.Cmp(amount) >= 0 {
   265  			break
   266  		}
   267  	}
   268  	if potentialFund.Cmp(amount) >= 0 && fund.Cmp(amount) < 0 {
   269  		return modules.ErrPotentialDoubleSpend
   270  	}
   271  	if fund.Cmp(amount) < 0 {
   272  		return modules.ErrLowBalance
   273  	}
   274  
   275  	// Create and add the output that will be used to fund the standard
   276  	// transaction.
   277  	parentUnlockConditions, err := tb.wallet.nextPrimarySeedAddress()
   278  	if err != nil {
   279  		return err
   280  	}
   281  	exactOutput := types.SiafundOutput{
   282  		Value:      amount,
   283  		UnlockHash: parentUnlockConditions.UnlockHash(),
   284  	}
   285  	parentTxn.SiafundOutputs = append(parentTxn.SiafundOutputs, exactOutput)
   286  
   287  	// Create a refund output if needed.
   288  	if amount.Cmp(fund) != 0 {
   289  		refundUnlockConditions, err := tb.wallet.nextPrimarySeedAddress()
   290  		if err != nil {
   291  			return err
   292  		}
   293  		refundOutput := types.SiafundOutput{
   294  			Value:      fund.Sub(amount),
   295  			UnlockHash: refundUnlockConditions.UnlockHash(),
   296  		}
   297  		parentTxn.SiafundOutputs = append(parentTxn.SiafundOutputs, refundOutput)
   298  	}
   299  
   300  	// Sign all of the inputs to the parent trancstion.
   301  	for _, sfi := range parentTxn.SiafundInputs {
   302  		_, err := addSignatures(&parentTxn, types.FullCoveredFields, sfi.UnlockConditions, crypto.Hash(sfi.ParentID), tb.wallet.keys[sfi.UnlockConditions.UnlockHash()])
   303  		if err != nil {
   304  			return err
   305  		}
   306  	}
   307  
   308  	// Add the exact output.
   309  	claimUnlockConditions, err := tb.wallet.nextPrimarySeedAddress()
   310  	if err != nil {
   311  		return err
   312  	}
   313  	newInput := types.SiafundInput{
   314  		ParentID:         parentTxn.SiafundOutputID(0),
   315  		UnlockConditions: parentUnlockConditions,
   316  		ClaimUnlockHash:  claimUnlockConditions.UnlockHash(),
   317  	}
   318  	tb.newParents = append(tb.newParents, len(tb.parents))
   319  	tb.parents = append(tb.parents, parentTxn)
   320  	tb.siafundInputs = append(tb.siafundInputs, len(tb.transaction.SiafundInputs))
   321  	tb.transaction.SiafundInputs = append(tb.transaction.SiafundInputs, newInput)
   322  
   323  	// Mark all outputs that were spent as spent.
   324  	for _, sfoid := range spentSfoids {
   325  		tb.wallet.spentOutputs[types.OutputID(sfoid)] = tb.wallet.consensusSetHeight
   326  	}
   327  	return nil
   328  }
   329  
   330  // AddParents adds a set of parents to the transaction.
   331  func (tb *transactionBuilder) AddParents(newParents []types.Transaction) {
   332  	tb.parents = append(tb.parents, newParents...)
   333  }
   334  
   335  // AddMinerFee adds a miner fee to the transaction, returning the index of the
   336  // miner fee within the transaction.
   337  func (tb *transactionBuilder) AddMinerFee(fee types.Currency) uint64 {
   338  	tb.transaction.MinerFees = append(tb.transaction.MinerFees, fee)
   339  	return uint64(len(tb.transaction.MinerFees) - 1)
   340  }
   341  
   342  // AddSiacoinInput adds a siacoin input to the transaction, returning the index
   343  // of the siacoin input within the transaction. When 'Sign' gets called, this
   344  // input will be left unsigned.
   345  func (tb *transactionBuilder) AddSiacoinInput(input types.SiacoinInput) uint64 {
   346  	tb.transaction.SiacoinInputs = append(tb.transaction.SiacoinInputs, input)
   347  	return uint64(len(tb.transaction.SiacoinInputs) - 1)
   348  }
   349  
   350  // AddSiacoinOutput adds a siacoin output to the transaction, returning the
   351  // index of the siacoin output within the transaction.
   352  func (tb *transactionBuilder) AddSiacoinOutput(output types.SiacoinOutput) uint64 {
   353  	tb.transaction.SiacoinOutputs = append(tb.transaction.SiacoinOutputs, output)
   354  	return uint64(len(tb.transaction.SiacoinOutputs) - 1)
   355  }
   356  
   357  // AddFileContract adds a file contract to the transaction, returning the index
   358  // of the file contract within the transaction.
   359  func (tb *transactionBuilder) AddFileContract(fc types.FileContract) uint64 {
   360  	tb.transaction.FileContracts = append(tb.transaction.FileContracts, fc)
   361  	return uint64(len(tb.transaction.FileContracts) - 1)
   362  }
   363  
   364  // AddFileContractRevision adds a file contract revision to the transaction,
   365  // returning the index of the file contract revision within the transaction.
   366  // When 'Sign' gets called, this revision will be left unsigned.
   367  func (tb *transactionBuilder) AddFileContractRevision(fcr types.FileContractRevision) uint64 {
   368  	tb.transaction.FileContractRevisions = append(tb.transaction.FileContractRevisions, fcr)
   369  	return uint64(len(tb.transaction.FileContractRevisions) - 1)
   370  }
   371  
   372  // AddStorageProof adds a storage proof to the transaction, returning the index
   373  // of the storage proof within the transaction.
   374  func (tb *transactionBuilder) AddStorageProof(sp types.StorageProof) uint64 {
   375  	tb.transaction.StorageProofs = append(tb.transaction.StorageProofs, sp)
   376  	return uint64(len(tb.transaction.StorageProofs) - 1)
   377  }
   378  
   379  // AddSiafundInput adds a siafund input to the transaction, returning the index
   380  // of the siafund input within the transaction. When 'Sign' is called, this
   381  // input will be left unsigned.
   382  func (tb *transactionBuilder) AddSiafundInput(input types.SiafundInput) uint64 {
   383  	tb.transaction.SiafundInputs = append(tb.transaction.SiafundInputs, input)
   384  	return uint64(len(tb.transaction.SiafundInputs) - 1)
   385  }
   386  
   387  // AddSiafundOutput adds a siafund output to the transaction, returning the
   388  // index of the siafund output within the transaction.
   389  func (tb *transactionBuilder) AddSiafundOutput(output types.SiafundOutput) uint64 {
   390  	tb.transaction.SiafundOutputs = append(tb.transaction.SiafundOutputs, output)
   391  	return uint64(len(tb.transaction.SiafundOutputs) - 1)
   392  }
   393  
   394  // AddArbitraryData adds arbitrary data to the transaction, returning the index
   395  // of the data within the transaction.
   396  func (tb *transactionBuilder) AddArbitraryData(arb []byte) uint64 {
   397  	tb.transaction.ArbitraryData = append(tb.transaction.ArbitraryData, arb)
   398  	return uint64(len(tb.transaction.ArbitraryData) - 1)
   399  }
   400  
   401  // AddTransactionSignature adds a transaction signature to the transaction,
   402  // returning the index of the signature within the transaction. The signature
   403  // should already be valid, and shouldn't sign any of the inputs that were
   404  // added by calling 'FundSiacoins' or 'FundSiafunds'.
   405  func (tb *transactionBuilder) AddTransactionSignature(sig types.TransactionSignature) uint64 {
   406  	tb.transaction.TransactionSignatures = append(tb.transaction.TransactionSignatures, sig)
   407  	return uint64(len(tb.transaction.TransactionSignatures) - 1)
   408  }
   409  
   410  // Drop discards all of the outputs in a transaction, returning them to the
   411  // pool so that other transactions may use them. 'Drop' should only be called
   412  // if a transaction is both unsigned and will not be used any further.
   413  func (tb *transactionBuilder) Drop() {
   414  	tb.wallet.mu.Lock()
   415  	defer tb.wallet.mu.Unlock()
   416  
   417  	// Iterate through all parents and the transaction itself and restore all
   418  	// outputs to the list of available outputs.
   419  	txns := append(tb.parents, tb.transaction)
   420  	for _, txn := range txns {
   421  		for _, sci := range txn.SiacoinInputs {
   422  			delete(tb.wallet.spentOutputs, types.OutputID(sci.ParentID))
   423  		}
   424  	}
   425  
   426  	tb.parents = nil
   427  	tb.signed = false
   428  	tb.transaction = types.Transaction{}
   429  
   430  	tb.newParents = nil
   431  	tb.siacoinInputs = nil
   432  	tb.siafundInputs = nil
   433  	tb.transactionSignatures = nil
   434  }
   435  
   436  // Sign will sign any inputs added by 'FundSiacoins' or 'FundSiafunds' and
   437  // return a transaction set that contains all parents prepended to the
   438  // transaction. If more fields need to be added, a new transaction builder will
   439  // need to be created.
   440  //
   441  // If the whole transaction flag is set to true, then the whole transaction
   442  // flag will be set in the covered fields object. If the whole transaction flag
   443  // is set to false, then the covered fields object will cover all fields that
   444  // have already been added to the transaction, but will also leave room for
   445  // more fields to be added.
   446  //
   447  // Sign should not be called more than once. If, for some reason, there is an
   448  // error while calling Sign, the builder should be dropped.
   449  func (tb *transactionBuilder) Sign(wholeTransaction bool) ([]types.Transaction, error) {
   450  	if tb.signed {
   451  		return nil, errBuilderAlreadySigned
   452  	}
   453  
   454  	// Create the coveredfields struct.
   455  	var coveredFields types.CoveredFields
   456  	if wholeTransaction {
   457  		coveredFields = types.CoveredFields{WholeTransaction: true}
   458  	} else {
   459  		for i := range tb.transaction.MinerFees {
   460  			coveredFields.MinerFees = append(coveredFields.MinerFees, uint64(i))
   461  		}
   462  		for i := range tb.transaction.SiacoinInputs {
   463  			coveredFields.SiacoinInputs = append(coveredFields.SiacoinInputs, uint64(i))
   464  		}
   465  		for i := range tb.transaction.SiacoinOutputs {
   466  			coveredFields.SiacoinOutputs = append(coveredFields.SiacoinOutputs, uint64(i))
   467  		}
   468  		for i := range tb.transaction.FileContracts {
   469  			coveredFields.FileContracts = append(coveredFields.FileContracts, uint64(i))
   470  		}
   471  		for i := range tb.transaction.FileContractRevisions {
   472  			coveredFields.FileContractRevisions = append(coveredFields.FileContractRevisions, uint64(i))
   473  		}
   474  		for i := range tb.transaction.StorageProofs {
   475  			coveredFields.StorageProofs = append(coveredFields.StorageProofs, uint64(i))
   476  		}
   477  		for i := range tb.transaction.SiafundInputs {
   478  			coveredFields.SiafundInputs = append(coveredFields.SiafundInputs, uint64(i))
   479  		}
   480  		for i := range tb.transaction.SiafundOutputs {
   481  			coveredFields.SiafundOutputs = append(coveredFields.SiafundOutputs, uint64(i))
   482  		}
   483  		for i := range tb.transaction.ArbitraryData {
   484  			coveredFields.ArbitraryData = append(coveredFields.ArbitraryData, uint64(i))
   485  		}
   486  	}
   487  	// TransactionSignatures don't get covered by the 'WholeTransaction' flag,
   488  	// and must be covered manually.
   489  	for i := range tb.transaction.TransactionSignatures {
   490  		coveredFields.TransactionSignatures = append(coveredFields.TransactionSignatures, uint64(i))
   491  	}
   492  
   493  	// For each siacoin input in the transaction that we added, provide a
   494  	// signature.
   495  	tb.wallet.mu.Lock()
   496  	defer tb.wallet.mu.Unlock()
   497  	for _, inputIndex := range tb.siacoinInputs {
   498  		input := tb.transaction.SiacoinInputs[inputIndex]
   499  		key := tb.wallet.keys[input.UnlockConditions.UnlockHash()]
   500  		newSigIndices, err := addSignatures(&tb.transaction, coveredFields, input.UnlockConditions, crypto.Hash(input.ParentID), key)
   501  		if err != nil {
   502  			return nil, err
   503  		}
   504  		tb.transactionSignatures = append(tb.transactionSignatures, newSigIndices...)
   505  		tb.signed = true // Signed is set to true after one successful signature to indicate that future signings can cause issues.
   506  	}
   507  	for _, inputIndex := range tb.siafundInputs {
   508  		input := tb.transaction.SiafundInputs[inputIndex]
   509  		key := tb.wallet.keys[input.UnlockConditions.UnlockHash()]
   510  		newSigIndices, err := addSignatures(&tb.transaction, coveredFields, input.UnlockConditions, crypto.Hash(input.ParentID), key)
   511  		if err != nil {
   512  			return nil, err
   513  		}
   514  		tb.transactionSignatures = append(tb.transactionSignatures, newSigIndices...)
   515  		tb.signed = true // Signed is set to true after one successful signature to indicate that future signings can cause issues.
   516  	}
   517  
   518  	// Get the transaction set and delete the transaction from the registry.
   519  	txnSet := append(tb.parents, tb.transaction)
   520  	return txnSet, nil
   521  }
   522  
   523  // ViewTransaction returns a transaction-in-progress along with all of its
   524  // parents, specified by id. An error is returned if the id is invalid.  Note
   525  // that ids become invalid for a transaction after 'SignTransaction' has been
   526  // called because the transaction gets deleted.
   527  func (tb *transactionBuilder) View() (types.Transaction, []types.Transaction) {
   528  	return tb.transaction, tb.parents
   529  }
   530  
   531  // ViewAdded returns all of the siacoin inputs, siafund inputs, and parent
   532  // transactions that have been automatically added by the builder.
   533  func (tb *transactionBuilder) ViewAdded() (newParents, siacoinInputs, siafundInputs, transactionSignatures []int) {
   534  	return tb.newParents, tb.siacoinInputs, tb.siafundInputs, tb.transactionSignatures
   535  }
   536  
   537  // RegisterTransaction takes a transaction and its parents and returns a
   538  // TransactionBuilder which can be used to expand the transaction. The most
   539  // typical call is 'RegisterTransaction(types.Transaction{}, nil)', which
   540  // registers a new transaction without parents.
   541  func (w *Wallet) RegisterTransaction(t types.Transaction, parents []types.Transaction) modules.TransactionBuilder {
   542  	// Create a deep copy of the transaction and parents by encoding them. A
   543  	// deep copy ensures that there are no pointer or slice related errors -
   544  	// the builder will be working directly on the transaction, and the
   545  	// transaction may be in use elsewhere (in this case, the host is using the
   546  	// transaction.
   547  	pBytes := encoding.Marshal(parents)
   548  	var pCopy []types.Transaction
   549  	err := encoding.Unmarshal(pBytes, &pCopy)
   550  	if err != nil {
   551  		panic(err)
   552  	}
   553  	tBytes := encoding.Marshal(t)
   554  	var tCopy types.Transaction
   555  	err = encoding.Unmarshal(tBytes, &tCopy)
   556  	if err != nil {
   557  		panic(err)
   558  	}
   559  	return &transactionBuilder{
   560  		parents:     pCopy,
   561  		transaction: tCopy,
   562  
   563  		wallet: w,
   564  	}
   565  }
   566  
   567  // StartTransaction is a convenience function that calls
   568  // RegisterTransaction(types.Transaction{}, nil).
   569  func (w *Wallet) StartTransaction() modules.TransactionBuilder {
   570  	return w.RegisterTransaction(types.Transaction{}, nil)
   571  }