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

     1  // Copyright 2016, 2024 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   * Parts of this file were auto generated. Edit only those parts of
     6   * the code inside of 'EXISTING_CODE' tags.
     7   */
     8  
     9  package exportPkg
    10  
    11  import (
    12  	// EXISTING_CODE
    13  	"encoding/json"
    14  	"io"
    15  	"net/http"
    16  	"net/url"
    17  	"strings"
    18  
    19  	"github.com/TrueBlocks/trueblocks-core/src/apps/chifra/internal/globals"
    20  	"github.com/TrueBlocks/trueblocks-core/src/apps/chifra/pkg/base"
    21  	"github.com/TrueBlocks/trueblocks-core/src/apps/chifra/pkg/caps"
    22  	"github.com/TrueBlocks/trueblocks-core/src/apps/chifra/pkg/logger"
    23  	"github.com/TrueBlocks/trueblocks-core/src/apps/chifra/pkg/rpc"
    24  	"github.com/TrueBlocks/trueblocks-core/src/apps/chifra/pkg/validate"
    25  	"github.com/TrueBlocks/trueblocks-core/src/apps/chifra/pkg/walk"
    26  	// EXISTING_CODE
    27  )
    28  
    29  // ExportOptions provides all command options for the chifra export command.
    30  type ExportOptions struct {
    31  	Addrs       []string              `json:"addrs,omitempty"`       // One or more addresses (0x...) to export
    32  	Topics      []string              `json:"topics,omitempty"`      // Filter by one or more log topics (only for --logs option)
    33  	Fourbytes   []string              `json:"fourbytes,omitempty"`   // Filter by one or more fourbytes (only for transactions and trace options)
    34  	Appearances bool                  `json:"appearances,omitempty"` // Export a list of appearances
    35  	Receipts    bool                  `json:"receipts,omitempty"`    // Export receipts instead of transactional data
    36  	Logs        bool                  `json:"logs,omitempty"`        // Export logs instead of transactional data
    37  	Traces      bool                  `json:"traces,omitempty"`      // Export traces instead of transactional data
    38  	Neighbors   bool                  `json:"neighbors,omitempty"`   // Export the neighbors of the given address
    39  	Accounting  bool                  `json:"accounting,omitempty"`  // Attach accounting records to the exported data (applies to transactions export only)
    40  	Statements  bool                  `json:"statements,omitempty"`  // For the accounting options only, export only statements
    41  	Balances    bool                  `json:"balances,omitempty"`    // Traverse the transaction history and show each change in ETH balances
    42  	Withdrawals bool                  `json:"withdrawals,omitempty"` // Export withdrawals for the given address
    43  	Articulate  bool                  `json:"articulate,omitempty"`  // Articulate transactions, traces, logs, and outputs
    44  	CacheTraces bool                  `json:"cacheTraces,omitempty"` // Force the transaction's traces into the cache
    45  	Count       bool                  `json:"count,omitempty"`       // For --appearances mode only, display only the count of records
    46  	FirstRecord uint64                `json:"firstRecord,omitempty"` // The first record to process
    47  	MaxRecords  uint64                `json:"maxRecords,omitempty"`  // The maximum number of records to process
    48  	Relevant    bool                  `json:"relevant,omitempty"`    // For log and accounting export only, export only logs relevant to one of the given export addresses
    49  	Emitter     []string              `json:"emitter,omitempty"`     // For the --logs option only, filter logs to show only those logs emitted by the given address(es)
    50  	Topic       []string              `json:"topic,omitempty"`       // For the --logs option only, filter logs to show only those with this topic(s)
    51  	Reverted    bool                  `json:"reverted,omitempty"`    // Export only transactions that were reverted
    52  	Asset       []string              `json:"asset,omitempty"`       // For the accounting options only, export statements only for this asset
    53  	Flow        string                `json:"flow,omitempty"`        // For the accounting options only, export statements with incoming, outgoing, or zero value
    54  	Factory     bool                  `json:"factory,omitempty"`     // For --traces only, report addresses created by (or self-destructed by) the given address(es)
    55  	Unripe      bool                  `json:"unripe,omitempty"`      // Export transactions labeled unripe (i.e. less than 28 blocks old)
    56  	Reversed    bool                  `json:"reversed,omitempty"`    // Produce results in reverse chronological order
    57  	NoZero      bool                  `json:"noZero,omitempty"`      // For the --count option only, suppress the display of zero appearance accounts
    58  	FirstBlock  base.Blknum           `json:"firstBlock,omitempty"`  // First block to process (inclusive)
    59  	LastBlock   base.Blknum           `json:"lastBlock,omitempty"`   // Last block to process (inclusive)
    60  	Globals     globals.GlobalOptions `json:"globals,omitempty"`     // The global options
    61  	Conn        *rpc.Connection       `json:"conn,omitempty"`        // The connection to the RPC server
    62  	BadFlag     error                 `json:"badFlag,omitempty"`     // An error flag if needed
    63  	// EXISTING_CODE
    64  	// EXISTING_CODE
    65  }
    66  
    67  var defaultExportOptions = ExportOptions{
    68  	MaxRecords: 250,
    69  	LastBlock:  base.NOPOSN,
    70  }
    71  
    72  // testLog is used only during testing to export the options for this test case.
    73  func (opts *ExportOptions) testLog() {
    74  	logger.TestLog(len(opts.Addrs) > 0, "Addrs: ", opts.Addrs)
    75  	logger.TestLog(len(opts.Topics) > 0, "Topics: ", opts.Topics)
    76  	logger.TestLog(len(opts.Fourbytes) > 0, "Fourbytes: ", opts.Fourbytes)
    77  	logger.TestLog(opts.Appearances, "Appearances: ", opts.Appearances)
    78  	logger.TestLog(opts.Receipts, "Receipts: ", opts.Receipts)
    79  	logger.TestLog(opts.Logs, "Logs: ", opts.Logs)
    80  	logger.TestLog(opts.Traces, "Traces: ", opts.Traces)
    81  	logger.TestLog(opts.Neighbors, "Neighbors: ", opts.Neighbors)
    82  	logger.TestLog(opts.Accounting, "Accounting: ", opts.Accounting)
    83  	logger.TestLog(opts.Statements, "Statements: ", opts.Statements)
    84  	logger.TestLog(opts.Balances, "Balances: ", opts.Balances)
    85  	logger.TestLog(opts.Withdrawals, "Withdrawals: ", opts.Withdrawals)
    86  	logger.TestLog(opts.Articulate, "Articulate: ", opts.Articulate)
    87  	logger.TestLog(opts.CacheTraces, "CacheTraces: ", opts.CacheTraces)
    88  	logger.TestLog(opts.Count, "Count: ", opts.Count)
    89  	logger.TestLog(opts.FirstRecord != 0, "FirstRecord: ", opts.FirstRecord)
    90  	logger.TestLog(opts.MaxRecords != 250, "MaxRecords: ", opts.MaxRecords)
    91  	logger.TestLog(opts.Relevant, "Relevant: ", opts.Relevant)
    92  	logger.TestLog(len(opts.Emitter) > 0, "Emitter: ", opts.Emitter)
    93  	logger.TestLog(len(opts.Topic) > 0, "Topic: ", opts.Topic)
    94  	logger.TestLog(opts.Reverted, "Reverted: ", opts.Reverted)
    95  	logger.TestLog(len(opts.Asset) > 0, "Asset: ", opts.Asset)
    96  	logger.TestLog(len(opts.Flow) > 0, "Flow: ", opts.Flow)
    97  	logger.TestLog(opts.Factory, "Factory: ", opts.Factory)
    98  	logger.TestLog(opts.Unripe, "Unripe: ", opts.Unripe)
    99  	logger.TestLog(opts.Reversed, "Reversed: ", opts.Reversed)
   100  	logger.TestLog(opts.NoZero, "NoZero: ", opts.NoZero)
   101  	logger.TestLog(opts.FirstBlock != 0, "FirstBlock: ", opts.FirstBlock)
   102  	logger.TestLog(opts.LastBlock != base.NOPOSN && opts.LastBlock != 0, "LastBlock: ", opts.LastBlock)
   103  	opts.Conn.TestLog(opts.getCaches())
   104  	opts.Globals.TestLog()
   105  }
   106  
   107  // String implements the Stringer interface
   108  func (opts *ExportOptions) String() string {
   109  	b, _ := json.MarshalIndent(opts, "", "  ")
   110  	return string(b)
   111  }
   112  
   113  // exportFinishParseApi finishes the parsing for server invocations. Returns a new ExportOptions.
   114  func exportFinishParseApi(w http.ResponseWriter, r *http.Request) *ExportOptions {
   115  	values := r.URL.Query()
   116  	if r.Header.Get("User-Agent") == "testRunner" {
   117  		values.Set("testRunner", "true")
   118  	}
   119  	return ExportFinishParseInternal(w, values)
   120  }
   121  
   122  func ExportFinishParseInternal(w io.Writer, values url.Values) *ExportOptions {
   123  	copy := defaultExportOptions
   124  	copy.Globals.Caps = getCaps()
   125  	opts := &copy
   126  	opts.MaxRecords = 250
   127  	opts.LastBlock = base.NOPOSN
   128  	for key, value := range values {
   129  		switch key {
   130  		case "addrs":
   131  			for _, val := range value {
   132  				s := strings.Split(val, " ") // may contain space separated items
   133  				opts.Addrs = append(opts.Addrs, s...)
   134  			}
   135  		case "topics":
   136  			for _, val := range value {
   137  				s := strings.Split(val, " ") // may contain space separated items
   138  				opts.Topics = append(opts.Topics, s...)
   139  			}
   140  		case "fourbytes":
   141  			for _, val := range value {
   142  				s := strings.Split(val, " ") // may contain space separated items
   143  				opts.Fourbytes = append(opts.Fourbytes, s...)
   144  			}
   145  		case "appearances":
   146  			opts.Appearances = true
   147  		case "receipts":
   148  			opts.Receipts = true
   149  		case "logs":
   150  			opts.Logs = true
   151  		case "traces":
   152  			opts.Traces = true
   153  		case "neighbors":
   154  			opts.Neighbors = true
   155  		case "accounting":
   156  			opts.Accounting = true
   157  		case "statements":
   158  			opts.Statements = true
   159  		case "balances":
   160  			opts.Balances = true
   161  		case "withdrawals":
   162  			opts.Withdrawals = true
   163  		case "articulate":
   164  			opts.Articulate = true
   165  		case "cacheTraces":
   166  			opts.CacheTraces = true
   167  		case "count":
   168  			opts.Count = true
   169  		case "firstRecord":
   170  			opts.FirstRecord = base.MustParseUint64(value[0])
   171  		case "maxRecords":
   172  			opts.MaxRecords = base.MustParseUint64(value[0])
   173  		case "relevant":
   174  			opts.Relevant = true
   175  		case "emitter":
   176  			for _, val := range value {
   177  				s := strings.Split(val, " ") // may contain space separated items
   178  				opts.Emitter = append(opts.Emitter, s...)
   179  			}
   180  		case "topic":
   181  			for _, val := range value {
   182  				s := strings.Split(val, " ") // may contain space separated items
   183  				opts.Topic = append(opts.Topic, s...)
   184  			}
   185  		case "reverted":
   186  			opts.Reverted = true
   187  		case "asset":
   188  			for _, val := range value {
   189  				s := strings.Split(val, " ") // may contain space separated items
   190  				opts.Asset = append(opts.Asset, s...)
   191  			}
   192  		case "flow":
   193  			opts.Flow = value[0]
   194  		case "factory":
   195  			opts.Factory = true
   196  		case "unripe":
   197  			opts.Unripe = true
   198  		case "reversed":
   199  			opts.Reversed = true
   200  		case "noZero":
   201  			opts.NoZero = true
   202  		case "firstBlock":
   203  			opts.FirstBlock = base.MustParseBlknum(value[0])
   204  		case "lastBlock":
   205  			opts.LastBlock = base.MustParseBlknum(value[0])
   206  		default:
   207  			if !copy.Globals.Caps.HasKey(key) {
   208  				err := validate.Usage("Invalid key ({0}) in {1} route.", key, "export")
   209  				if opts.BadFlag == nil || opts.BadFlag.Error() > err.Error() {
   210  					opts.BadFlag = err
   211  				}
   212  			}
   213  		}
   214  	}
   215  	opts.Conn = opts.Globals.FinishParseApi(w, values, opts.getCaches())
   216  
   217  	// EXISTING_CODE
   218  	if len(opts.Addrs) > 0 {
   219  		addrs := []string{}
   220  		for _, addr := range opts.Addrs {
   221  			if validate.IsValidTopic(addr) {
   222  				opts.Topic = append(opts.Topic, addr)
   223  				opts.Topics = append(opts.Topics, addr)
   224  			} else if validate.IsValidFourByte(addr) {
   225  				opts.Fourbytes = append(opts.Fourbytes, addr)
   226  			} else {
   227  				addrs = append(addrs, addr)
   228  			}
   229  		}
   230  		opts.Addrs = addrs
   231  	}
   232  	// EXISTING_CODE
   233  	opts.Addrs, _ = opts.Conn.GetEnsAddresses(opts.Addrs)
   234  	opts.Emitter, _ = opts.Conn.GetEnsAddresses(opts.Emitter)
   235  	opts.Asset, _ = opts.Conn.GetEnsAddresses(opts.Asset)
   236  
   237  	return opts
   238  }
   239  
   240  // exportFinishParse finishes the parsing for command line invocations. Returns a new ExportOptions.
   241  func exportFinishParse(args []string) *ExportOptions {
   242  	// remove duplicates from args if any (not needed in api mode because the server does it).
   243  	dedup := map[string]int{}
   244  	if len(args) > 0 {
   245  		tmp := []string{}
   246  		for _, arg := range args {
   247  			if value := dedup[arg]; value == 0 {
   248  				tmp = append(tmp, arg)
   249  			}
   250  			dedup[arg]++
   251  		}
   252  		args = tmp
   253  	}
   254  
   255  	defFmt := "txt"
   256  	opts := GetOptions()
   257  	opts.Conn = opts.Globals.FinishParse(args, opts.getCaches())
   258  
   259  	// EXISTING_CODE
   260  	for _, arg := range args {
   261  		if validate.IsValidTopic(arg) {
   262  			opts.Topic = append(opts.Topic, arg)
   263  			opts.Topics = append(opts.Topics, arg)
   264  		} else if validate.IsValidFourByte(arg) {
   265  			opts.Fourbytes = append(opts.Fourbytes, arg)
   266  		} else {
   267  			opts.Addrs = append(opts.Addrs, arg)
   268  		}
   269  	}
   270  	// EXISTING_CODE
   271  	opts.Addrs, _ = opts.Conn.GetEnsAddresses(opts.Addrs)
   272  	opts.Emitter, _ = opts.Conn.GetEnsAddresses(opts.Emitter)
   273  	opts.Asset, _ = opts.Conn.GetEnsAddresses(opts.Asset)
   274  	if len(opts.Globals.Format) == 0 || opts.Globals.Format == "none" {
   275  		opts.Globals.Format = defFmt
   276  	}
   277  
   278  	return opts
   279  }
   280  
   281  func GetOptions() *ExportOptions {
   282  	// EXISTING_CODE
   283  	// EXISTING_CODE
   284  	return &defaultExportOptions
   285  }
   286  
   287  func getCaps() caps.Capability {
   288  	var capabilities caps.Capability // capabilities for chifra export
   289  	capabilities = capabilities.Add(caps.Default)
   290  	capabilities = capabilities.Add(caps.Caching)
   291  	capabilities = capabilities.Add(caps.Ether)
   292  	capabilities = capabilities.Add(caps.Names)
   293  	// EXISTING_CODE
   294  	// EXISTING_CODE
   295  	return capabilities
   296  }
   297  
   298  func ResetOptions(testMode bool) {
   299  	// We want to keep writer between command file calls
   300  	w := GetOptions().Globals.Writer
   301  	opts := ExportOptions{}
   302  	globals.SetDefaults(&opts.Globals)
   303  	opts.Globals.TestMode = testMode
   304  	opts.Globals.Writer = w
   305  	opts.Globals.Caps = getCaps()
   306  	opts.MaxRecords = 250
   307  	opts.LastBlock = base.NOPOSN
   308  	defaultExportOptions = opts
   309  }
   310  
   311  func (opts *ExportOptions) getCaches() (caches map[walk.CacheType]bool) {
   312  	// EXISTING_CODE
   313  	caches = map[walk.CacheType]bool{
   314  		// TODO: Enable neighbors cache
   315  		walk.Cache_Transactions: true,
   316  		walk.Cache_Statements:   opts.Accounting,
   317  		walk.Cache_Traces:       opts.CacheTraces || (opts.Globals.Cache && (opts.Traces || opts.Neighbors)),
   318  	}
   319  	// EXISTING_CODE
   320  	return
   321  }
   322  
   323  // EXISTING_CODE
   324  // Validate calls into the opts validateExport routine
   325  func (opts *ExportOptions) Validate() error {
   326  	return opts.validateExport()
   327  }
   328  
   329  // TODO: If an abi file is newer than the monitor file - clear the cache
   330  // TODO: accounting disallows freshen, apps, logs, receipts, statements, traces, but requires articulate
   331  // TODO: accounting must be for one monitor address - why?
   332  // TODO: accounting requires node balances - why?
   333  // TODO: Used to do this: if any ABI files was newer, re-read abi and re-articulate in cache
   334  // TODO: Reconciliation loads traces -- plus it reduplicates the isSuicide, isGeneration, isUncle shit
   335  // TODO: If a monitor file is locked, remove the lock and move on (don't read) but don't wait either
   336  
   337  // TODO: In the old C++ code, we used to be able to customize the display of the output with a configuration string. This is a VERY important feature as it captures users
   338  // TODO: In the old C++ code, the first address on the command line was `accountedFor`. Is that still true? Or do we now do accounting for multiple addresses? There should be testing.
   339  // TODO: In the old C++ code, we used to load knownABIs if we were articulating. Is this still true? Do we load the known ABIs and then overlay them with contract specific clashes? (We should.)
   340  // TODO: In the old C++ code, we used to be able to configure certain things - for example, `--cache` is on by default for all queries, `--cache_traces` is on by default, display strings, max_records, max_traces for dDos protection, etc.
   341  // TODO: Need much better testing surrounding fourBytes, topics, emitters, relevant, etc. It's also very poorly documented.
   342  // TODO: In the old C++ code, the ArticulateAll routine used to identify transactions as token related. Do we still do that? Must we? Why did we do that?
   343  
   344  // EXISTING_CODE