github.com/ZuluSpl0it/Sia@v1.3.7/modules/wallet/update.go (about)

     1  package wallet
     2  
     3  import (
     4  	"math"
     5  
     6  	"github.com/NebulousLabs/Sia/modules"
     7  	"github.com/NebulousLabs/Sia/types"
     8  	"github.com/NebulousLabs/errors"
     9  
    10  	"github.com/coreos/bbolt"
    11  )
    12  
    13  type (
    14  	spentSiacoinOutputSet map[types.SiacoinOutputID]types.SiacoinOutput
    15  	spentSiafundOutputSet map[types.SiafundOutputID]types.SiafundOutput
    16  )
    17  
    18  // threadedResetSubscriptions unsubscribes the wallet from the consensus set and transaction pool
    19  // and subscribes again.
    20  func (w *Wallet) threadedResetSubscriptions() error {
    21  	if !w.scanLock.TryLock() {
    22  		return errScanInProgress
    23  	}
    24  	defer w.scanLock.Unlock()
    25  
    26  	w.cs.Unsubscribe(w)
    27  	w.tpool.Unsubscribe(w)
    28  
    29  	err := w.cs.ConsensusSetSubscribe(w, modules.ConsensusChangeBeginning, w.tg.StopChan())
    30  	if err != nil {
    31  		return err
    32  	}
    33  	w.tpool.TransactionPoolSubscribe(w)
    34  	return nil
    35  }
    36  
    37  // advanceSeedLookahead generates all keys from the current primary seed progress up to index
    38  // and adds them to the set of spendable keys.  Therefore the new primary seed progress will
    39  // be index+1 and new lookahead keys will be generated starting from index+1
    40  // Returns true if a blockchain rescan is required
    41  func (w *Wallet) advanceSeedLookahead(index uint64) (bool, error) {
    42  	progress, err := dbGetPrimarySeedProgress(w.dbTx)
    43  	if err != nil {
    44  		return false, err
    45  	}
    46  	newProgress := index + 1
    47  
    48  	// Add spendable keys and remove them from lookahead
    49  	spendableKeys := generateKeys(w.primarySeed, progress, newProgress-progress)
    50  	for _, key := range spendableKeys {
    51  		w.keys[key.UnlockConditions.UnlockHash()] = key
    52  		delete(w.lookahead, key.UnlockConditions.UnlockHash())
    53  	}
    54  
    55  	// Update the primarySeedProgress
    56  	dbPutPrimarySeedProgress(w.dbTx, newProgress)
    57  	if err != nil {
    58  		return false, err
    59  	}
    60  
    61  	// Regenerate lookahead
    62  	w.regenerateLookahead(newProgress)
    63  
    64  	// If more than lookaheadRescanThreshold keys were generated
    65  	// also initialize a rescan just to be safe.
    66  	if uint64(len(spendableKeys)) > lookaheadRescanThreshold {
    67  		return true, nil
    68  	}
    69  
    70  	return false, nil
    71  }
    72  
    73  // isWalletAddress is a helper function that checks if an UnlockHash is
    74  // derived from one of the wallet's spendable keys or future keys.
    75  func (w *Wallet) isWalletAddress(uh types.UnlockHash) bool {
    76  	_, exists := w.keys[uh]
    77  	return exists
    78  }
    79  
    80  // updateLookahead uses a consensus change to update the seed progress if one of the outputs
    81  // contains an unlock hash of the lookahead set. Returns true if a blockchain rescan is required
    82  func (w *Wallet) updateLookahead(tx *bolt.Tx, cc modules.ConsensusChange) (bool, error) {
    83  	var largestIndex uint64
    84  	for _, diff := range cc.SiacoinOutputDiffs {
    85  		if index, ok := w.lookahead[diff.SiacoinOutput.UnlockHash]; ok {
    86  			if index > largestIndex {
    87  				largestIndex = index
    88  			}
    89  		}
    90  	}
    91  	for _, diff := range cc.SiafundOutputDiffs {
    92  		if index, ok := w.lookahead[diff.SiafundOutput.UnlockHash]; ok {
    93  			if index > largestIndex {
    94  				largestIndex = index
    95  			}
    96  		}
    97  	}
    98  	if largestIndex > 0 {
    99  		return w.advanceSeedLookahead(largestIndex)
   100  	}
   101  
   102  	return false, nil
   103  }
   104  
   105  // updateConfirmedSet uses a consensus change to update the confirmed set of
   106  // outputs as understood by the wallet.
   107  func (w *Wallet) updateConfirmedSet(tx *bolt.Tx, cc modules.ConsensusChange) error {
   108  	for _, diff := range cc.SiacoinOutputDiffs {
   109  		// Verify that the diff is relevant to the wallet.
   110  		if !w.isWalletAddress(diff.SiacoinOutput.UnlockHash) {
   111  			continue
   112  		}
   113  
   114  		var err error
   115  		if diff.Direction == modules.DiffApply {
   116  			w.log.Println("Wallet has gained a spendable siacoin output:", diff.ID, "::", diff.SiacoinOutput.Value.HumanString())
   117  			err = dbPutSiacoinOutput(tx, diff.ID, diff.SiacoinOutput)
   118  		} else {
   119  			w.log.Println("Wallet has lost a spendable siacoin output:", diff.ID, "::", diff.SiacoinOutput.Value.HumanString())
   120  			err = dbDeleteSiacoinOutput(tx, diff.ID)
   121  		}
   122  		if err != nil {
   123  			w.log.Severe("Could not update siacoin output:", err)
   124  			return err
   125  		}
   126  	}
   127  	for _, diff := range cc.SiafundOutputDiffs {
   128  		// Verify that the diff is relevant to the wallet.
   129  		if !w.isWalletAddress(diff.SiafundOutput.UnlockHash) {
   130  			continue
   131  		}
   132  
   133  		var err error
   134  		if diff.Direction == modules.DiffApply {
   135  			w.log.Println("Wallet has gained a spendable siafund output:", diff.ID, "::", diff.SiafundOutput.Value)
   136  			err = dbPutSiafundOutput(tx, diff.ID, diff.SiafundOutput)
   137  		} else {
   138  			w.log.Println("Wallet has lost a spendable siafund output:", diff.ID, "::", diff.SiafundOutput.Value)
   139  			err = dbDeleteSiafundOutput(tx, diff.ID)
   140  		}
   141  		if err != nil {
   142  			w.log.Severe("Could not update siafund output:", err)
   143  			return err
   144  		}
   145  	}
   146  	for _, diff := range cc.SiafundPoolDiffs {
   147  		var err error
   148  		if diff.Direction == modules.DiffApply {
   149  			err = dbPutSiafundPool(tx, diff.Adjusted)
   150  		} else {
   151  			err = dbPutSiafundPool(tx, diff.Previous)
   152  		}
   153  		if err != nil {
   154  			w.log.Severe("Could not update siafund pool:", err)
   155  			return err
   156  		}
   157  	}
   158  	return nil
   159  }
   160  
   161  // revertHistory reverts any transaction history that was destroyed by reverted
   162  // blocks in the consensus change.
   163  func (w *Wallet) revertHistory(tx *bolt.Tx, reverted []types.Block) error {
   164  	for _, block := range reverted {
   165  		// Remove any transactions that have been reverted.
   166  		for i := len(block.Transactions) - 1; i >= 0; i-- {
   167  			// If the transaction is relevant to the wallet, it will be the
   168  			// most recent transaction in bucketProcessedTransactions.
   169  			txid := block.Transactions[i].ID()
   170  			pt, err := dbGetLastProcessedTransaction(tx)
   171  			if err != nil {
   172  				break // bucket is empty
   173  			}
   174  			if txid == pt.TransactionID {
   175  				w.log.Println("A wallet transaction has been reverted due to a reorg:", txid)
   176  				if err := dbDeleteLastProcessedTransaction(tx); err != nil {
   177  					w.log.Severe("Could not revert transaction:", err)
   178  					return err
   179  				}
   180  			}
   181  		}
   182  
   183  		// Remove the miner payout transaction if applicable.
   184  		for i, mp := range block.MinerPayouts {
   185  			// If the transaction is relevant to the wallet, it will be the
   186  			// most recent transaction in bucketProcessedTransactions.
   187  			pt, err := dbGetLastProcessedTransaction(tx)
   188  			if err != nil {
   189  				break // bucket is empty
   190  			}
   191  			if types.TransactionID(block.ID()) == pt.TransactionID {
   192  				w.log.Println("Miner payout has been reverted due to a reorg:", block.MinerPayoutID(uint64(i)), "::", mp.Value.HumanString())
   193  				if err := dbDeleteLastProcessedTransaction(tx); err != nil {
   194  					w.log.Severe("Could not revert transaction:", err)
   195  					return err
   196  				}
   197  				break // there will only ever be one miner transaction
   198  			}
   199  		}
   200  
   201  		// decrement the consensus height
   202  		if block.ID() != types.GenesisID {
   203  			consensusHeight, err := dbGetConsensusHeight(tx)
   204  			if err != nil {
   205  				return err
   206  			}
   207  			err = dbPutConsensusHeight(tx, consensusHeight-1)
   208  			if err != nil {
   209  				return err
   210  			}
   211  		}
   212  	}
   213  	return nil
   214  }
   215  
   216  // outputs and collects them in a map of SiacoinOutputID -> SiacoinOutput.
   217  func computeSpentSiacoinOutputSet(diffs []modules.SiacoinOutputDiff) spentSiacoinOutputSet {
   218  	outputs := make(spentSiacoinOutputSet)
   219  	for _, diff := range diffs {
   220  		if diff.Direction == modules.DiffRevert {
   221  			// DiffRevert means spent.
   222  			outputs[diff.ID] = diff.SiacoinOutput
   223  		}
   224  	}
   225  	return outputs
   226  }
   227  
   228  // computeSpentSiafundOutputSet scans a slice of Siafund output diffs for spent
   229  // outputs and collects them in a map of SiafundOutputID -> SiafundOutput.
   230  func computeSpentSiafundOutputSet(diffs []modules.SiafundOutputDiff) spentSiafundOutputSet {
   231  	outputs := make(spentSiafundOutputSet)
   232  	for _, diff := range diffs {
   233  		if diff.Direction == modules.DiffRevert {
   234  			// DiffRevert means spent.
   235  			outputs[diff.ID] = diff.SiafundOutput
   236  		}
   237  	}
   238  	return outputs
   239  }
   240  
   241  // computeProcessedTransactionsFromBlock searches all the miner payouts and
   242  // transactions in a block and computes a ProcessedTransaction slice containing
   243  // all of the transactions processed for the given block.
   244  func (w *Wallet) computeProcessedTransactionsFromBlock(tx *bolt.Tx, block types.Block, spentSiacoinOutputs spentSiacoinOutputSet, spentSiafundOutputs spentSiafundOutputSet, consensusHeight types.BlockHeight) []modules.ProcessedTransaction {
   245  	var pts []modules.ProcessedTransaction
   246  
   247  	// Find ProcessedTransactions from miner payouts.
   248  	relevant := false
   249  	for _, mp := range block.MinerPayouts {
   250  		relevant = relevant || w.isWalletAddress(mp.UnlockHash)
   251  	}
   252  	if relevant {
   253  		w.log.Println("Wallet has received new miner payouts:", block.ID())
   254  		// Apply the miner payout transaction if applicable.
   255  		minerPT := modules.ProcessedTransaction{
   256  			Transaction:           types.Transaction{},
   257  			TransactionID:         types.TransactionID(block.ID()),
   258  			ConfirmationHeight:    consensusHeight,
   259  			ConfirmationTimestamp: block.Timestamp,
   260  		}
   261  		for i, mp := range block.MinerPayouts {
   262  			w.log.Println("\tminer payout:", block.MinerPayoutID(uint64(i)), "::", mp.Value.HumanString())
   263  			minerPT.Outputs = append(minerPT.Outputs, modules.ProcessedOutput{
   264  				ID:             types.OutputID(block.MinerPayoutID(uint64(i))),
   265  				FundType:       types.SpecifierMinerPayout,
   266  				MaturityHeight: consensusHeight + types.MaturityDelay,
   267  				WalletAddress:  w.isWalletAddress(mp.UnlockHash),
   268  				RelatedAddress: mp.UnlockHash,
   269  				Value:          mp.Value,
   270  			})
   271  		}
   272  		pts = append(pts, minerPT)
   273  	}
   274  
   275  	// Find ProcessedTransactions from transactions.
   276  	for _, txn := range block.Transactions {
   277  		// Determine if transaction is relevant.
   278  		relevant := false
   279  		for _, sci := range txn.SiacoinInputs {
   280  			relevant = relevant || w.isWalletAddress(sci.UnlockConditions.UnlockHash())
   281  		}
   282  		for _, sco := range txn.SiacoinOutputs {
   283  			relevant = relevant || w.isWalletAddress(sco.UnlockHash)
   284  		}
   285  		for _, sfi := range txn.SiafundInputs {
   286  			relevant = relevant || w.isWalletAddress(sfi.UnlockConditions.UnlockHash())
   287  		}
   288  		for _, sfo := range txn.SiafundOutputs {
   289  			relevant = relevant || w.isWalletAddress(sfo.UnlockHash)
   290  		}
   291  
   292  		// Only create a ProcessedTransaction if transaction is relevant.
   293  		if !relevant {
   294  			continue
   295  		}
   296  		w.log.Println("A transaction has been confirmed on the blockchain:", txn.ID())
   297  
   298  		pt := modules.ProcessedTransaction{
   299  			Transaction:           txn,
   300  			TransactionID:         txn.ID(),
   301  			ConfirmationHeight:    consensusHeight,
   302  			ConfirmationTimestamp: block.Timestamp,
   303  		}
   304  
   305  		for _, sci := range txn.SiacoinInputs {
   306  			pi := modules.ProcessedInput{
   307  				ParentID:       types.OutputID(sci.ParentID),
   308  				FundType:       types.SpecifierSiacoinInput,
   309  				WalletAddress:  w.isWalletAddress(sci.UnlockConditions.UnlockHash()),
   310  				RelatedAddress: sci.UnlockConditions.UnlockHash(),
   311  				Value:          spentSiacoinOutputs[sci.ParentID].Value,
   312  			}
   313  			pt.Inputs = append(pt.Inputs, pi)
   314  
   315  			// Log any wallet-relevant inputs.
   316  			if pi.WalletAddress {
   317  				w.log.Println("\tSiacoin Input:", pi.ParentID, "::", pi.Value.HumanString())
   318  			}
   319  		}
   320  
   321  		for i, sco := range txn.SiacoinOutputs {
   322  			po := modules.ProcessedOutput{
   323  				ID:             types.OutputID(txn.SiacoinOutputID(uint64(i))),
   324  				FundType:       types.SpecifierSiacoinOutput,
   325  				MaturityHeight: consensusHeight,
   326  				WalletAddress:  w.isWalletAddress(sco.UnlockHash),
   327  				RelatedAddress: sco.UnlockHash,
   328  				Value:          sco.Value,
   329  			}
   330  			pt.Outputs = append(pt.Outputs, po)
   331  
   332  			// Log any wallet-relevant outputs.
   333  			if po.WalletAddress {
   334  				w.log.Println("\tSiacoin Output:", po.ID, "::", po.Value.HumanString())
   335  			}
   336  		}
   337  
   338  		for _, sfi := range txn.SiafundInputs {
   339  			pi := modules.ProcessedInput{
   340  				ParentID:       types.OutputID(sfi.ParentID),
   341  				FundType:       types.SpecifierSiafundInput,
   342  				WalletAddress:  w.isWalletAddress(sfi.UnlockConditions.UnlockHash()),
   343  				RelatedAddress: sfi.UnlockConditions.UnlockHash(),
   344  				Value:          spentSiafundOutputs[sfi.ParentID].Value,
   345  			}
   346  			pt.Inputs = append(pt.Inputs, pi)
   347  			// Log any wallet-relevant inputs.
   348  			if pi.WalletAddress {
   349  				w.log.Println("\tSiafund Input:", pi.ParentID, "::", pi.Value.HumanString())
   350  			}
   351  
   352  			siafundPool, err := dbGetSiafundPool(w.dbTx)
   353  			if err != nil {
   354  				w.log.Println("could not get siafund pool: ", err)
   355  				continue
   356  			}
   357  
   358  			sfo := spentSiafundOutputs[sfi.ParentID]
   359  			po := modules.ProcessedOutput{
   360  				ID:             types.OutputID(sfi.ParentID),
   361  				FundType:       types.SpecifierClaimOutput,
   362  				MaturityHeight: consensusHeight + types.MaturityDelay,
   363  				WalletAddress:  w.isWalletAddress(sfi.UnlockConditions.UnlockHash()),
   364  				RelatedAddress: sfi.ClaimUnlockHash,
   365  				Value:          siafundPool.Sub(sfo.ClaimStart).Mul(sfo.Value),
   366  			}
   367  			pt.Outputs = append(pt.Outputs, po)
   368  			// Log any wallet-relevant outputs.
   369  			if po.WalletAddress {
   370  				w.log.Println("\tClaim Output:", po.ID, "::", po.Value.HumanString())
   371  			}
   372  		}
   373  
   374  		for i, sfo := range txn.SiafundOutputs {
   375  			po := modules.ProcessedOutput{
   376  				ID:             types.OutputID(txn.SiafundOutputID(uint64(i))),
   377  				FundType:       types.SpecifierSiafundOutput,
   378  				MaturityHeight: consensusHeight,
   379  				WalletAddress:  w.isWalletAddress(sfo.UnlockHash),
   380  				RelatedAddress: sfo.UnlockHash,
   381  				Value:          sfo.Value,
   382  			}
   383  			pt.Outputs = append(pt.Outputs, po)
   384  			// Log any wallet-relevant outputs.
   385  			if po.WalletAddress {
   386  				w.log.Println("\tSiafund Output:", po.ID, "::", po.Value.HumanString())
   387  			}
   388  		}
   389  
   390  		for _, fee := range txn.MinerFees {
   391  			pt.Outputs = append(pt.Outputs, modules.ProcessedOutput{
   392  				FundType:       types.SpecifierMinerFee,
   393  				MaturityHeight: consensusHeight + types.MaturityDelay,
   394  				Value:          fee,
   395  			})
   396  		}
   397  		pts = append(pts, pt)
   398  	}
   399  	return pts
   400  }
   401  
   402  // applyHistory applies any transaction history that the applied blocks
   403  // introduced.
   404  func (w *Wallet) applyHistory(tx *bolt.Tx, cc modules.ConsensusChange) error {
   405  	spentSiacoinOutputs := computeSpentSiacoinOutputSet(cc.SiacoinOutputDiffs)
   406  	spentSiafundOutputs := computeSpentSiafundOutputSet(cc.SiafundOutputDiffs)
   407  
   408  	for _, block := range cc.AppliedBlocks {
   409  		consensusHeight, err := dbGetConsensusHeight(tx)
   410  		if err != nil {
   411  			return errors.AddContext(err, "failed to consensus height")
   412  		}
   413  		// Increment the consensus height.
   414  		if block.ID() != types.GenesisID {
   415  			consensusHeight++
   416  			err = dbPutConsensusHeight(tx, consensusHeight)
   417  			if err != nil {
   418  				return errors.AddContext(err, "failed to store consensus height in database")
   419  			}
   420  		}
   421  
   422  		pts := w.computeProcessedTransactionsFromBlock(tx, block, spentSiacoinOutputs, spentSiafundOutputs, consensusHeight)
   423  		for _, pt := range pts {
   424  			err := dbAppendProcessedTransaction(tx, pt)
   425  			if err != nil {
   426  				return errors.AddContext(err, "could not put processed transaction")
   427  			}
   428  		}
   429  	}
   430  
   431  	return nil
   432  }
   433  
   434  // ProcessConsensusChange parses a consensus change to update the set of
   435  // confirmed outputs known to the wallet.
   436  func (w *Wallet) ProcessConsensusChange(cc modules.ConsensusChange) {
   437  	if err := w.tg.Add(); err != nil {
   438  		return
   439  	}
   440  	defer w.tg.Done()
   441  
   442  	w.mu.Lock()
   443  	defer w.mu.Unlock()
   444  
   445  	if needRescan, err := w.updateLookahead(w.dbTx, cc); err != nil {
   446  		w.log.Severe("ERROR: failed to update lookahead:", err)
   447  		w.dbRollback = true
   448  	} else if needRescan {
   449  		go w.threadedResetSubscriptions()
   450  	}
   451  	if err := w.updateConfirmedSet(w.dbTx, cc); err != nil {
   452  		w.log.Severe("ERROR: failed to update confirmed set:", err)
   453  		w.dbRollback = true
   454  	}
   455  	if err := w.revertHistory(w.dbTx, cc.RevertedBlocks); err != nil {
   456  		w.log.Severe("ERROR: failed to revert consensus change:", err)
   457  		w.dbRollback = true
   458  	}
   459  	if err := w.applyHistory(w.dbTx, cc); err != nil {
   460  		w.log.Severe("ERROR: failed to apply consensus change:", err)
   461  		w.dbRollback = true
   462  	}
   463  	if err := dbPutConsensusChangeID(w.dbTx, cc.ID); err != nil {
   464  		w.log.Severe("ERROR: failed to update consensus change ID:", err)
   465  		w.dbRollback = true
   466  	}
   467  
   468  	if cc.Synced {
   469  		go w.threadedDefragWallet()
   470  	}
   471  }
   472  
   473  // ReceiveUpdatedUnconfirmedTransactions updates the wallet's unconfirmed
   474  // transaction set.
   475  func (w *Wallet) ReceiveUpdatedUnconfirmedTransactions(diff *modules.TransactionPoolDiff) {
   476  	if err := w.tg.Add(); err != nil {
   477  		return
   478  	}
   479  	defer w.tg.Done()
   480  
   481  	w.mu.Lock()
   482  	defer w.mu.Unlock()
   483  
   484  	// Do the pruning first. If there are any pruned transactions, we will need
   485  	// to re-allocate the whole processed transactions array.
   486  	droppedTransactions := make(map[types.TransactionID]struct{})
   487  	for i := range diff.RevertedTransactions {
   488  		txids := w.unconfirmedSets[diff.RevertedTransactions[i]]
   489  		for i := range txids {
   490  			droppedTransactions[txids[i]] = struct{}{}
   491  		}
   492  		delete(w.unconfirmedSets, diff.RevertedTransactions[i])
   493  	}
   494  
   495  	// Skip the reallocation if we can, otherwise reallocate the
   496  	// unconfirmedProcessedTransactions to no longer have the dropped
   497  	// transactions.
   498  	if len(droppedTransactions) != 0 {
   499  		// Capacity can't be reduced, because we have no way of knowing if the
   500  		// dropped transactions are relevant to the wallet or not, and some will
   501  		// not be relevant to the wallet, meaning they don't have a counterpart
   502  		// in w.unconfirmedProcessedTransactions.
   503  		newUPT := make([]modules.ProcessedTransaction, 0, len(w.unconfirmedProcessedTransactions))
   504  		for _, txn := range w.unconfirmedProcessedTransactions {
   505  			_, exists := droppedTransactions[txn.TransactionID]
   506  			if !exists {
   507  				// Transaction was not dropped, add it to the new unconfirmed
   508  				// transactions.
   509  				newUPT = append(newUPT, txn)
   510  			}
   511  		}
   512  
   513  		// Set the unconfirmed preocessed transactions to the pruned set.
   514  		w.unconfirmedProcessedTransactions = newUPT
   515  	}
   516  
   517  	// Scroll through all of the diffs and add any new transactions.
   518  	for _, unconfirmedTxnSet := range diff.AppliedTransactions {
   519  		// Mark all of the transactions that appeared in this set.
   520  		//
   521  		// TODO: Technically only necessary to mark the ones that are relevant
   522  		// to the wallet, but overhead should be low.
   523  		w.unconfirmedSets[unconfirmedTxnSet.ID] = unconfirmedTxnSet.IDs
   524  
   525  		// Get the values for the spent outputs.
   526  		spentSiacoinOutputs := make(map[types.SiacoinOutputID]types.SiacoinOutput)
   527  		for _, scod := range unconfirmedTxnSet.Change.SiacoinOutputDiffs {
   528  			// Only need to grab the reverted ones, because only reverted ones
   529  			// have the possibility of having been spent.
   530  			if scod.Direction == modules.DiffRevert {
   531  				spentSiacoinOutputs[scod.ID] = scod.SiacoinOutput
   532  			}
   533  		}
   534  
   535  		// Add each transaction to our set of unconfirmed transactions.
   536  		for i, txn := range unconfirmedTxnSet.Transactions {
   537  			// determine whether transaction is relevant to the wallet
   538  			relevant := false
   539  			for _, sci := range txn.SiacoinInputs {
   540  				relevant = relevant || w.isWalletAddress(sci.UnlockConditions.UnlockHash())
   541  			}
   542  			for _, sco := range txn.SiacoinOutputs {
   543  				relevant = relevant || w.isWalletAddress(sco.UnlockHash)
   544  			}
   545  
   546  			// only create a ProcessedTransaction if txn is relevant
   547  			if !relevant {
   548  				continue
   549  			}
   550  
   551  			pt := modules.ProcessedTransaction{
   552  				Transaction:           txn,
   553  				TransactionID:         unconfirmedTxnSet.IDs[i],
   554  				ConfirmationHeight:    types.BlockHeight(math.MaxUint64),
   555  				ConfirmationTimestamp: types.Timestamp(math.MaxUint64),
   556  			}
   557  			for _, sci := range txn.SiacoinInputs {
   558  				pt.Inputs = append(pt.Inputs, modules.ProcessedInput{
   559  					ParentID:       types.OutputID(sci.ParentID),
   560  					FundType:       types.SpecifierSiacoinInput,
   561  					WalletAddress:  w.isWalletAddress(sci.UnlockConditions.UnlockHash()),
   562  					RelatedAddress: sci.UnlockConditions.UnlockHash(),
   563  					Value:          spentSiacoinOutputs[sci.ParentID].Value,
   564  				})
   565  			}
   566  			for i, sco := range txn.SiacoinOutputs {
   567  				pt.Outputs = append(pt.Outputs, modules.ProcessedOutput{
   568  					ID:             types.OutputID(txn.SiacoinOutputID(uint64(i))),
   569  					FundType:       types.SpecifierSiacoinOutput,
   570  					MaturityHeight: types.BlockHeight(math.MaxUint64),
   571  					WalletAddress:  w.isWalletAddress(sco.UnlockHash),
   572  					RelatedAddress: sco.UnlockHash,
   573  					Value:          sco.Value,
   574  				})
   575  			}
   576  			for _, fee := range txn.MinerFees {
   577  				pt.Outputs = append(pt.Outputs, modules.ProcessedOutput{
   578  					FundType: types.SpecifierMinerFee,
   579  					Value:    fee,
   580  				})
   581  			}
   582  			w.unconfirmedProcessedTransactions = append(w.unconfirmedProcessedTransactions, pt)
   583  		}
   584  	}
   585  }