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 }