github.com/TrueBlocks/trueblocks-core/src/apps/chifra@v0.0.0-20241022031540-b362680128f7/internal/export/handle_withdrawals.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/types"
    18  	"github.com/TrueBlocks/trueblocks-core/src/apps/chifra/pkg/utils"
    19  )
    20  
    21  func (opts *ExportOptions) HandleWithdrawals(rCtx *output.RenderCtx, monitorArray []monitor.Monitor) error {
    22  	chain := opts.Globals.Chain
    23  	testMode := opts.Globals.TestMode
    24  	nErrors := 0
    25  	first := base.Max(base.KnownBlock(chain, "shanghai"), opts.FirstBlock)
    26  	filter := filter.NewFilter(
    27  		opts.Reversed,
    28  		false,
    29  		[]string{},
    30  		base.BlockRange{First: first, Last: opts.LastBlock},
    31  		// TODO: I feel (but have not investigated) that this may be a misake
    32  		// TODO: Shouldn't the RecordRange start with zero not block number?
    33  		// TODO: It means firstRecord, after all.
    34  		base.RecordRange{First: uint64(first), Last: opts.GetMax()},
    35  	)
    36  
    37  	fetchData := func(modelChan chan types.Modeler, errorChan chan error) {
    38  		for _, mon := range monitorArray {
    39  			if apps, cnt, err := mon.ReadAndFilterAppearances(filter, false /* withCount */); err != nil {
    40  				errorChan <- err
    41  				rCtx.Cancel()
    42  
    43  			} else if cnt == 0 {
    44  				errorChan <- fmt.Errorf("no blocks found for the query")
    45  				continue
    46  
    47  			} else {
    48  				if sliceOfMaps, _, err := types.AsSliceOfMaps[types.LightBlock](apps, filter.Reversed); err != nil {
    49  					errorChan <- err
    50  					rCtx.Cancel()
    51  
    52  				} else {
    53  					showProgress := opts.Globals.ShowProgress()
    54  					bar := logger.NewBar(logger.BarOptions{
    55  						Prefix:  mon.Address.Hex(),
    56  						Enabled: showProgress,
    57  						Total:   int64(cnt),
    58  					})
    59  
    60  					// TODO: BOGUS - THIS IS NOT CONCURRENCY SAFE
    61  					finished := false
    62  					for _, thisMap := range sliceOfMaps {
    63  						if rCtx.WasCanceled() {
    64  							return
    65  						}
    66  
    67  						if finished {
    68  							continue
    69  						}
    70  
    71  						for app := range thisMap {
    72  							thisMap[app] = new(types.LightBlock)
    73  						}
    74  
    75  						iterFunc := func(app types.Appearance, value *types.LightBlock) error {
    76  							var block types.LightBlock
    77  							if block, err = opts.Conn.GetBlockHeaderByNumber(base.Blknum(app.BlockNumber)); err != nil {
    78  								return err
    79  							}
    80  
    81  							withdrawals := make([]types.Withdrawal, 0, 16)
    82  							for _, w := range block.Withdrawals {
    83  								if w.Address == mon.Address {
    84  									withdrawals = append(withdrawals, w)
    85  								}
    86  							}
    87  							if len(withdrawals) > 0 {
    88  								block.Withdrawals = withdrawals
    89  								*value = block
    90  							}
    91  
    92  							bar.Tick()
    93  							return nil
    94  						}
    95  
    96  						iterErrorChan := make(chan error)
    97  						iterCtx, iterCancel := context.WithCancel(context.Background())
    98  						defer iterCancel()
    99  						go utils.IterateOverMap(iterCtx, iterErrorChan, thisMap, iterFunc)
   100  						for err := range iterErrorChan {
   101  							if !testMode || nErrors == 0 {
   102  								errorChan <- err
   103  								nErrors++
   104  							}
   105  						}
   106  
   107  						// Sort the items back into an ordered array by block number
   108  						items := make([]*types.Withdrawal, 0, len(thisMap))
   109  						for _, block := range thisMap {
   110  							for _, with := range block.Withdrawals {
   111  								items = append(items, &with)
   112  							}
   113  						}
   114  
   115  						sort.Slice(items, func(i, j int) bool {
   116  							if opts.Reversed {
   117  								i, j = j, i
   118  							}
   119  							return items[i].BlockNumber < items[j].BlockNumber
   120  						})
   121  
   122  						for _, item := range items {
   123  							var passes bool
   124  							passes, finished = filter.ApplyCountFilter()
   125  							if passes {
   126  								modelChan <- item
   127  							}
   128  							if finished {
   129  								break
   130  							}
   131  						}
   132  					}
   133  					bar.Finish(true /* newLine */)
   134  				}
   135  			}
   136  		}
   137  	}
   138  
   139  	extraOpts := map[string]any{
   140  		"export": true,
   141  	}
   142  
   143  	return output.StreamMany(rCtx, fetchData, opts.Globals.OutputOptsWithExtra(extraOpts))
   144  }