github.com/TrueBlocks/trueblocks-core/src/apps/chifra@v0.0.0-20241022031540-b362680128f7/internal/export/validate.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  	"errors"
     9  	"fmt"
    10  	"strings"
    11  
    12  	"github.com/TrueBlocks/trueblocks-core/src/apps/chifra/pkg/base"
    13  	"github.com/TrueBlocks/trueblocks-core/src/apps/chifra/pkg/config"
    14  	"github.com/TrueBlocks/trueblocks-core/src/apps/chifra/pkg/index"
    15  	"github.com/TrueBlocks/trueblocks-core/src/apps/chifra/pkg/logger"
    16  	"github.com/TrueBlocks/trueblocks-core/src/apps/chifra/pkg/validate"
    17  )
    18  
    19  func (opts *ExportOptions) validateExport() error {
    20  	chain := opts.Globals.Chain
    21  	opts.testLog()
    22  
    23  	if opts.BadFlag != nil {
    24  		return opts.BadFlag
    25  	}
    26  
    27  	if opts.LastBlock == 0 {
    28  		opts.LastBlock = base.NOPOSN
    29  	}
    30  
    31  	if opts.MaxRecords == 0 {
    32  		opts.MaxRecords = 250
    33  	}
    34  
    35  	if !config.IsChainConfigured(chain) {
    36  		return validate.Usage("chain {0} is not properly configured.", chain)
    37  	}
    38  
    39  	key := config.GetKey("trueblocks").License
    40  	if opts.Neighbors && !strings.Contains(key, "+neighbors") {
    41  		return validate.Usage("The {0} option requires a license key. Please contact us in our discord.", "--neighbors")
    42  	}
    43  	if opts.Accounting && !strings.Contains(key, "+accounting") {
    44  		return validate.Usage("The {0} option requires a license key. Please contact us in our discord.", "--accounting")
    45  	}
    46  
    47  	if opts.tooMany() {
    48  		return validate.Usage("Please choose only a single mode (--appearances, --logs, etc.)")
    49  	}
    50  
    51  	if opts.Traces {
    52  		err, ok := opts.Conn.IsNodeTracing()
    53  		if !ok {
    54  			return validate.Usage("{0} requires tracing, err: {1}", "chifra export --traces", err.Error())
    55  		}
    56  	}
    57  
    58  	if opts.Count {
    59  		if opts.Logs || opts.Traces || opts.Neighbors {
    60  			return validate.Usage("The {0} option is not available{1}.", "--count", " with --logs, --traces, or --neighbors")
    61  		}
    62  	}
    63  
    64  	if err := validate.ValidateAtLeastOneAddr(opts.Addrs); err != nil {
    65  		for _, a := range opts.Addrs {
    66  			if !base.IsValidAddress(a) {
    67  				logger.Error("Invalid address: {0}", a)
    68  			}
    69  		}
    70  		return err
    71  	}
    72  
    73  	for _, a := range opts.Addrs {
    74  		if !base.IsValidAddress(a) {
    75  			if len(a) < 10 {
    76  				return validate.Usage("Invalid fourbyte: {0}", a)
    77  			} else if len(a) > 60 {
    78  				return validate.Usage("Invalid hash: {0}", a)
    79  			} else {
    80  				return validate.Usage("Invalid address: {0}", a)
    81  			}
    82  		}
    83  	}
    84  
    85  	if opts.Globals.TestMode && opts.Unripe {
    86  		return validate.Usage("--unripe is disabled for testing.")
    87  	}
    88  
    89  	if opts.LastBlock == 0 {
    90  		opts.LastBlock = base.NOPOSN
    91  	}
    92  
    93  	if opts.FirstBlock >= opts.LastBlock {
    94  		msg := fmt.Sprintf("first_block (%d) must be strictly earlier than last_block (%d).", opts.FirstBlock, opts.LastBlock)
    95  		return validate.Usage(msg)
    96  	}
    97  
    98  	if opts.LastBlock != base.NOPOSN {
    99  		latest := opts.Conn.GetLatestBlockNumber()
   100  		if opts.LastBlock > latest {
   101  			msg := fmt.Sprintf("latest block (%d) must be before the chain's latest block (%d).", opts.LastBlock, latest)
   102  			return validate.Usage(msg)
   103  		}
   104  	}
   105  
   106  	if opts.Logs {
   107  		for _, e := range opts.Emitter {
   108  			if !base.IsValidAddress(e) {
   109  				return validate.Usage("Invalid emitter: {0}", e)
   110  			}
   111  		}
   112  		for _, t := range opts.Topics {
   113  			if !validate.IsValidHash(t) {
   114  				return validate.Usage("Invalid topic: {0}", t)
   115  			}
   116  		}
   117  		for _, t := range opts.Topic {
   118  			if !validate.IsValidHash(t) {
   119  				return validate.Usage("Invalid topic: {0}", t)
   120  			}
   121  		}
   122  	} else {
   123  		if len(opts.Emitter) > 0 {
   124  			return validate.Usage("The {0} option is only available with the {1} option.", "--emitter", "--logs")
   125  		}
   126  		if !opts.Receipts && len(opts.Topics) > 0 {
   127  			return validate.Usage("You may only provide topics with the {0} option.", "--logs")
   128  		}
   129  		if len(opts.Topic) > 0 {
   130  			return validate.Usage("The {0} option is only available with the {1} option.", "--topic", "--logs")
   131  		}
   132  		if opts.Relevant {
   133  			return validate.Usage("The {0} option is only available with the {1} option.", "--relevant", "--logs")
   134  		}
   135  	}
   136  
   137  	if !opts.Traces {
   138  		if opts.Factory {
   139  			return validate.Usage("The {0} option is only available with the {1} option.", "--factory", "--traces")
   140  		}
   141  	}
   142  
   143  	if len(opts.Fourbytes) > 0 {
   144  		if opts.Logs || opts.Receipts || opts.Appearances {
   145  			return validate.Usage("The {0} option is only available {1} option.", "--fourbyte", "when exporting or with the --accounting")
   146  		}
   147  		for _, t := range opts.Fourbytes {
   148  			if !validate.IsValidFourByte(t) {
   149  				return validate.Usage("Invalid four byte: {0}", t)
   150  			}
   151  		}
   152  	}
   153  
   154  	if opts.Accounting {
   155  		if len(opts.Addrs) != 1 {
   156  			return validate.Usage("The {0} option is allows with only a single address.", "--accounting")
   157  		}
   158  
   159  		if !opts.Conn.IsNodeArchive() {
   160  			return validate.Usage("The {0} option requires {1}.", "--accounting", "an archive node")
   161  		}
   162  
   163  		if opts.Globals.Chain != "mainnet" {
   164  			logger.Warn("The --accounting option reports a spotPrice of one for all assets on non-mainnet chains.")
   165  		}
   166  
   167  		if len(opts.Flow) > 0 {
   168  			if err := validate.ValidateEnum("--flow", opts.Flow, "[in|out|zero]"); err != nil {
   169  				return err
   170  			}
   171  
   172  			if !opts.Statements {
   173  				return validate.Usage("The {0} option is only available with the {1} option.", "--flow", "--statements")
   174  			}
   175  		}
   176  
   177  	} else {
   178  		if opts.Statements {
   179  			return validate.Usage("The {0} option is only available with the {1} option.", "--statements", "--accounting")
   180  		}
   181  
   182  		if opts.Globals.Format == "ofx" {
   183  			return validate.Usage("The {0} option is only available with the {1} option.", "--fmt ofx", "--accounting")
   184  		}
   185  	}
   186  
   187  	if len(opts.Asset) > 0 && !opts.Statements {
   188  		return validate.Usage("The {0} option is only available with the {1} option.", "--asset", "--statements")
   189  	}
   190  
   191  	if !validate.HasArticulationKey(opts.Articulate) {
   192  		return validate.Usage("The {0} option requires an Etherscan API key.", "--articulate")
   193  	}
   194  
   195  	if err := index.IsInitialized(chain, config.ExpectedVersion()); err != nil {
   196  		if (errors.Is(err, index.ErrNotInitialized) || errors.Is(err, index.ErrIncorrectHash)) && !opts.Globals.IsApiMode() {
   197  			logger.Fatal(err)
   198  		}
   199  		return err
   200  	}
   201  
   202  	return opts.Globals.Validate()
   203  	// if err != nil && strings.Contains(err.Error(), "option (ofx) must be one of") {
   204  	// 	// not an error
   205  	// 	err = nil
   206  	// }
   207  	// return err
   208  }
   209  
   210  func (opts *ExportOptions) tooMany() bool {
   211  	cnt := 0
   212  	if opts.Appearances {
   213  		cnt++
   214  	}
   215  	if opts.Receipts {
   216  		cnt++
   217  	}
   218  	if opts.Logs {
   219  		cnt++
   220  	}
   221  	if opts.Traces {
   222  		cnt++
   223  	}
   224  	if opts.Neighbors {
   225  		cnt++
   226  	}
   227  	if opts.Accounting {
   228  		cnt++
   229  	}
   230  	if opts.Withdrawals {
   231  		cnt++
   232  	}
   233  	return cnt > 1
   234  }