github.com/diadata-org/diadata@v1.4.593/internal/pkg/filtersBlockService/FiltersBlockService.go (about) 1 package filters 2 3 import ( 4 "errors" 5 "sync" 6 "time" 7 8 "github.com/cnf/structhash" 9 "github.com/diadata-org/diadata/pkg/dia" 10 models "github.com/diadata-org/diadata/pkg/model" 11 log "github.com/sirupsen/logrus" 12 ) 13 14 /* 15 const ( 16 filtersParam = dia.BlockSizeSeconds 17 ) 18 */ 19 20 type nothing struct{} 21 22 // getIdentifier returns the unique identifier for asset @a. 23 // It is used as a part of a filter's map's key. 24 func getIdentifier(a dia.Asset) string { 25 return a.Blockchain + "-" + a.Address 26 } 27 28 // filtersAsset is only used in the filters package. It is the auxilliary 29 // structure enabling us to compute prices for both, an asset on one exchange 30 // and an asset across all exchanges. 31 // @Identifier is the asset's unique identifier blockchain+Address 32 type filtersAsset struct { 33 Identifier string 34 Source string 35 } 36 37 // FiltersBlockService is the data structure containing all objects 38 // necessary for the processing of a tradesBlock. 39 type FiltersBlockService struct { 40 shutdown chan nothing 41 shutdownDone chan nothing 42 chanTradesBlock chan *dia.TradesBlock 43 chanFiltersBlock chan *dia.FiltersBlock 44 errorLock sync.RWMutex 45 error error 46 closed bool 47 started bool 48 // currentTime time.Time 49 filters map[filtersAsset][]Filter 50 lastLog time.Time 51 calculationValues []int 52 previousBlockFilters []dia.FilterPoint 53 datastore models.Datastore 54 } 55 56 // NewFiltersBlockService returns a new FiltersBlockService and 57 // runs mainLoop() in a go routine. 58 func NewFiltersBlockService(previousBlockFilters []dia.FilterPoint, datastore models.Datastore, chanFiltersBlock chan *dia.FiltersBlock) *FiltersBlockService { 59 s := &FiltersBlockService{ 60 shutdown: make(chan nothing), 61 shutdownDone: make(chan nothing), 62 chanTradesBlock: make(chan *dia.TradesBlock), 63 chanFiltersBlock: chanFiltersBlock, 64 error: nil, 65 started: false, 66 filters: make(map[filtersAsset][]Filter), 67 lastLog: time.Now(), 68 calculationValues: make([]int, 0), 69 previousBlockFilters: previousBlockFilters, 70 datastore: datastore, 71 } 72 s.calculationValues = append(s.calculationValues, dia.BlockSizeSeconds) 73 74 go s.mainLoop() 75 return s 76 } 77 78 // mainLoop runs processTradesBlock until FiltersBlockService @s is shut down. 79 func (s *FiltersBlockService) mainLoop() { 80 for { 81 log.Info("x FiltersBlockService mainloop") 82 select { 83 case <-s.shutdown: 84 log.Println("Filters shutting down") 85 s.cleanup(nil) 86 return 87 case tb, ok := <-s.chanTradesBlock: 88 log.Info("receive tradesBlock for further processing ok: ", ok) 89 s.processTradesBlock(tb) 90 } 91 } 92 } 93 94 // processTradesBlock is the 'main' function in the sense that all mathematical 95 // computations are done here. 96 func (s *FiltersBlockService) processTradesBlock(tb *dia.TradesBlock) { 97 98 log.Infoln("processTradesBlock starting") 99 t0 := time.Now() 100 101 for _, trade := range tb.TradesBlockData.Trades { 102 s.createFilters(trade.QuoteToken, "", tb.TradesBlockData.BeginTime) 103 s.createFilters(trade.QuoteToken, trade.Source, tb.TradesBlockData.BeginTime) 104 s.computeFilters(trade, "") 105 s.computeFilters(trade, trade.Source) 106 } 107 108 log.Info("time spent for create and compute filters: ", time.Since(t0)) 109 log.Info("filter begin time: ", tb.TradesBlockData.BeginTime) 110 resultFilters := []dia.FilterPoint{} 111 112 t0 = time.Now() 113 114 for _, filters := range s.filters { 115 for _, f := range filters { 116 f.finalCompute(tb.TradesBlockData.EndTime) 117 fp := f.filterPointForBlock() 118 if fp != nil { 119 resultFilters = append(resultFilters, *fp) 120 } 121 } 122 } 123 log.Info("time spent for final compute: ", time.Since(t0)) 124 125 resultFilters = addMissingPoints(s.previousBlockFilters, resultFilters) 126 127 s.previousBlockFilters = resultFilters 128 129 fb := &dia.FiltersBlock{ 130 FiltersBlockData: dia.FiltersBlockData{ 131 FilterPoints: resultFilters, 132 FiltersNumber: len(resultFilters), 133 EndTime: tb.TradesBlockData.EndTime, 134 BeginTime: tb.TradesBlockData.BeginTime, 135 TradesBlockHash: tb.BlockHash, 136 }, 137 } 138 139 hash, err := structhash.Hash(fb.FiltersBlockData, 1) 140 if err != nil { 141 log.Printf("error on hash") 142 hash = "hashError" 143 } 144 fb.BlockHash = hash 145 log.Printf("Generating Filters block %v (size:%v)", hash, fb.FiltersBlockData.FiltersNumber) 146 147 if len(resultFilters) != 0 && s.chanFiltersBlock != nil { 148 s.chanFiltersBlock <- fb 149 } 150 151 t0 = time.Now() 152 for _, filters := range s.filters { 153 for _, f := range filters { 154 err = f.save(s.datastore) 155 if err != nil { 156 log.Error(err) 157 } 158 } 159 } 160 log.Info("time spent for save filters: ", time.Since(t0)) 161 162 err = s.datastore.ExecuteRedisPipe() 163 if err != nil { 164 log.Error("execute redis pipe: ", err) 165 } 166 167 err = s.datastore.FlushRedisPipe() 168 if err != nil { 169 log.Error("flush redis pipe: ", err) 170 } 171 172 err = s.datastore.Flush() 173 if err != nil { 174 log.Error("flush influx batch: ", err) 175 } 176 177 } 178 179 func (s *FiltersBlockService) createFilters(asset dia.Asset, exchange string, BeginTime time.Time) { 180 fa := filtersAsset{ 181 Identifier: getIdentifier(asset), 182 Source: exchange, 183 } 184 _, ok := s.filters[fa] 185 if !ok { 186 s.filters[fa] = []Filter{ 187 NewFilterMA(asset, exchange, BeginTime, dia.BlockSizeSeconds, false), 188 NewFilterMAIR(asset, exchange, BeginTime, dia.BlockSizeSeconds, false), 189 NewFilterMEDIR(asset, exchange, BeginTime, dia.BlockSizeSeconds, false), 190 NewFilterVWAPIR(asset, exchange, BeginTime, dia.BlockSizeSeconds, false), 191 NewFilterVOL(asset, exchange, dia.BlockSizeSeconds), 192 NewFilterCOUNT(asset, exchange, dia.BlockSizeSeconds), 193 NewFilterTLT(asset, exchange), 194 } 195 } 196 } 197 198 func (s *FiltersBlockService) computeFilters(t dia.Trade, exchange string) { 199 fa := filtersAsset{ 200 Identifier: getIdentifier(t.QuoteToken), 201 Source: exchange, 202 } 203 for _, f := range s.filters[fa] { 204 f.compute(t) 205 } 206 } 207 208 func addMissingPoints(previousBlockFilters []dia.FilterPoint, newFilters []dia.FilterPoint) []dia.FilterPoint { 209 log.Debug("previousBlockFilters", previousBlockFilters) 210 log.Debug("newFilters:", newFilters) 211 missingPoints := 0 212 result := newFilters 213 newFiltersMap := make(map[filtersAsset]*dia.FilterPoint) 214 for i, filter := range newFilters { 215 fa := filtersAsset{ 216 Identifier: getIdentifier(filter.Asset), 217 Source: filter.Name, 218 } 219 newFiltersMap[fa] = &newFilters[i] 220 } 221 222 for _, filter := range previousBlockFilters { 223 224 d := time.Since(filter.Time) 225 // log.Info("filter:", filter, " age:", d) 226 fa := filtersAsset{ 227 Identifier: getIdentifier(filter.Asset), 228 Source: filter.Name, 229 } 230 if d > time.Hour*24 { 231 _, ok := newFiltersMap[fa] 232 if !ok { 233 result = append(result, filter) 234 log.Infof("Adding old filter %s on blockchain %s with age %v", filter.Name+filter.Asset.Symbol, filter.Asset.Blockchain, d) 235 missingPoints++ 236 } 237 } else { 238 log.Warnf("ignoring old filter %s on blockchain %s with age %v", filter.Asset.Symbol, filter.Asset.Blockchain, d) 239 } 240 } 241 if missingPoints != 0 { 242 log.Printf("Added %v missing point from previous block", missingPoints) 243 } 244 log.Debug("result:", result) 245 return result 246 } 247 248 // ProcessTradesBlock sends a filled tradesBlock into the filtersBlock channel. 249 func (s *FiltersBlockService) ProcessTradesBlock(tradesBlock *dia.TradesBlock) { 250 s.chanTradesBlock <- tradesBlock 251 log.Info("Processing TradesBlock done.") 252 } 253 254 // Close gracefully closes the Filtersblockservice 255 func (s *FiltersBlockService) Close() error { 256 if s.closed { 257 return errors.New("filters: Already closed") 258 } 259 close(s.shutdown) 260 <-s.shutdownDone 261 return s.error 262 } 263 264 // cleanup must only be called from mainLoop 265 func (s *FiltersBlockService) cleanup(err error) { 266 s.errorLock.Lock() 267 defer s.errorLock.Unlock() 268 if err != nil { 269 s.error = err 270 } 271 s.closed = true 272 close(s.shutdownDone) // signal that shutdown is complete 273 }