// ReceiveUpdatedUnconfirmedTransactions updates the wallet's unconfirmed // transaction set. func (w *Wallet) ReceiveUpdatedUnconfirmedTransactions(txns []types.Transaction, _ modules.ConsensusChange) { // There are two different situations under which a subscribee calls // ProcessConsensusChange. The first is when w.subscribed is set to false // AND the mutex is already locked. The other situation is that subscribed // is set to true and is not going to be changed. Therefore there is no // race condition here. If w.subscribed is set to false, trying to grab the // lock would cause a deadlock. if w.subscribed { lockID := w.mu.Lock() defer w.mu.Unlock(lockID) } w.unconfirmedProcessedTransactions = nil for _, txn := range txns { // To save on code complexity, relveancy is determined while building // up the wallet transaction. relevant := false pt := modules.ProcessedTransaction{ Transaction: txn, TransactionID: txn.ID(), ConfirmationHeight: types.BlockHeight(math.MaxUint64), ConfirmationTimestamp: types.Timestamp(math.MaxUint64), } for _, sci := range txn.SiacoinInputs { _, exists := w.keys[sci.UnlockConditions.UnlockHash()] if exists { relevant = true } pt.Inputs = append(pt.Inputs, modules.ProcessedInput{ FundType: types.SpecifierSiacoinInput, WalletAddress: exists, RelatedAddress: sci.UnlockConditions.UnlockHash(), Value: w.historicOutputs[types.OutputID(sci.ParentID)], }) } for i, sco := range txn.SiacoinOutputs { _, exists := w.keys[sco.UnlockHash] if exists { relevant = true } pt.Outputs = append(pt.Outputs, modules.ProcessedOutput{ FundType: types.SpecifierSiacoinOutput, MaturityHeight: types.BlockHeight(math.MaxUint64), WalletAddress: exists, RelatedAddress: sco.UnlockHash, Value: sco.Value, }) w.historicOutputs[types.OutputID(txn.SiacoinOutputID(i))] = sco.Value } for _, fee := range txn.MinerFees { pt.Outputs = append(pt.Outputs, modules.ProcessedOutput{ FundType: types.SpecifierMinerFee, Value: fee, }) } if relevant { w.unconfirmedProcessedTransactions = append(w.unconfirmedProcessedTransactions, pt) } } }
// ReceiveUpdatedUnconfirmedTransactions updates the wallet's unconfirmed // transaction set. func (w *Wallet) ReceiveUpdatedUnconfirmedTransactions(txns []types.Transaction, _ modules.ConsensusChange) { if err := w.tg.Add(); err != nil { // Gracefully reject transactions if the wallet's Close method has // closed the wallet's ThreadGroup already. return } defer w.tg.Done() w.mu.Lock() defer w.mu.Unlock() w.unconfirmedProcessedTransactions = nil for _, txn := range txns { // To save on code complexity, relevancy is determined while building // up the wallet transaction. relevant := false pt := modules.ProcessedTransaction{ Transaction: txn, TransactionID: txn.ID(), ConfirmationHeight: types.BlockHeight(math.MaxUint64), ConfirmationTimestamp: types.Timestamp(math.MaxUint64), } for _, sci := range txn.SiacoinInputs { _, exists := w.keys[sci.UnlockConditions.UnlockHash()] if exists { relevant = true } pt.Inputs = append(pt.Inputs, modules.ProcessedInput{ FundType: types.SpecifierSiacoinInput, WalletAddress: exists, RelatedAddress: sci.UnlockConditions.UnlockHash(), Value: w.historicOutputs[types.OutputID(sci.ParentID)], }) } for i, sco := range txn.SiacoinOutputs { _, exists := w.keys[sco.UnlockHash] if exists { relevant = true } pt.Outputs = append(pt.Outputs, modules.ProcessedOutput{ FundType: types.SpecifierSiacoinOutput, MaturityHeight: types.BlockHeight(math.MaxUint64), WalletAddress: exists, RelatedAddress: sco.UnlockHash, Value: sco.Value, }) w.historicOutputs[types.OutputID(txn.SiacoinOutputID(uint64(i)))] = sco.Value } for _, fee := range txn.MinerFees { pt.Outputs = append(pt.Outputs, modules.ProcessedOutput{ FundType: types.SpecifierMinerFee, Value: fee, }) } if relevant { w.unconfirmedProcessedTransactions = append(w.unconfirmedProcessedTransactions, pt) } } }
// Drop discards all of the outputs in a transaction, returning them to the // pool so that other transactions may use them. 'Drop' should only be called // if a transaction is both unsigned and will not be used any further. func (tb *transactionBuilder) Drop() { tb.wallet.mu.Lock() defer tb.wallet.mu.Unlock() // Iterate through all parents and the transaction itself and restore all // outputs to the list of available outputs. txns := append(tb.parents, tb.transaction) for _, txn := range txns { for _, sci := range txn.SiacoinInputs { delete(tb.wallet.spentOutputs, types.OutputID(sci.ParentID)) } } tb.parents = nil tb.transaction = types.Transaction{} tb.siacoinInputs = nil tb.siafundInputs = nil }
// 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 }
// 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 }
// ProcessConsensusChange parses a consensus change to update the set of // confirmed outputs known to the wallet. func (w *Wallet) ProcessConsensusChange(cc modules.ConsensusChange) { // There are two different situations under which a subscribee calls // ProcessConsensusChange. The first is when w.subscribed is set to false // AND the mutex is already locked. The other situation is that subscribed // is set to true and is not going to be changed. Therefore there is no // race condition here. If w.subscribed is set to false, trying to grab the // lock would cause a deadlock. if w.subscribed { lockID := w.mu.Lock() defer w.mu.Unlock(lockID) } // Iterate through the output diffs (siacoin and siafund) and apply all of // them. Only apply the outputs that relate to unlock hashes we understand. for _, diff := range cc.SiacoinOutputDiffs { // Verify that the diff is relevant to the wallet. _, exists := w.keys[diff.SiacoinOutput.UnlockHash] if !exists { continue } _, exists = w.siacoinOutputs[diff.ID] if diff.Direction == modules.DiffApply { if exists && build.DEBUG { panic("adding an existing output to wallet") } w.siacoinOutputs[diff.ID] = diff.SiacoinOutput } else { if !exists && build.DEBUG { panic("deleting nonexisting output from wallet") } delete(w.siacoinOutputs, diff.ID) } } for _, diff := range cc.SiafundOutputDiffs { // Verify that the diff is relevant to the wallet. _, exists := w.keys[diff.SiafundOutput.UnlockHash] if !exists { continue } _, exists = w.siafundOutputs[diff.ID] if diff.Direction == modules.DiffApply { if exists && build.DEBUG { panic("adding an existing output to wallet") } w.siafundOutputs[diff.ID] = diff.SiafundOutput } else { if !exists && build.DEBUG { panic("deleting nonexisting output from wallet") } delete(w.siafundOutputs, diff.ID) } } for _, diff := range cc.SiafundPoolDiffs { if diff.Direction == modules.DiffApply { w.siafundPool = diff.Adjusted } else { w.siafundPool = diff.Previous } } // Iterate through the transactions and find every transaction somehow // related to the wallet. Wallet transactions must be removed in the same // order they were added. for _, block := range cc.RevertedBlocks { // Remove any transactions that have been reverted. for i := len(block.Transactions) - 1; i >= 0; i-- { // If the transaction is relevant to the wallet, it will be the // most recent transaction appended to w.processedTransactions. // Relevance can be determined just by looking at the last element // of w.processedTransactions. txn := block.Transactions[i] txid := txn.ID() if len(w.processedTransactions) > 0 && txid == w.processedTransactions[len(w.processedTransactions)-1].TransactionID { w.processedTransactions = w.processedTransactions[:len(w.processedTransactions)-1] delete(w.processedTransactionMap, txid) } } // Remove the miner payout transaction if applicable. for _, mp := range block.MinerPayouts { _, exists := w.keys[mp.UnlockHash] if exists { w.processedTransactions = w.processedTransactions[:len(w.processedTransactions)-1] delete(w.processedTransactionMap, types.TransactionID(block.ID())) break } } w.consensusSetHeight-- } // Apply all of the new blocks. for _, block := range cc.AppliedBlocks { w.consensusSetHeight++ // Apply the miner payout transaction if applicable. minerPT := modules.ProcessedTransaction{ Transaction: types.Transaction{}, TransactionID: types.TransactionID(block.ID()), ConfirmationHeight: w.consensusSetHeight, ConfirmationTimestamp: block.Timestamp, } relevant := false for i, mp := range block.MinerPayouts { _, exists := w.keys[mp.UnlockHash] if exists { relevant = true } minerPT.Outputs = append(minerPT.Outputs, modules.ProcessedOutput{ FundType: types.SpecifierMinerPayout, MaturityHeight: w.consensusSetHeight + types.MaturityDelay, WalletAddress: exists, RelatedAddress: mp.UnlockHash, Value: mp.Value, }) w.historicOutputs[types.OutputID(block.MinerPayoutID(uint64(i)))] = mp.Value } if relevant { w.processedTransactions = append(w.processedTransactions, minerPT) w.processedTransactionMap[minerPT.TransactionID] = &w.processedTransactions[len(w.processedTransactions)-1] } for _, txn := range block.Transactions { relevant := false pt := modules.ProcessedTransaction{ Transaction: txn, TransactionID: txn.ID(), ConfirmationHeight: w.consensusSetHeight, ConfirmationTimestamp: block.Timestamp, } for _, sci := range txn.SiacoinInputs { _, exists := w.keys[sci.UnlockConditions.UnlockHash()] if exists { relevant = true } pt.Inputs = append(pt.Inputs, modules.ProcessedInput{ FundType: types.SpecifierSiacoinInput, WalletAddress: exists, RelatedAddress: sci.UnlockConditions.UnlockHash(), Value: w.historicOutputs[types.OutputID(sci.ParentID)], }) } for i, sco := range txn.SiacoinOutputs { _, exists := w.keys[sco.UnlockHash] if exists { relevant = true } pt.Outputs = append(pt.Outputs, modules.ProcessedOutput{ FundType: types.SpecifierSiacoinOutput, MaturityHeight: w.consensusSetHeight, WalletAddress: exists, RelatedAddress: sco.UnlockHash, Value: sco.Value, }) w.historicOutputs[types.OutputID(txn.SiacoinOutputID(i))] = sco.Value } for _, sfi := range txn.SiafundInputs { _, exists := w.keys[sfi.UnlockConditions.UnlockHash()] if exists { relevant = true } sfiValue := w.historicOutputs[types.OutputID(sfi.ParentID)] pt.Inputs = append(pt.Inputs, modules.ProcessedInput{ FundType: types.SpecifierSiafundInput, WalletAddress: exists, RelatedAddress: sfi.UnlockConditions.UnlockHash(), Value: sfiValue, }) claimValue := w.siafundPool.Sub(w.historicClaimStarts[sfi.ParentID]).Mul(sfiValue) pt.Outputs = append(pt.Outputs, modules.ProcessedOutput{ FundType: types.SpecifierClaimOutput, MaturityHeight: w.consensusSetHeight + types.MaturityDelay, WalletAddress: exists, RelatedAddress: sfi.ClaimUnlockHash, Value: claimValue, }) } for i, sfo := range txn.SiafundOutputs { _, exists := w.keys[sfo.UnlockHash] if exists { relevant = true } pt.Outputs = append(pt.Outputs, modules.ProcessedOutput{ FundType: types.SpecifierSiafundOutput, MaturityHeight: w.consensusSetHeight, WalletAddress: exists, RelatedAddress: sfo.UnlockHash, Value: sfo.Value, }) w.historicOutputs[types.OutputID(txn.SiafundOutputID(i))] = sfo.Value w.historicClaimStarts[txn.SiafundOutputID(i)] = sfo.ClaimStart } for _, fee := range txn.MinerFees { pt.Outputs = append(pt.Outputs, modules.ProcessedOutput{ FundType: types.SpecifierMinerFee, Value: fee, }) } if relevant { w.processedTransactions = append(w.processedTransactions, pt) w.processedTransactionMap[pt.TransactionID] = &w.processedTransactions[len(w.processedTransactions)-1] } } } }
// 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 }
// applyHistory applies any transaction history that was introduced by the // applied blocks. func (w *Wallet) applyHistory(cc modules.ConsensusChange) { for _, block := range cc.AppliedBlocks { w.consensusSetHeight++ // Apply the miner payout transaction if applicable. minerPT := modules.ProcessedTransaction{ Transaction: types.Transaction{}, TransactionID: types.TransactionID(block.ID()), ConfirmationHeight: w.consensusSetHeight, ConfirmationTimestamp: block.Timestamp, } relevant := false for i, mp := range block.MinerPayouts { _, exists := w.keys[mp.UnlockHash] if exists { relevant = true } minerPT.Outputs = append(minerPT.Outputs, modules.ProcessedOutput{ FundType: types.SpecifierMinerPayout, MaturityHeight: w.consensusSetHeight + types.MaturityDelay, WalletAddress: exists, RelatedAddress: mp.UnlockHash, Value: mp.Value, }) w.historicOutputs[types.OutputID(block.MinerPayoutID(uint64(i)))] = mp.Value } if relevant { w.processedTransactions = append(w.processedTransactions, minerPT) w.processedTransactionMap[minerPT.TransactionID] = &w.processedTransactions[len(w.processedTransactions)-1] } for _, txn := range block.Transactions { relevant := false pt := modules.ProcessedTransaction{ Transaction: txn, TransactionID: txn.ID(), ConfirmationHeight: w.consensusSetHeight, ConfirmationTimestamp: block.Timestamp, } for _, sci := range txn.SiacoinInputs { _, exists := w.keys[sci.UnlockConditions.UnlockHash()] if exists { relevant = true } pt.Inputs = append(pt.Inputs, modules.ProcessedInput{ FundType: types.SpecifierSiacoinInput, WalletAddress: exists, RelatedAddress: sci.UnlockConditions.UnlockHash(), Value: w.historicOutputs[types.OutputID(sci.ParentID)], }) } for i, sco := range txn.SiacoinOutputs { _, exists := w.keys[sco.UnlockHash] if exists { relevant = true } pt.Outputs = append(pt.Outputs, modules.ProcessedOutput{ FundType: types.SpecifierSiacoinOutput, MaturityHeight: w.consensusSetHeight, WalletAddress: exists, RelatedAddress: sco.UnlockHash, Value: sco.Value, }) w.historicOutputs[types.OutputID(txn.SiacoinOutputID(i))] = sco.Value } for _, sfi := range txn.SiafundInputs { _, exists := w.keys[sfi.UnlockConditions.UnlockHash()] if exists { relevant = true } sfiValue := w.historicOutputs[types.OutputID(sfi.ParentID)] pt.Inputs = append(pt.Inputs, modules.ProcessedInput{ FundType: types.SpecifierSiafundInput, WalletAddress: exists, RelatedAddress: sfi.UnlockConditions.UnlockHash(), Value: sfiValue, }) claimValue := w.siafundPool.Sub(w.historicClaimStarts[sfi.ParentID]).Mul(sfiValue) pt.Outputs = append(pt.Outputs, modules.ProcessedOutput{ FundType: types.SpecifierClaimOutput, MaturityHeight: w.consensusSetHeight + types.MaturityDelay, WalletAddress: exists, RelatedAddress: sfi.ClaimUnlockHash, Value: claimValue, }) } for i, sfo := range txn.SiafundOutputs { _, exists := w.keys[sfo.UnlockHash] if exists { relevant = true } pt.Outputs = append(pt.Outputs, modules.ProcessedOutput{ FundType: types.SpecifierSiafundOutput, MaturityHeight: w.consensusSetHeight, WalletAddress: exists, RelatedAddress: sfo.UnlockHash, Value: sfo.Value, }) w.historicOutputs[types.OutputID(txn.SiafundOutputID(i))] = sfo.Value w.historicClaimStarts[txn.SiafundOutputID(i)] = sfo.ClaimStart } for _, fee := range txn.MinerFees { pt.Outputs = append(pt.Outputs, modules.ProcessedOutput{ FundType: types.SpecifierMinerFee, Value: fee, }) } if relevant { w.processedTransactions = append(w.processedTransactions, pt) w.processedTransactionMap[pt.TransactionID] = &w.processedTransactions[len(w.processedTransactions)-1] } } } }