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

     1  package wallet
     2  
     3  import (
     4  	"fmt"
     5  
     6  	"github.com/NebulousLabs/Sia/build"
     7  	"github.com/NebulousLabs/Sia/modules"
     8  	"github.com/NebulousLabs/Sia/persist"
     9  	"github.com/NebulousLabs/Sia/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  	seed             modules.Seed
    61  	siacoinOutputs   map[types.SiacoinOutputID]scannedOutput
    62  	siafundOutputs   map[types.SiafundOutputID]scannedOutput
    63  
    64  	log *persist.Logger
    65  }
    66  
    67  func (s *seedScanner) numKeys() uint64 {
    68  	return uint64(len(s.keys))
    69  }
    70  
    71  // generateKeys generates n additional keys from the seedScanner's seed.
    72  func (s *seedScanner) generateKeys(n uint64) {
    73  	initialProgress := s.numKeys()
    74  	for i, k := range generateKeys(s.seed, initialProgress, n) {
    75  		s.keys[k.UnlockConditions.UnlockHash()] = initialProgress + uint64(i)
    76  	}
    77  }
    78  
    79  // ProcessConsensusChange scans the blockchain for information relevant to the
    80  // seedScanner.
    81  func (s *seedScanner) ProcessConsensusChange(cc modules.ConsensusChange) {
    82  	// update outputs
    83  	for _, diff := range cc.SiacoinOutputDiffs {
    84  		if diff.Direction == modules.DiffApply {
    85  			if index, exists := s.keys[diff.SiacoinOutput.UnlockHash]; exists && diff.SiacoinOutput.Value.Cmp(s.dustThreshold) > 0 {
    86  				s.siacoinOutputs[diff.ID] = scannedOutput{
    87  					id:        types.OutputID(diff.ID),
    88  					value:     diff.SiacoinOutput.Value,
    89  					seedIndex: index,
    90  				}
    91  			}
    92  		} else if diff.Direction == modules.DiffRevert {
    93  			// NOTE: DiffRevert means the output was either spent or was in a
    94  			// block that was reverted.
    95  			if _, exists := s.keys[diff.SiacoinOutput.UnlockHash]; exists {
    96  				delete(s.siacoinOutputs, diff.ID)
    97  			}
    98  		}
    99  	}
   100  	for _, diff := range cc.SiafundOutputDiffs {
   101  		if diff.Direction == modules.DiffApply {
   102  			// do not compare against dustThreshold here; we always want to
   103  			// sweep every siafund found
   104  			if index, exists := s.keys[diff.SiafundOutput.UnlockHash]; exists {
   105  				s.siafundOutputs[diff.ID] = scannedOutput{
   106  					id:        types.OutputID(diff.ID),
   107  					value:     diff.SiafundOutput.Value,
   108  					seedIndex: index,
   109  				}
   110  			}
   111  		} else if diff.Direction == modules.DiffRevert {
   112  			// NOTE: DiffRevert means the output was either spent or was in a
   113  			// block that was reverted.
   114  			if _, exists := s.keys[diff.SiafundOutput.UnlockHash]; exists {
   115  				delete(s.siafundOutputs, diff.ID)
   116  			}
   117  		}
   118  	}
   119  
   120  	// update s.largestIndexSeen
   121  	for _, diff := range cc.SiacoinOutputDiffs {
   122  		index, exists := s.keys[diff.SiacoinOutput.UnlockHash]
   123  		if exists {
   124  			s.log.Debugln("Seed scanner found a key used at index", index)
   125  			if index > s.largestIndexSeen {
   126  				s.largestIndexSeen = index
   127  			}
   128  		}
   129  	}
   130  	for _, diff := range cc.SiafundOutputDiffs {
   131  		index, exists := s.keys[diff.SiafundOutput.UnlockHash]
   132  		if exists {
   133  			s.log.Debugln("Seed scanner found a key used at index", index)
   134  			if index > s.largestIndexSeen {
   135  				s.largestIndexSeen = index
   136  			}
   137  		}
   138  	}
   139  }
   140  
   141  // scan subscribes s to cs and scans the blockchain for addresses that belong
   142  // to s's seed. If scan returns errMaxKeys, additional keys may need to be
   143  // generated to find all the addresses.
   144  func (s *seedScanner) scan(cs modules.ConsensusSet, cancel <-chan struct{}) error {
   145  	// generate a bunch of keys and scan the blockchain looking for them. If
   146  	// none of the 'upper' half of the generated keys are found, we are done;
   147  	// otherwise, generate more keys and try again (bounded by a sane
   148  	// default).
   149  	//
   150  	// NOTE: since scanning is very slow, we aim to only scan once, which
   151  	// means generating many keys.
   152  	var numKeys uint64 = numInitialKeys
   153  	for s.numKeys() < maxScanKeys {
   154  		s.generateKeys(numKeys)
   155  		if err := cs.ConsensusSetSubscribe(s, modules.ConsensusChangeBeginning, cancel); err != nil {
   156  			return err
   157  		}
   158  		cs.Unsubscribe(s)
   159  		if s.largestIndexSeen < s.numKeys()/2 {
   160  			return nil
   161  		}
   162  		// increase number of keys generated each iteration, capping so that
   163  		// we do not exceed maxScanKeys
   164  		numKeys *= scanMultiplier
   165  		if numKeys > maxScanKeys-s.numKeys() {
   166  			numKeys = maxScanKeys - s.numKeys()
   167  		}
   168  	}
   169  	return errMaxKeys
   170  }
   171  
   172  // newSeedScanner returns a new seedScanner.
   173  func newSeedScanner(seed modules.Seed, log *persist.Logger) *seedScanner {
   174  	return &seedScanner{
   175  		seed:           seed,
   176  		keys:           make(map[types.UnlockHash]uint64, numInitialKeys),
   177  		siacoinOutputs: make(map[types.SiacoinOutputID]scannedOutput),
   178  		siafundOutputs: make(map[types.SiafundOutputID]scannedOutput),
   179  
   180  		log: log,
   181  	}
   182  }