github.com/fozzysec/SiaPrime@v0.0.0-20190612043147-66c8e8d11fe3/modules/wallet/update.go (about)

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