github.com/TrueBlocks/trueblocks-core/src/apps/chifra@v0.0.0-20241022031540-b362680128f7/pkg/rpc/provider/provider.go (about) 1 package provider 2 3 import ( 4 "context" 5 "errors" 6 "fmt" 7 "sync" 8 9 "github.com/TrueBlocks/trueblocks-core/src/apps/chifra/pkg/base" 10 "github.com/TrueBlocks/trueblocks-core/src/apps/chifra/pkg/logger" 11 "github.com/TrueBlocks/trueblocks-core/src/apps/chifra/pkg/types" 12 "github.com/TrueBlocks/trueblocks-core/src/apps/chifra/pkg/utils" 13 ) 14 15 // When providers create a new buffered channel, they set this as buffer size. 16 const providerChannelBufferSize = 50 17 18 // SlurpedPageItem carries Transaction (if available) and Appearance (to give more info 19 // about Transaction, like Address). Either Transaction or Appearance comes from 3rd party 20 // RPC provider. For example, from Etherscan we get Transaction, but from Key - Appearance. 21 type SlurpedPageItem struct { 22 Appearance *types.Appearance 23 Transaction *types.Slurp 24 } 25 26 // Provider is an abstraction over different RPC services, like Etherscan or Key 27 type Provider interface { 28 // TransactionsByAddress returns a channel that will be populated with transactions 29 TransactionsByAddress(context.Context, *Query, chan error) chan types.Slurp 30 31 // Appearances returns a channel that will be populated with appearances 32 Appearances(context.Context, *Query, chan error) chan types.Appearance 33 34 // Count returns a channel that will be populated with monitors, one for each address. 35 // These monitors will only have NRecords set. 36 Count(context.Context, *Query, chan error) chan types.Monitor 37 38 // NewPaginator creates and returns Paginator that should be used for given service 39 NewPaginator(*Query) Paginator 40 41 // PrintProgress returns true if Provider should print progress bar 42 PrintProgress() bool 43 SetPrintProgress(bool) 44 } 45 46 // fetchPageFunc is a function that gets a page of data from 3rd party provider 47 type fetchPageFunc func(ctx context.Context, address base.Address, paginator Paginator, resource string) ([]SlurpedPageItem, int, error) 48 49 // fetchAndFilterData gets all pages available for every query.Address and every query.Resource and push individual page items to a channel. 50 // It is a helper that encapsulates logic shared by multiple providers 51 func fetchAndFilterData(ctx context.Context, provider Provider, query *Query, errorChan chan error, fetchPage fetchPageFunc) (resultChan chan SlurpedPageItem) { 52 resultChan = make(chan SlurpedPageItem, providerChannelBufferSize) 53 54 totalFetched := 0 55 totalFiltered := 0 56 57 go func() { 58 defer close(resultChan) 59 60 for _, address := range query.Addresses { 61 for _, resource := range query.Resources { 62 bar := logger.NewBar(logger.BarOptions{ 63 Type: logger.Expanding, 64 Enabled: provider.PrintProgress(), 65 Prefix: fmt.Sprintf("%s %s", utils.FormattedHash(false, address.String()), resource), 66 }) 67 68 // Get a paginator so we can iterate 69 paginator := provider.NewPaginator(query) 70 71 for !paginator.Done() { 72 select { 73 case <-ctx.Done(): 74 return 75 default: 76 if err := paginator.NextPage(); err != nil { 77 errorChan <- err 78 return 79 } 80 page, fetched, err := fetchPage(ctx, address, paginator, resource) 81 totalFetched += fetched 82 if err != nil { 83 errorChan <- err 84 // we break here because if cannot fetch the page, we don't 85 // know if we're finished (we can get into infinite loop) 86 paginator.SetDone(true) 87 break 88 } 89 90 for _, slurpedItem := range page { 91 // Make sure we want this block 92 if ok, err := query.InRange(base.Blknum(slurpedItem.Appearance.BlockNumber)); !ok { 93 if err != nil { 94 errorChan <- err 95 } 96 continue 97 } 98 totalFiltered++ 99 bar.Tick() 100 101 resultChan <- slurpedItem 102 } 103 } 104 } 105 bar.Finish(true /* newLine */) 106 } 107 if totalFiltered == 0 { 108 msg := fmt.Sprintf("zero transactions reported, %d fetched", totalFetched) 109 errorChan <- errors.New(msg) 110 } 111 } 112 }() 113 114 return 115 } 116 117 // countSlurped turns a channel of SlurpedPageItem into a channel of Monitors. 118 // It is a helper that encapsulates logic shared by multiple providers 119 func countSlurped(ctx context.Context, query *Query, slurpedChan chan SlurpedPageItem) (monitorChan chan types.Monitor) { 120 monitorChan = make(chan types.Monitor) 121 122 recordCount := make(map[base.Address]types.Monitor, len(query.Addresses)) 123 var mu sync.Mutex 124 125 go func() { 126 defer close(monitorChan) 127 for { 128 select { 129 case <-ctx.Done(): 130 return 131 case slurpedTx, ok := <-slurpedChan: 132 if !ok { 133 for _, monitor := range recordCount { 134 monitorChan <- monitor 135 } 136 return 137 } 138 mu.Lock() 139 address := slurpedTx.Appearance.Address 140 monitor := recordCount[address] 141 recordCount[address] = types.Monitor{ 142 Address: address, 143 NRecords: monitor.NRecords + 1, 144 } 145 mu.Unlock() 146 } 147 } 148 }() 149 150 return 151 }