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 }