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 }