gitlab.com/SiaPrime/SiaPrime@v1.4.1/modules/wallet/scan.go (about)

     1  package wallet
     2  
     3  import (
     4  	"fmt"
     5  
     6  	"gitlab.com/SiaPrime/SiaPrime/build"
     7  	"gitlab.com/SiaPrime/SiaPrime/modules"
     8  	"gitlab.com/SiaPrime/SiaPrime/persist"
     9  	"gitlab.com/SiaPrime/SiaPrime/types"
    10  )
    11  
    12  const scanMultiplier = 4 // how many more keys to generate after each scan iteration
    13  
    14  var errMaxKeys = fmt.Errorf("refused to generate more than %v keys from seed", maxScanKeys)
    15  
    16  // maxScanKeys is the number of maximum number of keys the seedScanner will
    17  // generate before giving up.
    18  var maxScanKeys = func() uint64 {
    19  	switch build.Release {
    20  	case "dev":
    21  		return 1e6
    22  	case "standard":
    23  		return 100e6
    24  	case "testing":
    25  		return 100e3
    26  	default:
    27  		panic("unrecognized build.Release")
    28  	}
    29  }()
    30  
    31  // numInitialKeys is the number of keys generated by the seedScanner before
    32  // scanning the blockchain for the first time.
    33  var numInitialKeys = func() uint64 {
    34  	switch build.Release {
    35  	case "dev":
    36  		return 10e3
    37  	case "standard":
    38  		return 1e6
    39  	case "testing":
    40  		return 1e3
    41  	default:
    42  		panic("unrecognized build.Release")
    43  	}
    44  }()
    45  
    46  // A scannedOutput is an output found in the blockchain that was generated
    47  // from a given seed.
    48  type scannedOutput struct {
    49  	id        types.OutputID
    50  	value     types.Currency
    51  	seedIndex uint64
    52  }
    53  
    54  // A seedScanner scans the blockchain for addresses that belong to a given
    55  // seed.
    56  type seedScanner struct {
    57  	dustThreshold    types.Currency              // minimum value of outputs to be included
    58  	keys             map[types.UnlockHash]uint64 // map address to seed index
    59  	largestIndexSeen uint64                      // largest index that has appeared in the blockchain
    60  	scannedHeight    types.BlockHeight
    61  	seed             modules.Seed
    62  	siacoinOutputs   map[types.SiacoinOutputID]scannedOutput
    63  	siafundOutputs   map[types.SiafundOutputID]scannedOutput
    64  
    65  	log *persist.Logger
    66  }
    67  
    68  func (s *seedScanner) numKeys() uint64 {
    69  	return uint64(len(s.keys))
    70  }
    71  
    72  // generateKeys generates n additional keys from the seedScanner's seed.
    73  func (s *seedScanner) generateKeys(n uint64) {
    74  	initialProgress := s.numKeys()
    75  	for i, k := range generateKeys(s.seed, initialProgress, n) {
    76  		s.keys[k.UnlockConditions.UnlockHash()] = initialProgress + uint64(i)
    77  	}
    78  }
    79  
    80  // ProcessConsensusChange scans the blockchain for information relevant to the
    81  // seedScanner.
    82  func (s *seedScanner) ProcessConsensusChange(cc modules.ConsensusChange) {
    83  	// update outputs
    84  	for _, diff := range cc.SiacoinOutputDiffs {
    85  		if diff.Direction == modules.DiffApply {
    86  			if index, exists := s.keys[diff.SiacoinOutput.UnlockHash]; exists && diff.SiacoinOutput.Value.Cmp(s.dustThreshold) > 0 {
    87  				s.siacoinOutputs[diff.ID] = scannedOutput{
    88  					id:        types.OutputID(diff.ID),
    89  					value:     diff.SiacoinOutput.Value,
    90  					seedIndex: index,
    91  				}
    92  			}
    93  		} else if diff.Direction == modules.DiffRevert {
    94  			// NOTE: DiffRevert means the output was either spent or was in a
    95  			// block that was reverted.
    96  			if _, exists := s.keys[diff.SiacoinOutput.UnlockHash]; exists {
    97  				delete(s.siacoinOutputs, diff.ID)
    98  			}
    99  		}
   100  	}
   101  	for _, diff := range cc.SiafundOutputDiffs {
   102  		if diff.Direction == modules.DiffApply {
   103  			// do not compare against dustThreshold here; we always want to
   104  			// sweep every siafund found
   105  			if index, exists := s.keys[diff.SiafundOutput.UnlockHash]; exists {
   106  				s.siafundOutputs[diff.ID] = scannedOutput{
   107  					id:        types.OutputID(diff.ID),
   108  					value:     diff.SiafundOutput.Value,
   109  					seedIndex: index,
   110  				}
   111  			}
   112  		} else if diff.Direction == modules.DiffRevert {
   113  			// NOTE: DiffRevert means the output was either spent or was in a
   114  			// block that was reverted.
   115  			if _, exists := s.keys[diff.SiafundOutput.UnlockHash]; exists {
   116  				delete(s.siafundOutputs, diff.ID)
   117  			}
   118  		}
   119  	}
   120  
   121  	// update s.largestIndexSeen
   122  	for _, diff := range cc.SiacoinOutputDiffs {
   123  		index, exists := s.keys[diff.SiacoinOutput.UnlockHash]
   124  		if exists {
   125  			s.log.Debugln("Seed scanner found a key used at index", index)
   126  			if index > s.largestIndexSeen {
   127  				s.largestIndexSeen = index
   128  			}
   129  		}
   130  	}
   131  	for _, diff := range cc.SiafundOutputDiffs {
   132  		index, exists := s.keys[diff.SiafundOutput.UnlockHash]
   133  		if exists {
   134  			s.log.Debugln("Seed scanner found a key used at index", index)
   135  			if index > s.largestIndexSeen {
   136  				s.largestIndexSeen = index
   137  			}
   138  		}
   139  	}
   140  	// Adjust the scanned height and print the scan progress.
   141  	s.scannedHeight += types.BlockHeight(len(cc.AppliedBlocks) - len(cc.RevertedBlocks))
   142  	if !cc.Synced {
   143  		print("\rWallet: scanned to height ", s.scannedHeight, "...")
   144  	} else {
   145  		println("\nDone!")
   146  	}
   147  }
   148  
   149  // scan subscribes s to cs and scans the blockchain for addresses that belong
   150  // to s's seed. If scan returns errMaxKeys, additional keys may need to be
   151  // generated to find all the addresses.
   152  func (s *seedScanner) scan(cs modules.ConsensusSet, cancel <-chan struct{}) error {
   153  	// generate a bunch of keys and scan the blockchain looking for them. If
   154  	// none of the 'upper' half of the generated keys are found, we are done;
   155  	// otherwise, generate more keys and try again (bounded by a sane
   156  	// default).
   157  	//
   158  	// NOTE: since scanning is very slow, we aim to only scan once, which
   159  	// means generating many keys.
   160  	numKeys := numInitialKeys
   161  	for s.numKeys() < maxScanKeys {
   162  		s.generateKeys(numKeys)
   163  		if err := cs.ConsensusSetSubscribe(s, modules.ConsensusChangeBeginning, cancel); err != nil {
   164  			return err
   165  		}
   166  		cs.Unsubscribe(s)
   167  		if s.largestIndexSeen < s.numKeys()/2 {
   168  			return nil
   169  		}
   170  		// increase number of keys generated each iteration, capping so that
   171  		// we do not exceed maxScanKeys
   172  		numKeys *= scanMultiplier
   173  		if numKeys > maxScanKeys-s.numKeys() {
   174  			numKeys = maxScanKeys - s.numKeys()
   175  		}
   176  	}
   177  	return errMaxKeys
   178  }
   179  
   180  // newSeedScanner returns a new seedScanner.
   181  func newSeedScanner(seed modules.Seed, log *persist.Logger) *seedScanner {
   182  	return &seedScanner{
   183  		seed:           seed,
   184  		keys:           make(map[types.UnlockHash]uint64, numInitialKeys),
   185  		siacoinOutputs: make(map[types.SiacoinOutputID]scannedOutput),
   186  		siafundOutputs: make(map[types.SiafundOutputID]scannedOutput),
   187  
   188  		log: log,
   189  	}
   190  }