github.com/TrueBlocks/trueblocks-core/src/apps/chifra@v0.0.0-20241022031540-b362680128f7/internal/explore/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 explorePkg
     6  
     7  import (
     8  	"fmt"
     9  	"strings"
    10  
    11  	"github.com/TrueBlocks/trueblocks-core/src/apps/chifra/pkg/base"
    12  	"github.com/TrueBlocks/trueblocks-core/src/apps/chifra/pkg/config"
    13  	"github.com/TrueBlocks/trueblocks-core/src/apps/chifra/pkg/types"
    14  	"github.com/TrueBlocks/trueblocks-core/src/apps/chifra/pkg/validate"
    15  	"github.com/ethereum/go-ethereum"
    16  )
    17  
    18  func (opts *ExploreOptions) validateExplore() error {
    19  	chain := opts.Globals.Chain
    20  
    21  	opts.testLog()
    22  
    23  	if opts.BadFlag != nil {
    24  		return opts.BadFlag
    25  	}
    26  
    27  	if !config.IsChainConfigured(chain) {
    28  		return validate.Usage("chain {0} is not properly configured.", chain)
    29  	}
    30  
    31  	if opts.Globals.IsApiMode() {
    32  		return validate.Usage("The {0} option is not available{1}.", "explore", " in api mode")
    33  	}
    34  
    35  	if opts.Google && opts.Local {
    36  		return validate.Usage("The {0} option is not available{1}.", "--local", " with the --google option")
    37  	}
    38  
    39  	if opts.Dalle && opts.Local {
    40  		return validate.Usage("The {0} option is not available{1}.", "--local", " with the --dalle option")
    41  	}
    42  
    43  	if len(opts.Terms) == 0 && (opts.Dalle || opts.Google) {
    44  		return validate.Usage("The {0} options require {1}.", "--dalle and --google", "an address term")
    45  	}
    46  
    47  	for _, arg := range opts.Terms {
    48  		arg = strings.ToLower(arg)
    49  
    50  		if base.IsValidAddress(arg) {
    51  			if strings.Contains(arg, ".eth") {
    52  				opts.Destinations = append(opts.Destinations, types.NewDestination(arg, types.DestinationEnsName))
    53  			} else {
    54  				opts.Destinations = append(opts.Destinations, types.NewDestination(arg, types.DestinationAddress))
    55  			}
    56  			// We got a valid address, we're done checking
    57  			continue
    58  		}
    59  
    60  		// The argument is not an address, so we can't use --google
    61  		if opts.Google || opts.Dalle {
    62  			continue
    63  		}
    64  
    65  		valid, _ := validate.IsValidTransId(chain, []string{arg}, validate.ValidTransId)
    66  		if valid {
    67  			txHash, err := opts.idToTxHash(arg, validate.IsBlockHash)
    68  			if err == nil {
    69  				opts.Destinations = append(opts.Destinations, types.NewDestination(txHash, types.DestinationTx))
    70  				continue
    71  			}
    72  			// an error here is okay since we can't distinquish between tx hashes and block hashes...
    73  		}
    74  
    75  		valid, _ = validate.IsValidBlockId(chain, []string{arg}, validate.ValidBlockIdWithRangeAndDate)
    76  		if valid {
    77  			blockHash, err := opts.idToBlockHash(chain, arg, validate.IsBlockHash)
    78  			if err == nil {
    79  				opts.Destinations = append(opts.Destinations, types.NewDestination(blockHash.Hex(), types.DestinationBlock))
    80  				continue
    81  			}
    82  			// An error here is not okay because we have a valid hash but it's not a valid on-chain
    83  			// thingy, so we must have been told why by the node
    84  			return fmt.Errorf("block at %s returned an error: %w", arg, ethereum.NotFound)
    85  		}
    86  
    87  		if validate.IsValidFourByte(arg) {
    88  			opts.Destinations = append(opts.Destinations, types.NewDestination(arg, types.DestinationFourByte))
    89  			continue
    90  		}
    91  
    92  		return validate.Usage("The {0} option ({1}) {2}.", "term", arg, "is not valid")
    93  	}
    94  
    95  	if len(opts.Destinations) == 0 {
    96  		if opts.Google || opts.Dalle {
    97  			return validate.Usage("The {0} options require {1}.", "--dalle and --google", "an address term")
    98  		}
    99  		opts.Destinations = append(opts.Destinations, types.NewDestination("", types.DestinationNone))
   100  	}
   101  
   102  	return opts.Globals.Validate()
   103  }
   104  
   105  func (opts *ExploreOptions) idToBlockHash(chain, arg string, isBlockHash func(arg string) bool) (base.Hash, error) {
   106  	_ = chain // linter
   107  	if isBlockHash(arg) {
   108  		return opts.Conn.GetBlockHashByHash(arg)
   109  	}
   110  	return opts.Conn.GetBlockHashByNumber(base.MustParseBlknum(arg))
   111  }
   112  
   113  // idToTxHash takes a valid identifier (txHash/blockHash, blockHash.txId, blockNumber.txId)
   114  // and returns the transaction hash represented by that identifier. (If it's a valid transaction.
   115  // It may not be because transaction hashes and block hashes are both 32-byte hex)
   116  func (opts *ExploreOptions) idToTxHash(arg string, isBlockHash func(arg string) bool) (string, error) {
   117  	// simple case first
   118  	if !strings.Contains(arg, ".") {
   119  		// We know it's a hash, but we want to know if it's a legitimate tx on chain
   120  		return opts.Conn.GetTransactionHashByHash(arg)
   121  	}
   122  
   123  	parts := strings.Split(arg, ".")
   124  	if len(parts) != 2 {
   125  		panic("Programmer error - valid transaction identifiers with a `.` must have two and only two parts")
   126  	}
   127  
   128  	if isBlockHash(parts[0]) {
   129  		return opts.Conn.GetTransactionHashByHashAndID(parts[0], base.MustParseTxnum(parts[1]))
   130  	}
   131  
   132  	blockNum := base.MustParseBlknum(parts[0])
   133  	txId := base.MustParseTxnum(parts[1])
   134  	hash, err := opts.Conn.GetTransactionHashByNumberAndID(blockNum, base.Txnum(txId))
   135  	return hash.Hex(), err
   136  }