// applySiafundOutput applies a siafund output to the consensus set. func applySiafundOutputs(tx *bolt.Tx, pb *processedBlock, t types.Transaction) { for i, sfo := range t.SiafundOutputs { sfoid := t.SiafundOutputID(uint64(i)) sfo.ClaimStart = getSiafundPool(tx) sfod := modules.SiafundOutputDiff{ Direction: modules.DiffApply, ID: sfoid, SiafundOutput: sfo, } pb.SiafundOutputDiffs = append(pb.SiafundOutputDiffs, sfod) commitSiafundOutputDiff(tx, sfod, modules.DiffApply) } }
// removeSiafundOutputs removes all of the siafund outputs of a transaction // from the unconfirmed consensus set. func (tp *TransactionPool) removeSiafundOutputs(t types.Transaction) { for i, _ := range t.SiafundOutputs { // Sanity check - the output should exist in the unconfirmed set as // there is no dependent transaction which could have spent the output. sfoid := t.SiafundOutputID(i) if build.DEBUG { _, exists := tp.siafundOutputs[sfoid] if !exists { panic("trying to remove nonexisting siafund output from unconfirmed set") } } delete(tp.siafundOutputs, sfoid) } }
// applySiafundOutput applies a siafund output to the consensus set. func (cs *ConsensusSet) applySiafundOutputs(tx *bolt.Tx, pb *processedBlock, t types.Transaction) error { for i, sfo := range t.SiafundOutputs { sfoid := t.SiafundOutputID(i) sfo.ClaimStart = getSiafundPool(tx) sfod := modules.SiafundOutputDiff{ Direction: modules.DiffApply, ID: sfoid, SiafundOutput: sfo, } pb.SiafundOutputDiffs = append(pb.SiafundOutputDiffs, sfod) err := cs.commitTxSiafundOutputDiff(tx, sfod, modules.DiffApply) if err != nil { return err } } return nil }
// applySiafundOutputs takes all of the siafund outputs in a transaction and // applies them to the state, updating the diffs in the block node. func (cs *State) applySiafundOutputs(bn *blockNode, t types.Transaction) { for i, sfo := range t.SiafundOutputs { // Sanity check - the output should not exist within the blockchain. sfoid := t.SiafundOutputID(i) if build.DEBUG { _, exists := cs.siafundOutputs[sfoid] if exists { panic(ErrMisuseApplySiafundOutput) } } // Set the claim start. sfo.ClaimStart = cs.siafundPool // Create and apply the diff. sfod := modules.SiafundOutputDiff{ Direction: modules.DiffApply, ID: sfoid, SiafundOutput: sfo, } bn.siafundOutputDiffs = append(bn.siafundOutputDiffs, sfod) cs.commitSiafundOutputDiff(sfod, modules.DiffApply) } }
// FundSiafunds will add a siafund input of exaclty 'amount' to the // transaction. A parent transaction may be needed to achieve an input with the // correct value. The siafund input will not be signed until 'Sign' is called // on the transaction builder. func (tb *transactionBuilder) FundSiafunds(amount types.Currency) error { tb.wallet.mu.Lock() defer tb.wallet.mu.Unlock() // Create and fund a parent transaction that will add the correct amount of // siafunds to the transaction. var fund types.Currency var potentialFund types.Currency parentTxn := types.Transaction{} var spentSfoids []types.SiafundOutputID for sfoid, sfo := range tb.wallet.siafundOutputs { // Check that this output has not recently been spent by the wallet. spendHeight := tb.wallet.spentOutputs[types.OutputID(sfoid)] // Prevent an underflow error. allowedHeight := tb.wallet.consensusSetHeight - RespendTimeout if tb.wallet.consensusSetHeight < RespendTimeout { allowedHeight = 0 } if spendHeight > allowedHeight { potentialFund = potentialFund.Add(sfo.Value) continue } outputUnlockConditions := tb.wallet.keys[sfo.UnlockHash].UnlockConditions if tb.wallet.consensusSetHeight < outputUnlockConditions.Timelock { continue } // Add a siafund input for this output. parentClaimUnlockConditions, err := tb.wallet.nextPrimarySeedAddress() if err != nil { return err } sfi := types.SiafundInput{ ParentID: sfoid, UnlockConditions: outputUnlockConditions, ClaimUnlockHash: parentClaimUnlockConditions.UnlockHash(), } parentTxn.SiafundInputs = append(parentTxn.SiafundInputs, sfi) spentSfoids = append(spentSfoids, sfoid) // Add the output to the total fund fund = fund.Add(sfo.Value) potentialFund = potentialFund.Add(sfo.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.SiafundOutput{ Value: amount, UnlockHash: parentUnlockConditions.UnlockHash(), } parentTxn.SiafundOutputs = append(parentTxn.SiafundOutputs, exactOutput) // Create a refund output if needed. if amount.Cmp(fund) != 0 { refundUnlockConditions, err := tb.wallet.nextPrimarySeedAddress() if err != nil { return err } refundOutput := types.SiafundOutput{ Value: fund.Sub(amount), UnlockHash: refundUnlockConditions.UnlockHash(), } parentTxn.SiafundOutputs = append(parentTxn.SiafundOutputs, refundOutput) } // Sign all of the inputs to the parent trancstion. for _, sfi := range parentTxn.SiafundInputs { _, err := addSignatures(&parentTxn, types.FullCoveredFields, sfi.UnlockConditions, crypto.Hash(sfi.ParentID), tb.wallet.keys[sfi.UnlockConditions.UnlockHash()]) if err != nil { return err } } // Add the exact output. claimUnlockConditions, err := tb.wallet.nextPrimarySeedAddress() if err != nil { return err } newInput := types.SiafundInput{ ParentID: parentTxn.SiafundOutputID(0), UnlockConditions: parentUnlockConditions, ClaimUnlockHash: claimUnlockConditions.UnlockHash(), } tb.newParents = append(tb.newParents, len(tb.parents)) tb.parents = append(tb.parents, parentTxn) tb.siafundInputs = append(tb.siafundInputs, len(tb.transaction.SiafundInputs)) tb.transaction.SiafundInputs = append(tb.transaction.SiafundInputs, newInput) // Mark all outputs that were spent as spent. for _, sfoid := range spentSfoids { tb.wallet.spentOutputs[types.OutputID(sfoid)] = tb.wallet.consensusSetHeight } return nil }
// 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 }
// 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) }
// TestApplySiafundOutputs probes the applySiafundOutputs method of the // consensus set. func TestApplySiafundOutputs(t *testing.T) { if testing.Short() { t.SkipNow() } cst, err := createConsensusSetTester("TestApplySiafundOutputs") if err != nil { t.Fatal(err) } cst.cs.siafundPool = types.NewCurrency64(101) // Create a block node to use with application. bn := new(blockNode) // Apply a transaction with a single siafund output. txn := types.Transaction{ SiafundOutputs: []types.SiafundOutput{{}}, } cst.cs.applySiafundOutputs(bn, txn) sfoid := txn.SiafundOutputID(0) _, exists := cst.cs.siafundOutputs[sfoid] if !exists { t.Error("Failed to create siafund output") } if len(cst.cs.siafundOutputs) != 3 { t.Error("siafund outputs not correctly updated") } if len(bn.siafundOutputDiffs) != 1 { t.Error("block node was not updated for single element transaction") } if bn.siafundOutputDiffs[0].Direction != modules.DiffApply { t.Error("wrong diff direction applied when creating a siafund output") } if bn.siafundOutputDiffs[0].ID != sfoid { t.Error("wrong id used when creating a siafund output") } if bn.siafundOutputDiffs[0].SiafundOutput.ClaimStart.Cmp(types.NewCurrency64(101)) != 0 { t.Error("claim start set incorrectly when creating a siafund output") } // Apply a transaction with 2 siacoin outputs. txn = types.Transaction{ SiafundOutputs: []types.SiafundOutput{ {Value: types.NewCurrency64(1)}, {Value: types.NewCurrency64(2)}, }, } cst.cs.applySiafundOutputs(bn, txn) sfoid0 := txn.SiafundOutputID(0) sfoid1 := txn.SiafundOutputID(1) _, exists = cst.cs.siafundOutputs[sfoid0] if !exists { t.Error("Failed to create siafund output") } _, exists = cst.cs.siafundOutputs[sfoid1] if !exists { t.Error("Failed to create siafund output") } if len(cst.cs.siafundOutputs) != 5 { t.Error("siafund outputs not correctly updated") } if len(bn.siafundOutputDiffs) != 3 { t.Error("block node was not updated for single element transaction") } }
// FundSiafunds will add a siafund input of exaclty 'amount' to the // transaction. A parent transaction may be needed to achieve an input with the // correct value. The siafund input will not be signed until 'Sign' is called // on the transaction builder. // // TODO: The implementation of FundSiacoins is known to have quirks/bugs // (non-fatal), and has diverged from the implementation of FundSiacoins. The // implementations should be converged once again. func (tb *transactionBuilder) FundSiafunds(amount types.Currency) error { lockID := tb.wallet.mu.Lock() defer tb.wallet.mu.Unlock(lockID) // Create and fund a parent transaction that will add the correct amount of // siafunds to the transaction. var fund types.Currency parentTxn := types.Transaction{} for scoid, sco := range tb.wallet.siafundOutputs { // Check that this output has not recently been spent by the wallet. spendHeight := tb.wallet.spentOutputs[types.OutputID(scoid)] if spendHeight > tb.wallet.consensusSetHeight-RespendTimeout { continue } outputUnlockConditions := tb.wallet.keys[sco.UnlockHash].unlockConditions if tb.wallet.consensusSetHeight < outputUnlockConditions.Timelock { continue } // Mark the output as spent. tb.wallet.spentOutputs[types.OutputID(scoid)] = tb.wallet.consensusSetHeight // Add a siafund input for this output. parentClaimUnlockConditions, err := tb.wallet.nextPrimarySeedAddress() if err != nil { return err } sci := types.SiafundInput{ ParentID: scoid, UnlockConditions: outputUnlockConditions, ClaimUnlockHash: parentClaimUnlockConditions.UnlockHash(), } parentTxn.SiafundInputs = append(parentTxn.SiafundInputs, sci) // Add the output to the total fund fund = fund.Add(sco.Value) if fund.Cmp(amount) >= 0 { break } } // 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.SiafundOutput{ Value: amount, UnlockHash: parentUnlockConditions.UnlockHash(), } parentTxn.SiafundOutputs = append(parentTxn.SiafundOutputs, exactOutput) // Create a refund output if needed. if amount.Cmp(fund) != 0 { refundUnlockConditions, err := tb.wallet.nextPrimarySeedAddress() if err != nil { return err } refundOutput := types.SiafundOutput{ Value: fund.Sub(amount), UnlockHash: refundUnlockConditions.UnlockHash(), } parentTxn.SiafundOutputs = append(parentTxn.SiafundOutputs, refundOutput) } // Sign all of the inputs to the parent trancstion. for _, sfi := range parentTxn.SiafundInputs { err := addSignatures(&parentTxn, types.FullCoveredFields, sfi.UnlockConditions, crypto.Hash(sfi.ParentID), tb.wallet.keys[sfi.UnlockConditions.UnlockHash()]) if err != nil { return err } } // Add the exact output. claimUnlockConditions, err := tb.wallet.nextPrimarySeedAddress() if err != nil { return err } newInput := types.SiafundInput{ ParentID: parentTxn.SiafundOutputID(0), UnlockConditions: parentUnlockConditions, ClaimUnlockHash: claimUnlockConditions.UnlockHash(), } tb.parents = append(tb.parents, parentTxn) tb.siafundInputs = append(tb.siafundInputs, len(tb.transaction.SiafundInputs)) tb.transaction.SiafundInputs = append(tb.transaction.SiafundInputs, newInput) return nil }