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  }