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 }