github.com/TrueBlocks/trueblocks-core/src/apps/chifra@v0.0.0-20241022031540-b362680128f7/internal/export/handle_balances.go (about)

     1  // Copyright 2021 The TrueBlocks Authors. All rights reserved.
     2  // Use of this source code is governed by a license that can
     3  // be found in the LICENSE file.
     4  
     5  package exportPkg
     6  
     7  import (
     8  	"context"
     9  	"fmt"
    10  	"sort"
    11  
    12  	"github.com/TrueBlocks/trueblocks-core/src/apps/chifra/pkg/base"
    13  	"github.com/TrueBlocks/trueblocks-core/src/apps/chifra/pkg/filter"
    14  	"github.com/TrueBlocks/trueblocks-core/src/apps/chifra/pkg/logger"
    15  	"github.com/TrueBlocks/trueblocks-core/src/apps/chifra/pkg/monitor"
    16  	"github.com/TrueBlocks/trueblocks-core/src/apps/chifra/pkg/output"
    17  	"github.com/TrueBlocks/trueblocks-core/src/apps/chifra/pkg/tslib"
    18  	"github.com/TrueBlocks/trueblocks-core/src/apps/chifra/pkg/types"
    19  	"github.com/TrueBlocks/trueblocks-core/src/apps/chifra/pkg/utils"
    20  )
    21  
    22  func (opts *ExportOptions) HandleBalances(rCtx *output.RenderCtx, monitorArray []monitor.Monitor) error {
    23  	chain := opts.Globals.Chain
    24  	testMode := opts.Globals.TestMode
    25  	nErrors := 0
    26  
    27  	filter := filter.NewFilter(
    28  		opts.Reversed,
    29  		opts.Reverted,
    30  		opts.Fourbytes,
    31  		base.BlockRange{First: opts.FirstBlock, Last: opts.LastBlock},
    32  		base.RecordRange{First: opts.FirstRecord, Last: opts.GetMax()},
    33  	)
    34  
    35  	fetchData := func(modelChan chan types.Modeler, errorChan chan error) {
    36  		currentBn := base.Blknum(0)
    37  		prevBalance := base.NewWei(0)
    38  
    39  		for _, mon := range monitorArray {
    40  			if apps, cnt, err := mon.ReadAndFilterAppearances(filter, false /* withCount */); err != nil {
    41  				errorChan <- err
    42  				rCtx.Cancel()
    43  
    44  			} else if cnt == 0 {
    45  				errorChan <- fmt.Errorf("no blocks found for the query")
    46  				continue
    47  
    48  			} else {
    49  				if sliceOfMaps, _, err := types.AsSliceOfMaps[types.Token](apps, filter.Reversed); err != nil {
    50  					errorChan <- err
    51  					rCtx.Cancel()
    52  
    53  				} else {
    54  					showProgress := opts.Globals.ShowProgress()
    55  					bar := logger.NewBar(logger.BarOptions{
    56  						Prefix:  mon.Address.Hex(),
    57  						Enabled: showProgress,
    58  						Total:   int64(cnt),
    59  					})
    60  
    61  					// TODO: BOGUS - THIS IS NOT CONCURRENCY SAFE
    62  					finished := false
    63  					prevBalance, _ = opts.Conn.GetBalanceAt(mon.Address, filter.GetOuterBounds().First)
    64  					for _, thisMap := range sliceOfMaps {
    65  						if rCtx.WasCanceled() {
    66  							return
    67  						}
    68  
    69  						if finished {
    70  							continue
    71  						}
    72  
    73  						for app := range thisMap {
    74  							thisMap[app] = new(types.Token)
    75  						}
    76  
    77  						iterFunc := func(app types.Appearance, value *types.Token) error {
    78  							var balance *base.Wei
    79  							if balance, err = opts.Conn.GetBalanceAt(mon.Address, base.Blknum(app.BlockNumber)); err != nil {
    80  								return err
    81  							}
    82  							value.Address = base.FAKE_ETH_ADDRESS
    83  							value.Holder = mon.Address
    84  							value.BlockNumber = base.Blknum(app.BlockNumber)
    85  							value.TransactionIndex = base.Txnum(app.TransactionIndex)
    86  							value.Balance = *balance
    87  							value.Timestamp = app.Timestamp
    88  							bar.Tick()
    89  							return nil
    90  						}
    91  
    92  						iterErrorChan := make(chan error)
    93  						iterCtx, iterCancel := context.WithCancel(context.Background())
    94  						defer iterCancel()
    95  						go utils.IterateOverMap(iterCtx, iterErrorChan, thisMap, iterFunc)
    96  						for err := range iterErrorChan {
    97  							if !testMode || nErrors == 0 {
    98  								errorChan <- err
    99  								nErrors++
   100  							}
   101  						}
   102  
   103  						items := make([]*types.Token, 0, len(thisMap))
   104  						for _, tx := range thisMap {
   105  							items = append(items, tx)
   106  						}
   107  
   108  						sort.Slice(items, func(i, j int) bool {
   109  							if opts.Reversed {
   110  								i, j = j, i
   111  							}
   112  							return items[i].BlockNumber < items[j].BlockNumber
   113  						})
   114  
   115  						for idx, item := range items {
   116  							visitToken := func(idx int, item *types.Token) error {
   117  								item.PriorBalance = *prevBalance
   118  								if item.BlockNumber == 0 || item.BlockNumber != currentBn || item.Timestamp == 0xdeadbeef {
   119  									item.Timestamp, _ = tslib.FromBnToTs(chain, item.BlockNumber)
   120  								}
   121  								currentBn = item.BlockNumber
   122  								if idx == 0 || item.PriorBalance.Cmp(&item.Balance) != 0 || opts.Globals.Verbose {
   123  									var passes bool
   124  									passes, finished = filter.ApplyCountFilter()
   125  									if passes {
   126  										modelChan <- item
   127  									}
   128  								}
   129  								prevBalance = &item.Balance
   130  								return nil
   131  							}
   132  							if err := visitToken(idx, item); err != nil {
   133  								errorChan <- err
   134  								return
   135  							}
   136  							if finished {
   137  								break
   138  							}
   139  						}
   140  					}
   141  					bar.Finish(true /* newLine */)
   142  				}
   143  				prevBalance = base.NewWei(0)
   144  			}
   145  
   146  		}
   147  	}
   148  
   149  	extraOpts := map[string]any{
   150  		"export": true,
   151  		"parts":  []string{"blockNumber", "date", "holder", "balance", "diff", "balanceDec"},
   152  	}
   153  
   154  	return output.StreamMany(rCtx, fetchData, opts.Globals.OutputOptsWithExtra(extraOpts))
   155  }