Beispiel #1
0
// TestApplySiacoinOutputs probes the applySiacoinOutput method of the
// consensus set.
func TestApplySiacoinOutputs(t *testing.T) {
	if testing.Short() {
		t.SkipNow()
	}
	cst, err := createConsensusSetTester("TestApplySiacoinOutputs")
	if err != nil {
		t.Fatal(err)
	}

	// Create a block node to use with application.
	bn := new(blockNode)

	// Apply a transaction with a single siacoin output.
	txn := types.Transaction{
		SiacoinOutputs: []types.SiacoinOutput{{}},
	}
	cst.cs.applySiacoinOutputs(bn, txn)
	scoid := txn.SiacoinOutputID(0)
	_, exists := cst.cs.siacoinOutputs[scoid]
	if !exists {
		t.Error("Failed to create siacoin output")
	}
	if len(cst.cs.siacoinOutputs) != 3 { // 3 because createConsensusSetTester has 2 initially.
		t.Error("siacoin outputs not correctly updated")
	}
	if len(bn.siacoinOutputDiffs) != 1 {
		t.Error("block node was not updated for single element transaction")
	}
	if bn.siacoinOutputDiffs[0].Direction != modules.DiffApply {
		t.Error("wrong diff direction applied when creating a siacoin output")
	}
	if bn.siacoinOutputDiffs[0].ID != scoid {
		t.Error("wrong id used when creating a siacoin output")
	}

	// Apply a transaction with 2 siacoin outputs.
	txn = types.Transaction{
		SiacoinOutputs: []types.SiacoinOutput{
			{Value: types.NewCurrency64(1)},
			{Value: types.NewCurrency64(2)},
		},
	}
	cst.cs.applySiacoinOutputs(bn, txn)
	scoid0 := txn.SiacoinOutputID(0)
	scoid1 := txn.SiacoinOutputID(1)
	_, exists = cst.cs.siacoinOutputs[scoid0]
	if !exists {
		t.Error("Failed to create siacoin output")
	}
	_, exists = cst.cs.siacoinOutputs[scoid1]
	if !exists {
		t.Error("Failed to create siacoin output")
	}
	if len(cst.cs.siacoinOutputs) != 5 { // 5 because createConsensusSetTester has 2 initially.
		t.Error("siacoin outputs not correctly updated")
	}
	if len(bn.siacoinOutputDiffs) != 3 {
		t.Error("block node was not updated correctly")
	}
}
Beispiel #2
0
// applySiacoinOutputs takes all of the siacoin outputs in a transaction and
// applies them to the state, updating the diffs in the processed block.
func applySiacoinOutputs(tx *bolt.Tx, pb *processedBlock, t types.Transaction) {
	// Add all siacoin outputs to the unspent siacoin outputs list.
	for i, sco := range t.SiacoinOutputs {
		scoid := t.SiacoinOutputID(uint64(i))
		scod := modules.SiacoinOutputDiff{
			Direction:     modules.DiffApply,
			ID:            scoid,
			SiacoinOutput: sco,
		}
		pb.SiacoinOutputDiffs = append(pb.SiacoinOutputDiffs, scod)
		commitSiacoinOutputDiff(tx, scod, modules.DiffApply)
	}
}
Beispiel #3
0
Datei: update.go Projekt: mm3/Sia
// removeSiacoinOutputs removes all of the siacoin outputs of a transaction
// from the unconfirmed consensus set.
func (tp *TransactionPool) removeSiacoinOutputs(t types.Transaction) {
	for i, _ := range t.SiacoinOutputs {
		scoid := t.SiacoinOutputID(i)
		// Sanity check - the output should exist in the unconfirmed set as
		// there should be no transaction dependents who have spent the output.
		if build.DEBUG {
			_, exists := tp.siacoinOutputs[scoid]
			if !exists {
				panic("trying to delete missing siacoin output")
			}
		}

		delete(tp.siacoinOutputs, scoid)
	}
}
Beispiel #4
0
// applySiacoinOutputs takes all of the siacoin outputs in a transaction and
// applies them to the state, updating the diffs in the processed block.
func (cs *ConsensusSet) applySiacoinOutputs(tx *bolt.Tx, pb *processedBlock, t types.Transaction) error {
	// Add all siacoin outputs to the unspent siacoin outputs list.
	scoBucket := tx.Bucket(SiacoinOutputs)
	for i, sco := range t.SiacoinOutputs {
		scoid := t.SiacoinOutputID(i)
		scod := modules.SiacoinOutputDiff{
			Direction:     modules.DiffApply,
			ID:            scoid,
			SiacoinOutput: sco,
		}
		pb.SiacoinOutputDiffs = append(pb.SiacoinOutputDiffs, scod)
		err := cs.commitBucketSiacoinOutputDiff(scoBucket, scod, modules.DiffApply)
		if err != nil {
			return err
		}
	}
	return nil
}
Beispiel #5
0
// applySiacoinOutputs takes all of the siacoin outputs in a transaction and
// applies them to the state, updating the diffs in the block node.
func (cs *State) applySiacoinOutputs(bn *blockNode, t types.Transaction) {
	// Add all siacoin outputs to the unspent siacoin outputs list.
	for i, sco := range t.SiacoinOutputs {
		// Sanity check - the output should not exist within the state.
		scoid := t.SiacoinOutputID(i)
		if build.DEBUG {
			_, exists := cs.siacoinOutputs[scoid]
			if exists {
				panic(ErrMisuseApplySiacoinOutput)
			}
		}

		scod := modules.SiacoinOutputDiff{
			Direction:     modules.DiffApply,
			ID:            scoid,
			SiacoinOutput: sco,
		}
		bn.siacoinOutputDiffs = append(bn.siacoinOutputDiffs, scod)
		cs.commitSiacoinOutputDiff(scod, modules.DiffApply)
	}
}
Beispiel #6
0
// TestPartialConfirmationWeave checks that the transaction pool correctly
// accepts a transaction set which has parents that have been accepted by the
// consensus set but not the whole set has been accepted by the consensus set,
// this time weaving the dependencies, such that the first transaction is not
// in the consensus set, the second is, and the third has both as dependencies.
func TestPartialConfirmationWeave(t *testing.T) {
	if testing.Short() {
		t.SkipNow()
	}
	tpt, err := createTpoolTester("TestPartialConfirmation")
	if err != nil {
		t.Fatal(err)
	}
	defer tpt.Close()

	// Step 1: create an output to the empty address in a tx.
	// Step 2: create a second output to the empty address in another tx.
	// Step 3: create a transaction using both those outputs.
	// Step 4: mine the txn set in step 2
	// Step 5: Submit the complete set.

	// Create a transaction with a single output to a fully controlled address.
	emptyUH := types.UnlockConditions{}.UnlockHash()
	builder1 := tpt.wallet.StartTransaction()
	funding1 := types.NewCurrency64(1e9)
	err = builder1.FundSiacoins(funding1)
	if err != nil {
		t.Fatal(err)
	}
	scOutput1 := types.SiacoinOutput{
		Value:      funding1,
		UnlockHash: emptyUH,
	}
	i1 := builder1.AddSiacoinOutput(scOutput1)
	tSet1, err := builder1.Sign(true)
	if err != nil {
		t.Fatal(err)
	}
	// Submit to the transaction pool and mine the block, to minimize
	// complexity.
	err = tpt.tpool.AcceptTransactionSet(tSet1)
	if err != nil {
		t.Fatal(err)
	}
	_, err = tpt.miner.AddBlock()
	if err != nil {
		t.Fatal(err)
	}

	// Create a second output to the fully controlled address, to fund the
	// second transaction in the weave.
	builder2 := tpt.wallet.StartTransaction()
	funding2 := types.NewCurrency64(2e9)
	err = builder2.FundSiacoins(funding2)
	if err != nil {
		t.Fatal(err)
	}
	scOutput2 := types.SiacoinOutput{
		Value:      funding2,
		UnlockHash: emptyUH,
	}
	i2 := builder2.AddSiacoinOutput(scOutput2)
	tSet2, err := builder2.Sign(true)
	if err != nil {
		t.Fatal(err)
	}
	// Submit to the transaction pool and mine the block, to minimize
	// complexity.
	err = tpt.tpool.AcceptTransactionSet(tSet2)
	if err != nil {
		t.Fatal(err)
	}
	_, err = tpt.miner.AddBlock()
	if err != nil {
		t.Fatal(err)
	}

	// Create a passthrough transaction for output1 and output2, so that they
	// can be used as unconfirmed dependencies.
	txn1 := types.Transaction{
		SiacoinInputs: []types.SiacoinInput{{
			ParentID: tSet1[len(tSet1)-1].SiacoinOutputID(i1),
		}},
		SiacoinOutputs: []types.SiacoinOutput{{
			Value:      funding1,
			UnlockHash: emptyUH,
		}},
	}
	txn2 := types.Transaction{
		SiacoinInputs: []types.SiacoinInput{{
			ParentID: tSet2[len(tSet2)-1].SiacoinOutputID(i2),
		}},
		SiacoinOutputs: []types.SiacoinOutput{{
			Value:      funding2,
			UnlockHash: emptyUH,
		}},
	}

	// Create a child transaction that depends on inputs from both txn1 and
	// txn2.
	child := types.Transaction{
		SiacoinInputs: []types.SiacoinInput{
			{
				ParentID: txn1.SiacoinOutputID(0),
			},
			{
				ParentID: txn2.SiacoinOutputID(0),
			},
		},
		SiacoinOutputs: []types.SiacoinOutput{{
			Value: funding1.Add(funding2),
		}},
	}

	// Get txn2 accepted into the consensus set.
	err = tpt.tpool.AcceptTransactionSet([]types.Transaction{txn2})
	if err != nil {
		t.Fatal(err)
	}
	_, err = tpt.miner.AddBlock()
	if err != nil {
		t.Fatal(err)
	}

	// Try to get the set of txn1, txn2, and child accepted into the
	// transaction pool.
	err = tpt.tpool.AcceptTransactionSet([]types.Transaction{txn1, txn2, child})
	if err != nil {
		t.Fatal(err)
	}
}
Beispiel #7
0
// FundSiacoins will add a siacoin input of exaclty 'amount' to the
// transaction. A parent transaction may be needed to achieve an input with the
// correct value. The siacoin input will not be signed until 'Sign' is called
// on the transaction builder.
func (tb *transactionBuilder) FundSiacoins(amount types.Currency) error {
	tb.wallet.mu.Lock()
	defer tb.wallet.mu.Unlock()

	// Collect a value-sorted set of siacoin outputs.
	var so sortedOutputs
	for scoid, sco := range tb.wallet.siacoinOutputs {
		so.ids = append(so.ids, scoid)
		so.outputs = append(so.outputs, sco)
	}
	// Add all of the unconfirmed outputs as well.
	for _, upt := range tb.wallet.unconfirmedProcessedTransactions {
		for i, sco := range upt.Transaction.SiacoinOutputs {
			// Determine if the output belongs to the wallet.
			_, exists := tb.wallet.keys[sco.UnlockHash]
			if !exists {
				continue
			}
			so.ids = append(so.ids, upt.Transaction.SiacoinOutputID(uint64(i)))
			so.outputs = append(so.outputs, sco)
		}
	}
	sort.Sort(sort.Reverse(so))

	// Create and fund a parent transaction that will add the correct amount of
	// siacoins to the transaction.
	var fund types.Currency
	// potentialFund tracks the balance of the wallet including outputs that
	// have been spent in other unconfirmed transactions recently. This is to
	// provide the user with a more useful error message in the event that they
	// are overspending.
	var potentialFund types.Currency
	parentTxn := types.Transaction{}
	var spentScoids []types.SiacoinOutputID
	for i := range so.ids {
		scoid := so.ids[i]
		sco := so.outputs[i]
		// Check that this output has not recently been spent by the wallet.
		spendHeight := tb.wallet.spentOutputs[types.OutputID(scoid)]
		// Prevent an underflow error.
		allowedHeight := tb.wallet.consensusSetHeight - RespendTimeout
		if tb.wallet.consensusSetHeight < RespendTimeout {
			allowedHeight = 0
		}
		if spendHeight > allowedHeight {
			potentialFund = potentialFund.Add(sco.Value)
			continue
		}
		outputUnlockConditions := tb.wallet.keys[sco.UnlockHash].UnlockConditions
		if tb.wallet.consensusSetHeight < outputUnlockConditions.Timelock {
			continue
		}

		// Add a siacoin input for this output.
		sci := types.SiacoinInput{
			ParentID:         scoid,
			UnlockConditions: outputUnlockConditions,
		}
		parentTxn.SiacoinInputs = append(parentTxn.SiacoinInputs, sci)
		spentScoids = append(spentScoids, scoid)

		// Add the output to the total fund
		fund = fund.Add(sco.Value)
		potentialFund = potentialFund.Add(sco.Value)
		if fund.Cmp(amount) >= 0 {
			break
		}
	}
	if potentialFund.Cmp(amount) >= 0 && fund.Cmp(amount) < 0 {
		return modules.ErrPotentialDoubleSpend
	}
	if fund.Cmp(amount) < 0 {
		return modules.ErrLowBalance
	}

	// Create and add the output that will be used to fund the standard
	// transaction.
	parentUnlockConditions, err := tb.wallet.nextPrimarySeedAddress()
	if err != nil {
		return err
	}
	exactOutput := types.SiacoinOutput{
		Value:      amount,
		UnlockHash: parentUnlockConditions.UnlockHash(),
	}
	parentTxn.SiacoinOutputs = append(parentTxn.SiacoinOutputs, exactOutput)

	// Create a refund output if needed.
	if amount.Cmp(fund) != 0 {
		refundUnlockConditions, err := tb.wallet.nextPrimarySeedAddress()
		if err != nil {
			return err
		}
		refundOutput := types.SiacoinOutput{
			Value:      fund.Sub(amount),
			UnlockHash: refundUnlockConditions.UnlockHash(),
		}
		parentTxn.SiacoinOutputs = append(parentTxn.SiacoinOutputs, refundOutput)
	}

	// Sign all of the inputs to the parent trancstion.
	for _, sci := range parentTxn.SiacoinInputs {
		_, err := addSignatures(&parentTxn, types.FullCoveredFields, sci.UnlockConditions, crypto.Hash(sci.ParentID), tb.wallet.keys[sci.UnlockConditions.UnlockHash()])
		if err != nil {
			return err
		}
	}
	// Mark the parent output as spent. Must be done after the transaction is
	// finished because otherwise the txid and output id will change.
	tb.wallet.spentOutputs[types.OutputID(parentTxn.SiacoinOutputID(0))] = tb.wallet.consensusSetHeight

	// Add the exact output.
	newInput := types.SiacoinInput{
		ParentID:         parentTxn.SiacoinOutputID(0),
		UnlockConditions: parentUnlockConditions,
	}
	tb.newParents = append(tb.newParents, len(tb.parents))
	tb.parents = append(tb.parents, parentTxn)
	tb.siacoinInputs = append(tb.siacoinInputs, len(tb.transaction.SiacoinInputs))
	tb.transaction.SiacoinInputs = append(tb.transaction.SiacoinInputs, newInput)

	// Mark all outputs that were spent as spent.
	for _, scoid := range spentScoids {
		tb.wallet.spentOutputs[types.OutputID(scoid)] = tb.wallet.consensusSetHeight
	}
	return nil
}
Beispiel #8
0
// buildExplorerTransaction takes a transaction and the height + id of the
// block it appears in an uses that to build an explorer transaction.
func (srv *Server) buildExplorerTransaction(height types.BlockHeight, parent types.BlockID, txn types.Transaction) (et ExplorerTransaction) {
	// Get the header information for the transaction.
	et.ID = txn.ID()
	et.Height = height
	et.Parent = parent
	et.RawTransaction = txn

	// Add the siacoin outputs that correspond with each siacoin input.
	for _, sci := range txn.SiacoinInputs {
		sco, exists := srv.explorer.SiacoinOutput(sci.ParentID)
		if build.DEBUG && !exists {
			panic("could not find corresponding siacoin output")
		}
		et.SiacoinInputOutputs = append(et.SiacoinInputOutputs, sco)
	}

	for i := range txn.SiacoinOutputs {
		et.SiacoinOutputIDs = append(et.SiacoinOutputIDs, txn.SiacoinOutputID(uint64(i)))
	}

	// Add all of the valid and missed proof ids as extra data to the file
	// contracts.
	for i, fc := range txn.FileContracts {
		fcid := txn.FileContractID(uint64(i))
		var fcvpoids []types.SiacoinOutputID
		var fcmpoids []types.SiacoinOutputID
		for j := range fc.ValidProofOutputs {
			fcvpoids = append(fcvpoids, fcid.StorageProofOutputID(types.ProofValid, uint64(j)))
		}
		for j := range fc.MissedProofOutputs {
			fcmpoids = append(fcmpoids, fcid.StorageProofOutputID(types.ProofMissed, uint64(j)))
		}
		et.FileContractIDs = append(et.FileContractIDs, fcid)
		et.FileContractValidProofOutputIDs = append(et.FileContractValidProofOutputIDs, fcvpoids)
		et.FileContractMissedProofOutputIDs = append(et.FileContractMissedProofOutputIDs, fcmpoids)
	}

	// Add all of the valid and missed proof ids as extra data to the file
	// contract revisions.
	for _, fcr := range txn.FileContractRevisions {
		var fcrvpoids []types.SiacoinOutputID
		var fcrmpoids []types.SiacoinOutputID
		for j := range fcr.NewValidProofOutputs {
			fcrvpoids = append(fcrvpoids, fcr.ParentID.StorageProofOutputID(types.ProofValid, uint64(j)))
		}
		for j := range fcr.NewMissedProofOutputs {
			fcrmpoids = append(fcrmpoids, fcr.ParentID.StorageProofOutputID(types.ProofMissed, uint64(j)))
		}
		et.FileContractValidProofOutputIDs = append(et.FileContractValidProofOutputIDs, fcrvpoids)
		et.FileContractMissedProofOutputIDs = append(et.FileContractMissedProofOutputIDs, fcrmpoids)
	}

	// Add all of the output ids and outputs corresponding with each storage
	// proof.
	for _, sp := range txn.StorageProofs {
		fileContract, fileContractRevisions, fileContractExists, _ := srv.explorer.FileContractHistory(sp.ParentID)
		if !fileContractExists && build.DEBUG {
			panic("could not find a file contract connected with a storage proof")
		}
		var storageProofOutputs []types.SiacoinOutput
		if len(fileContractRevisions) > 0 {
			storageProofOutputs = fileContractRevisions[len(fileContractRevisions)-1].NewValidProofOutputs
		} else {
			storageProofOutputs = fileContract.ValidProofOutputs
		}
		var storageProofOutputIDs []types.SiacoinOutputID
		for i := range storageProofOutputs {
			storageProofOutputIDs = append(storageProofOutputIDs, sp.ParentID.StorageProofOutputID(types.ProofValid, uint64(i)))
		}
		et.StorageProofOutputIDs = append(et.StorageProofOutputIDs, storageProofOutputIDs)
		et.StorageProofOutputs = append(et.StorageProofOutputs, storageProofOutputs)
	}

	// Add the siafund outputs that correspond to each siacoin input.
	for _, sci := range txn.SiafundInputs {
		sco, exists := srv.explorer.SiafundOutput(sci.ParentID)
		if build.DEBUG && !exists {
			panic("could not find corresponding siafund output")
		}
		et.SiafundInputOutputs = append(et.SiafundInputOutputs, sco)
	}

	for i := range txn.SiafundOutputs {
		et.SiafundOutputIDs = append(et.SiafundOutputIDs, txn.SiafundOutputID(uint64(i)))
	}

	for _, sfi := range txn.SiafundInputs {
		et.SiaClaimOutputIDs = append(et.SiaClaimOutputIDs, sfi.ParentID.SiaClaimOutputID())
	}
	return et
}
Beispiel #9
0
// addTransaction is called from addBlockDB, and delegates the adding
// of information to the database to the functions defined above
func (tx *boltTx) addTransaction(txn types.Transaction) {
	// Store this for quick lookup
	txid := txn.ID()

	// Append each input to the list of modifications
	for _, input := range txn.SiacoinInputs {
		tx.addAddress(input.UnlockConditions.UnlockHash(), txid)
		tx.addSiacoinInput(input.ParentID, txid)
	}

	// Handle all the transaction outputs
	for i, output := range txn.SiacoinOutputs {
		tx.addAddress(output.UnlockHash, txid)
		tx.addNewOutput(txn.SiacoinOutputID(uint64(i)), txid)
	}

	// Handle each file contract individually
	for i, contract := range txn.FileContracts {
		fcid := txn.FileContractID(uint64(i))
		tx.addNewHash("FileContracts", hashFilecontract, crypto.Hash(fcid), fcInfo{
			Contract: txid,
		})

		for j, output := range contract.ValidProofOutputs {
			tx.addAddress(output.UnlockHash, txid)
			tx.addNewOutput(fcid.StorageProofOutputID(true, uint64(j)), txid)
		}
		for j, output := range contract.MissedProofOutputs {
			tx.addAddress(output.UnlockHash, txid)
			tx.addNewOutput(fcid.StorageProofOutputID(false, uint64(j)), txid)
		}

		tx.addAddress(contract.UnlockHash, txid)
	}

	// Update the list of revisions
	for _, revision := range txn.FileContractRevisions {
		tx.addFcRevision(revision.ParentID, txid)

		// Note the old outputs will still be there in the
		// database. This is to provide information to the
		// people who may just need it.
		for i, output := range revision.NewValidProofOutputs {
			tx.addAddress(output.UnlockHash, txid)
			tx.addNewOutput(revision.ParentID.StorageProofOutputID(true, uint64(i)), txid)
		}
		for i, output := range revision.NewMissedProofOutputs {
			tx.addAddress(output.UnlockHash, txid)
			tx.addNewOutput(revision.ParentID.StorageProofOutputID(false, uint64(i)), txid)
		}

		tx.addAddress(revision.NewUnlockHash, txid)
	}

	// Update the list of storage proofs
	for _, proof := range txn.StorageProofs {
		tx.addFcProof(proof.ParentID, txid)
	}

	// Append all the siafund inputs to the modification list
	for _, input := range txn.SiafundInputs {
		tx.addSiafundInput(input.ParentID, txid)
	}

	// Handle all the siafund outputs
	for i, output := range txn.SiafundOutputs {
		tx.addAddress(output.UnlockHash, txid)
		tx.addNewSFOutput(txn.SiafundOutputID(uint64(i)), txid)

	}

	tx.putObject("Hashes", txid, hashTransaction)
}
Beispiel #10
0
// FundTransaction adds siacoins to a transaction that the wallet knows how to
// spend. The exact amount of coins are always added, and this is achieved by
// creating two transactions. The first transaciton, the parent, spends a set
// of outputs that add up to at least the desired amount, and then creates a
// single output of the exact amount and a second refund output.
func (w *Wallet) FundTransaction(id string, amount types.Currency) (t types.Transaction, err error) {
	counter := w.mu.Lock()
	defer w.mu.Unlock(counter)

	// Create a parent transaction and supply it with enough inputs to cover
	// 'amount'.
	parentTxn := types.Transaction{}
	fundingOutputs, fundingTotal, err := w.findOutputs(amount)
	if err != nil {
		return
	}
	for _, output := range fundingOutputs {
		output.age = w.age
		key := w.keys[output.output.UnlockHash]
		newInput := types.SiacoinInput{
			ParentID:         output.id,
			UnlockConditions: key.unlockConditions,
		}
		parentTxn.SiacoinInputs = append(parentTxn.SiacoinInputs, newInput)
	}

	// Create and add the output that will be used to fund the standard
	// transaction.
	parentDest, parentSpendConds, err := w.coinAddress(false) // false indicates that the address should not be visible to the user
	exactOutput := types.SiacoinOutput{
		Value:      amount,
		UnlockHash: parentDest,
	}
	parentTxn.SiacoinOutputs = append(parentTxn.SiacoinOutputs, exactOutput)

	// Create a refund output if needed.
	if amount.Cmp(fundingTotal) != 0 {
		var refundDest types.UnlockHash
		refundDest, _, err = w.coinAddress(false) // false indicates that the address should not be visible to the user
		if err != nil {
			return
		}
		refundOutput := types.SiacoinOutput{
			Value:      fundingTotal.Sub(amount),
			UnlockHash: refundDest,
		}
		parentTxn.SiacoinOutputs = append(parentTxn.SiacoinOutputs, refundOutput)
	}

	// Sign all of the inputs to the parent trancstion.
	coveredFields := types.CoveredFields{WholeTransaction: true}
	for _, input := range parentTxn.SiacoinInputs {
		sig := types.TransactionSignature{
			ParentID:       crypto.Hash(input.ParentID),
			CoveredFields:  coveredFields,
			PublicKeyIndex: 0,
		}
		parentTxn.TransactionSignatures = append(parentTxn.TransactionSignatures, sig)

		// Hash the transaction according to the covered fields.
		coinAddress := input.UnlockConditions.UnlockHash()
		sigIndex := len(parentTxn.TransactionSignatures) - 1
		secKey := w.keys[coinAddress].secretKey
		sigHash := parentTxn.SigHash(sigIndex)

		// Get the signature.
		var encodedSig crypto.Signature
		encodedSig, err = crypto.SignHash(sigHash, secKey)
		if err != nil {
			return
		}
		parentTxn.TransactionSignatures[sigIndex].Signature = types.Signature(encodedSig[:])
	}

	// Add the exact output to the wallet's knowledgebase before releasing the
	// lock, to prevent the wallet from using the exact output elsewhere.
	key := w.keys[parentSpendConds.UnlockHash()]
	key.outputs[parentTxn.SiacoinOutputID(0)] = &knownOutput{
		id:     parentTxn.SiacoinOutputID(0),
		output: exactOutput,

		spendable: true,
		age:       w.age,
	}

	// Send the transaction to the transaction pool.
	err = w.tpool.AcceptTransaction(parentTxn)
	if err != nil {
		return
	}

	// Get the transaction that was originally meant to be funded.
	openTxn, exists := w.transactions[id]
	if !exists {
		err = ErrInvalidID
		return
	}
	txn := openTxn.transaction

	// Add the exact output.
	newInput := types.SiacoinInput{
		ParentID:         parentTxn.SiacoinOutputID(0),
		UnlockConditions: parentSpendConds,
	}
	openTxn.inputs = append(openTxn.inputs, len(txn.SiacoinInputs))
	txn.SiacoinInputs = append(txn.SiacoinInputs, newInput)
	t = *txn
	return
}