From 1913ad27a0602c5124f9cf9f31dcfa637944518a Mon Sep 17 00:00:00 2001 From: Jingfu Wang Date: Mon, 12 Sep 2022 12:49:28 -0400 Subject: [PATCH] feat: error handling for logger and cmd Signed-off-by: Jingfu Wang --- cmd/check_construction.go | 17 ++++--- cmd/check_data.go | 18 ++++--- cmd/check_spec.go | 40 ++++++++------- cmd/check_spec_utils.go | 14 ++---- cmd/configuration_create.go | 2 +- cmd/configuration_validate.go | 2 +- cmd/root.go | 10 ++-- cmd/utils_asserter_configuration.go | 6 +-- cmd/utils_train_zstd.go | 4 +- cmd/validate_asserter_config.go | 78 ++++++++++++----------------- cmd/view_balance.go | 12 ++--- cmd/view_block.go | 21 ++++---- cmd/view_networks.go | 9 ++-- pkg/errors/errors.go | 22 ++++++-- pkg/logger/logger.go | 49 +++++++++--------- 15 files changed, 154 insertions(+), 150 deletions(-) diff --git a/cmd/check_construction.go b/cmd/check_construction.go index 617a4a0d..497497d7 100644 --- a/cmd/check_construction.go +++ b/cmd/check_construction.go @@ -17,13 +17,15 @@ package cmd import ( "context" "fmt" - "github.com/coinbase/rosetta-cli/pkg/errors" "time" + cliErrs "github.com/coinbase/rosetta-cli/pkg/errors" + "github.com/coinbase/rosetta-cli/pkg/results" "github.com/coinbase/rosetta-cli/pkg/tester" "github.com/coinbase/rosetta-sdk-go/fetcher" + "github.com/coinbase/rosetta-sdk-go/types" "github.com/coinbase/rosetta-sdk-go/utils" "github.com/spf13/cobra" "golang.org/x/sync/errgroup" @@ -59,7 +61,7 @@ func runCheckConstructionCmd(_ *cobra.Command, _ []string) error { Config, nil, nil, - errors.ErrConstructionConfigMissing, + cliErrs.ErrConstructionConfigMissing, ) } @@ -88,7 +90,7 @@ func runCheckConstructionCmd(_ *cobra.Command, _ []string) error { Config, nil, nil, - fmt.Errorf("%w: unable to initialize asserter", fetchErr.Err), + fmt.Errorf("unable to initialize asserter for fetcher: %w", fetchErr.Err), ) } @@ -99,7 +101,7 @@ func runCheckConstructionCmd(_ *cobra.Command, _ []string) error { Config, nil, nil, - fmt.Errorf("%w: unable to confirm network is supported", err), + fmt.Errorf("unable to confirm network %s is supported: %w", types.PrintStruct(Config.Network), err), ) } @@ -112,7 +114,7 @@ func runCheckConstructionCmd(_ *cobra.Command, _ []string) error { Config, nil, nil, - err, + fmt.Errorf("network options don't match asserter configuration file %s: %w", asserterConfigurationFile, err), ) } } @@ -130,10 +132,9 @@ func runCheckConstructionCmd(_ *cobra.Command, _ []string) error { Config, nil, nil, - fmt.Errorf("%w: unable to initialize construction tester", err), + fmt.Errorf("unable to initialize construction tester: %w", err), ) } - defer constructionTester.CloseDatabase(ctx) if err := constructionTester.PerformBroadcasts(ctx); err != nil { @@ -141,7 +142,7 @@ func runCheckConstructionCmd(_ *cobra.Command, _ []string) error { Config, nil, nil, - fmt.Errorf("%w: unable to perform broadcasts", err), + fmt.Errorf("unable to perform broadcasts: %w", err), ) } diff --git a/cmd/check_data.go b/cmd/check_data.go index cc54433b..962f3647 100644 --- a/cmd/check_data.go +++ b/cmd/check_data.go @@ -17,12 +17,12 @@ package cmd import ( "context" "fmt" - "github.com/coinbase/rosetta-cli/pkg/errors" "time" "github.com/coinbase/rosetta-cli/pkg/results" "github.com/coinbase/rosetta-cli/pkg/tester" "github.com/coinbase/rosetta-sdk-go/fetcher" + "github.com/coinbase/rosetta-sdk-go/types" "github.com/coinbase/rosetta-sdk-go/utils" "github.com/spf13/cobra" "golang.org/x/sync/errgroup" @@ -96,7 +96,7 @@ func runCheckDataCmd(_ *cobra.Command, _ []string) error { Config, nil, nil, - fmt.Errorf("%w: unable to initialize asserter", fetchErr.Err), + fmt.Errorf("unable to initialize asserter for fetcher: %w", fetchErr.Err), "", "", ) @@ -109,7 +109,7 @@ func runCheckDataCmd(_ *cobra.Command, _ []string) error { Config, nil, nil, - fmt.Errorf("%w: unable to confirm network", err), + fmt.Errorf("unable to confirm network %s is supported: %w", types.PrintStruct(Config.Network), err), "", "", ) @@ -124,7 +124,7 @@ func runCheckDataCmd(_ *cobra.Command, _ []string) error { Config, nil, nil, - err, + fmt.Errorf("network options don't match asserter configuration file %s: %w", asserterConfigurationFile, err), "", "", ) @@ -141,9 +141,15 @@ func runCheckDataCmd(_ *cobra.Command, _ []string) error { nil, // only populated when doing recursive search &SignalReceived, ) - if err != nil { - return fmt.Errorf("%s:%s", errors.ErrInitDataTester, err) + return results.ExitData( + Config, + nil, + nil, + fmt.Errorf("unable to initialize data tester: %w", err), + "", + "", + ) } defer dataTester.CloseDatabase(ctx) diff --git a/cmd/check_spec.go b/cmd/check_spec.go index 206f1cf1..129b8c44 100644 --- a/cmd/check_spec.go +++ b/cmd/check_spec.go @@ -23,6 +23,8 @@ import ( "github.com/coinbase/rosetta-sdk-go/fetcher" "github.com/coinbase/rosetta-sdk-go/types" "github.com/spf13/cobra" + + cliErrs "github.com/coinbase/rosetta-cli/pkg/errors" ) var ( @@ -32,7 +34,7 @@ var ( Long: `Check:spec checks whether a Rosetta implementation satisfies either Coinbase-specific requirements or minimum requirements specified in rosetta-api.org. -By default, check:spec will verify only Coinbase spec requirements. To verifiy the minimum requirements as well, +By default, check:spec will verify only Coinbase spec requirements. To verify the minimum requirements as well, add the --all flag to the check:spec command: rosetta-cli check:spec --all --configuration-file [filepath] @@ -60,7 +62,7 @@ type checkSpec struct { func newCheckSpec(ctx context.Context) (*checkSpec, error) { if Config.Construction == nil { - return nil, fmt.Errorf("%v", errRosettaConfigNoConstruction) + return nil, cliErrs.ErrConstructionConfigMissing } onlineFetcherOpts := []fetcher.Option{ @@ -97,7 +99,7 @@ func newCheckSpec(ctx context.Context) (*checkSpec, error) { Config, nil, nil, - fmt.Errorf("%v: unable to initialize asserter for online node fetcher", fetchErr.Err), + fmt.Errorf("unable to initialize asserter for online fetcher: %w", fetchErr.Err), "", "", ) @@ -132,7 +134,7 @@ func (cs *checkSpec) networkOptions(ctx context.Context) checkSpecOutput { // This is an endpoint for offline mode _, err := cs.offlineFetcher.NetworkOptionsRetry(ctx, Config.Network, nil) if err != nil { - printError("%v: unable to fetch network options\n", err.Err) + printError("unable to fetch network options: %v\n", err.Err) markAllValidationsFailed(output) return output } @@ -169,7 +171,7 @@ func (cs *checkSpec) networkList(ctx context.Context) checkSpecOutput { // endpoint for offline mode if err != nil { - printError("%v: unable to fetch network list", err.Err) + printError("unable to fetch network list: %v\n", err.Err) markAllValidationsFailed(output) return output } @@ -211,12 +213,12 @@ func (cs *checkSpec) accountCoins(ctx context.Context) checkSpecOutput { if isUTXO() { acct, _, currencies, err := cs.getAccount(ctx) if err != nil { - printError("%v: unable to get an account\n", err) + printError("unable to get an account: %v\n", err) markAllValidationsFailed(output) return output } - if err != nil { - printError("%v\n", errAccountNullPointer) + if acct == nil { + printError("%v\n", cliErrs.ErrAccountNullPointer) markAllValidationsFailed(output) return output } @@ -228,7 +230,7 @@ func (cs *checkSpec) accountCoins(ctx context.Context) checkSpecOutput { false, currencies) if fetchErr != nil { - printError("%v: unable to get coins for account: %v\n", fetchErr.Err, *acct) + printError("unable to get coins for account %s: %v\n", types.PrintStruct(acct), fetchErr.Err) markAllValidationsFailed(output) return output } @@ -261,7 +263,7 @@ func (cs *checkSpec) block(ctx context.Context) checkSpecOutput { res, fetchErr := cs.onlineFetcher.NetworkStatusRetry(ctx, Config.Network, nil) if fetchErr != nil { - printError("%v: unable to get network status\n", fetchErr.Err) + printError("unable to get network status: %v\n", fetchErr.Err) markAllValidationsFailed(output) return output } @@ -278,7 +280,7 @@ func (cs *checkSpec) block(ctx context.Context) checkSpecOutput { } b, fetchErr := cs.onlineFetcher.BlockRetry(ctx, Config.Network, &blockID) if fetchErr != nil { - printError("%v: unable to fetch block %v\n", fetchErr.Err, blockID) + printError("unable to fetch block %s: %v\n", types.PrintStruct(blockID), fetchErr.Err) markAllValidationsFailed(output) return output } @@ -286,7 +288,7 @@ func (cs *checkSpec) block(ctx context.Context) checkSpecOutput { if block == nil { block = b } else if !isEqual(types.Hash(*block), types.Hash(*b)) { - printError("%v\n", errBlockNotIdempotent) + printError("%v\n", cliErrs.ErrBlockNotIdempotent) setValidationStatusFailed(output, idempotent) } } @@ -295,24 +297,24 @@ func (cs *checkSpec) block(ctx context.Context) checkSpecOutput { // fetch the tip block again res, fetchErr = cs.onlineFetcher.NetworkStatusRetry(ctx, Config.Network, nil) if fetchErr != nil { - printError("%v: unable to get network status\n", fetchErr.Err) + printError("unable to get network status: %v\n", fetchErr.Err) setValidationStatusFailed(output, defaultTip) return output } tip := res.CurrentBlockIdentifier - // tip shoud be returned if block_identifier is not specified + // tip should be returned if block_identifier is not specified emptyBlockID := &types.PartialBlockIdentifier{} block, fetchErr := cs.onlineFetcher.BlockRetry(ctx, Config.Network, emptyBlockID) if fetchErr != nil { - printError("%v: unable to fetch tip block\n", fetchErr.Err) + printError("unable to fetch tip block: %v\n", fetchErr.Err) setValidationStatusFailed(output, defaultTip) return output } // block index returned from /block should be >= the index returned by /network/status if isNegative(block.BlockIdentifier.Index - tip.Index) { - printError("%v\n", errBlockTip) + printError("%v\n", cliErrs.ErrBlockTip) setValidationStatusFailed(output, defaultTip) } @@ -383,7 +385,7 @@ func (cs *checkSpec) getAccount(ctx context.Context) ( error) { res, err := cs.onlineFetcher.NetworkStatusRetry(ctx, Config.Network, nil) if err != nil { - return nil, nil, nil, fmt.Errorf("%v: unable to get network status", err.Err) + return nil, nil, nil, fmt.Errorf("unable to get network status of network %s: %w", types.PrintStruct(Config.Network), err.Err) } var acct *types.AccountIdentifier @@ -399,7 +401,7 @@ func (cs *checkSpec) getAccount(ctx context.Context) ( block, err := cs.onlineFetcher.BlockRetry(ctx, Config.Network, blockID) if err != nil { - return nil, nil, nil, fmt.Errorf("%v: unable to fetch block at index: %v", err.Err, i) + return nil, nil, nil, fmt.Errorf("unable to fetch block at index %d: %w", i, err.Err) } // looking for an account in block transactions @@ -425,7 +427,7 @@ func runCheckSpecCmd(_ *cobra.Command, _ []string) error { ctx := context.Background() cs, err := newCheckSpec(ctx) if err != nil { - return fmt.Errorf("%v: unable to create checkSpec object with online URL", err) + return fmt.Errorf("unable to create checkSpec object with online URL: %w", err) } output := []checkSpecOutput{} diff --git a/cmd/check_spec_utils.go b/cmd/check_spec_utils.go index ab70d3f2..bc119727 100644 --- a/cmd/check_spec_utils.go +++ b/cmd/check_spec_utils.go @@ -15,21 +15,13 @@ package cmd import ( - "errors" "fmt" "strconv" "github.com/coinbase/rosetta-sdk-go/fetcher" "github.com/fatih/color" -) -var ( - errErrorEmptyMessage = errors.New("Error object can't have empty message") - errErrorNegativeCode = errors.New("Error object can't have negative code") - errAccountNullPointer = errors.New("Null pointer to Account object") - errBlockNotIdempotent = errors.New("Multiple calls with the same hash don't return the same block") - errBlockTip = errors.New("Unspecified block_identifier doesn't give the tip block") - errRosettaConfigNoConstruction = errors.New("No construction element in Rosetta config") + cliErrs "github.com/coinbase/rosetta-cli/pkg/errors" ) type checkSpecAPI string @@ -112,12 +104,12 @@ func setValidationStatusFailed(output checkSpecOutput, req checkSpecRequirement) func validateErrorObject(err *fetcher.Error, output checkSpecOutput) { if err != nil { if err.ClientErr != nil && isNegative(int64(err.ClientErr.Code)) { - printError("%v\n", errErrorNegativeCode) + printError("%v\n", cliErrs.ErrErrorNegativeCode) setValidationStatusFailed(output, errorCode) } if err.ClientErr != nil && isEmpty(err.ClientErr.Message) { - printError("%v\n", errErrorEmptyMessage) + printError("%v\n", cliErrs.ErrErrorEmptyMessage) setValidationStatusFailed(output, errorMessage) } } diff --git a/cmd/configuration_create.go b/cmd/configuration_create.go index 0bba6bc5..42c728ef 100644 --- a/cmd/configuration_create.go +++ b/cmd/configuration_create.go @@ -34,7 +34,7 @@ var ( func runConfigurationCreateCmd(cmd *cobra.Command, args []string) error { if err := utils.SerializeAndWrite(args[0], configuration.DefaultConfiguration()); err != nil { - return fmt.Errorf("%w: unable to save configuration file to %s", err, args[0]) + return fmt.Errorf("unable to save configuration file to %s: %w", args[0], err) } return nil diff --git a/cmd/configuration_validate.go b/cmd/configuration_validate.go index 80844519..6fb2c9d2 100644 --- a/cmd/configuration_validate.go +++ b/cmd/configuration_validate.go @@ -35,7 +35,7 @@ var ( func runConfigurationValidateCmd(cmd *cobra.Command, args []string) error { _, err := configuration.LoadConfiguration(Context, args[0]) if err != nil { - return fmt.Errorf("%w: configuration validation failed %s", err, args[0]) + return fmt.Errorf("configuration validation failed %s: %w", args[0], err) } color.Green("Configuration file validated!") diff --git a/cmd/root.go b/cmd/root.go index 48eba524..590eeddd 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -103,12 +103,12 @@ var ( // rootPreRun is executed before the root command runs and sets up cpu // profiling. // -// Bassed on https://golang.org/pkg/runtime/pprof/#hdr-Profiling_a_Go_program +// Based on https://golang.org/pkg/runtime/pprof/#hdr-Profiling_a_Go_program func rootPreRun(*cobra.Command, []string) error { if cpuProfile != "" { f, err := os.Create(path.Clean(cpuProfile)) if err != nil { - return fmt.Errorf("%w: unable to create CPU profile file", err) + return fmt.Errorf("unable to create CPU profile file: %w", err) } if err := pprof.StartCPUProfile(f); err != nil { if err := f.Close(); err != nil { @@ -129,7 +129,7 @@ func rootPreRun(*cobra.Command, []string) error { runtime.SetBlockProfileRate(1) f, err := os.Create(path.Clean(blockProfile)) if err != nil { - return fmt.Errorf("%w: unable to create block profile file", err) + return fmt.Errorf("unable to create block profile file: %w", err) } p := pprof.Lookup("block") @@ -355,7 +355,7 @@ func initConfig() { } if err != nil { - log.Fatalf("%s: unable to load configuration", err.Error()) + log.Fatalf("unable to load configuration: %s", err.Error()) } // Override node url in configuration file when it's explicitly set via CLI @@ -407,7 +407,7 @@ func ensureDataDirectoryExists() { if len(Config.DataDirectory) == 0 { tmpDir, err := utils.CreateTempDir() if err != nil { - log.Fatalf("%s: unable to create temporary directory", err.Error()) + log.Fatalf("unable to create temporary directory: %s", err.Error()) } Config.DataDirectory = tmpDir diff --git a/cmd/utils_asserter_configuration.go b/cmd/utils_asserter_configuration.go index 39afbdbe..db8e4100 100644 --- a/cmd/utils_asserter_configuration.go +++ b/cmd/utils_asserter_configuration.go @@ -55,18 +55,18 @@ func runCreateConfigurationCmd(cmd *cobra.Command, args []string) error { // Initialize the fetcher's asserter _, _, fetchErr := newFetcher.InitializeAsserter(Context, Config.Network, Config.ValidationFile) if fetchErr != nil { - return fmt.Errorf("%w: failed to initialize asserter", fetchErr.Err) + return fmt.Errorf("failed to initialize asserter for fetcher: %w", fetchErr.Err) } configuration, err := newFetcher.Asserter.ClientConfiguration() if err != nil { - return fmt.Errorf("%w: unable to generate spec", err) + return fmt.Errorf("unable to generate asserter configuration: %w", err) } sortArrayFieldsOnConfiguration(configuration) if err := utils.SerializeAndWrite(args[0], configuration); err != nil { - return fmt.Errorf("%w: unable to serialize asserter configuration", err) + return fmt.Errorf("unable to serialize asserter configuration: %w", err) } color.Green("Configuration file saved!") diff --git a/cmd/utils_train_zstd.go b/cmd/utils_train_zstd.go index 5a338f00..294436f9 100644 --- a/cmd/utils_train_zstd.go +++ b/cmd/utils_train_zstd.go @@ -56,7 +56,7 @@ func runTrainZstdCmd(cmd *cobra.Command, args []string) error { dictionaryPath := path.Clean(args[2]) maxItems, err := strconv.Atoi(args[3]) if err != nil { - return fmt.Errorf("%w: unable to convert max items to integer", err) + return fmt.Errorf("unable to convert max items to integer: %w", err) } compressorEntries := []*encoder.CompressorEntry{} @@ -80,7 +80,7 @@ func runTrainZstdCmd(cmd *cobra.Command, args []string) error { compressorEntries, ) if err != nil { - return fmt.Errorf("%w: badger training failed", err) + return fmt.Errorf("badger training failed: %w", err) } color.Green("Training successful!") diff --git a/cmd/validate_asserter_config.go b/cmd/validate_asserter_config.go index ba5afe38..8fddeecd 100644 --- a/cmd/validate_asserter_config.go +++ b/cmd/validate_asserter_config.go @@ -17,14 +17,15 @@ package cmd import ( "context" "fmt" - "github.com/coinbase/rosetta-cli/pkg/errors" + "reflect" + "sort" + "strings" + + cliErrs "github.com/coinbase/rosetta-cli/pkg/errors" "github.com/coinbase/rosetta-sdk-go/asserter" "github.com/coinbase/rosetta-sdk-go/fetcher" "github.com/coinbase/rosetta-sdk-go/types" "github.com/coinbase/rosetta-sdk-go/utils" - "reflect" - "sort" - "strings" ) // Common helper across Construction and Data @@ -37,46 +38,57 @@ func validateNetworkOptionsMatchesAsserterConfiguration( ) error { var asserterConfiguration asserter.Configuration if err := utils.LoadAndParse(asserterConfigurationFile, &asserterConfiguration); err != nil { - return fmt.Errorf("%w: failure loading / parsing asserter-configuration-file", err) + return fmt.Errorf("failed to load and parse asserter configuration file %s: %w", asserterConfigurationFile, err) } resp, fetchErr := f.NetworkOptions(ctx, network, nil) if fetchErr != nil { - return fmt.Errorf("%w: failure getting /network/options", fetchErr.Err) + return fmt.Errorf("failed to get network options: %w", fetchErr.Err) } - return validateNetworkAndAsserterAllowMatch(resp.Allow, &asserterConfiguration) + err := validateNetworkAndAsserterAllowMatch(resp.Allow, &asserterConfiguration) + if err != nil { + return fmt.Errorf("failed to validate network options allowlist with asserter configuration: %w", err) + } + + return nil } func validateNetworkAndAsserterAllowMatch( networkAllow *types.Allow, asserterConfiguration *asserter.Configuration, ) error { if networkAllow == nil { - return fmt.Errorf("%w: /network/options object's Allow is nil", errors.ErrAsserterConfigError) + return cliErrs.ErrNetworkOptionsAllowlistIsNil } if asserterConfiguration == nil { - return fmt.Errorf("%w: asserter-configuration-file object is nil", errors.ErrAsserterConfigError) + return cliErrs.ErrAsserterConfigurationIsNil } if err := verifyTimestampStartIndex( networkAllow.TimestampStartIndex, asserterConfiguration.AllowedTimestampStartIndex, ); err != nil { - return err + return fmt.Errorf("failed to verify timestamp start index: %w", err) } if err := verifyOperationTypes( networkAllow.OperationTypes, asserterConfiguration.AllowedOperationTypes, ); err != nil { - return err + return fmt.Errorf("failed to verify operation types: %w", err) } if err := verifyOperationStatuses( networkAllow.OperationStatuses, asserterConfiguration.AllowedOperationStatuses, ); err != nil { - return err + return fmt.Errorf("failed to verify operation statuses: %w", err) } - return verifyErrors(networkAllow.Errors, asserterConfiguration.AllowedErrors) + if err := verifyErrors( + networkAllow.Errors, asserterConfiguration.AllowedErrors, + ); err != nil { + return fmt.Errorf("failed to verify errors: %w", err) + } + + return nil } func verifyTimestampStartIndex(networkTsi *int64, assertTsi int64) error { @@ -85,10 +97,7 @@ func verifyTimestampStartIndex(networkTsi *int64, assertTsi int64) error { networkTsiVal = *networkTsi } if networkTsiVal != assertTsi { - return fmt.Errorf( - "/network/options / asserter-configuration-file timestamp start index mismatch. %d %d", - networkTsiVal, assertTsi, - ) + return fmt.Errorf("network options timestamp start index %d, asserter configuration timestamp start index %d: %w", networkTsiVal, assertTsi, cliErrs.ErrTimestampStartIndexMismatch) } return nil @@ -96,11 +105,7 @@ func verifyTimestampStartIndex(networkTsi *int64, assertTsi int64) error { func verifyOperationTypes(networkOt, asserterOt []string) error { if len(networkOt) != len(asserterOt) { - return fmt.Errorf( - "/network/options / asserter-configuration-file operation types length mismatch %+v "+ - "%+v", - networkOt, asserterOt, - ) + return fmt.Errorf("network options operation type length %d, asserter configuration operation type length %d: %w", len(networkOt), len(asserterOt), cliErrs.ErrOperationTypeLengthMismatch) } sort.Strings(networkOt) @@ -109,11 +114,7 @@ func verifyOperationTypes(networkOt, asserterOt []string) error { for i, networkOperationType := range networkOt { asserterOperationType := asserterOt[i] if networkOperationType != asserterOperationType { - return fmt.Errorf( - "/network/options / asserter-configuration-file operation type mismatch %+v "+ - "%+v\nnetwork operation types: %+v\nasserter operation types: %+v", - networkOperationType, asserterOperationType, networkOt, asserterOt, - ) + return fmt.Errorf("network options operation type %s, asserter configuration operation type %s: %w", networkOperationType, asserterOperationType, cliErrs.ErrOperationTypeMismatch) } } @@ -122,11 +123,7 @@ func verifyOperationTypes(networkOt, asserterOt []string) error { func verifyOperationStatuses(networkOs, asserterOs []*types.OperationStatus) error { if len(networkOs) != len(asserterOs) { - return fmt.Errorf( - "/network/options / asserter-configuration-file operation statuses length mismatch "+ - "%+v %+v", - networkOs, asserterOs, - ) + return fmt.Errorf("network options operation status length %d, asserter configuration operation status length %d: %w", len(networkOs), len(asserterOs), cliErrs.ErrOperationStatusLengthMismatch) } sort.Slice(networkOs, func(i, j int) bool { @@ -139,11 +136,7 @@ func verifyOperationStatuses(networkOs, asserterOs []*types.OperationStatus) err for i, networkOperationStatus := range networkOs { asserterOperationStatus := asserterOs[i] if !reflect.DeepEqual(networkOperationStatus, asserterOperationStatus) { - return fmt.Errorf( - "/network/options / asserter-configuration-file operation status mismatch %+v "+ - "%+v\nnetwork operation statuses: %+v\nasserter operation statuses: %+v", - networkOperationStatus, asserterOperationStatus, networkOs, asserterOs, - ) + return fmt.Errorf("network options operation type %s, asserter configuration operation type %s: %w", types.PrintStruct(networkOperationStatus), types.PrintStruct(asserterOperationStatus), cliErrs.ErrOperationStatusMismatch) } } @@ -152,10 +145,7 @@ func verifyOperationStatuses(networkOs, asserterOs []*types.OperationStatus) err func verifyErrors(networkErrors, asserterErrors []*types.Error) error { if len(networkErrors) != len(asserterErrors) { - return fmt.Errorf( - "/network/options / asserter-configuration-file errors length mismatch %+v %+v", - networkErrors, asserterErrors, - ) + return fmt.Errorf("network options error length %d, asserter configuration error length %d: %w", len(networkErrors), len(asserterErrors), cliErrs.ErrErrorLengthMismatch) } sort.Slice(networkErrors, func(i, j int) bool { @@ -168,11 +158,7 @@ func verifyErrors(networkErrors, asserterErrors []*types.Error) error { for i, networkError := range networkErrors { asserterError := asserterErrors[i] if !reflect.DeepEqual(networkError, asserterError) { - return fmt.Errorf( - "/network/options / asserter-configuration-file error mismatch %+v %+v\n"+ - "network errors: %+v\nasserter errors: %+v", - networkError, asserterError, networkErrors, asserterErrors, - ) + return fmt.Errorf("network options error %s, asserter configuration error %s: %w", types.PrintStruct(networkError), types.PrintStruct(asserterError), cliErrs.ErrErrorMismatch) } } diff --git a/cmd/view_balance.go b/cmd/view_balance.go index bfe3bbe2..72642452 100644 --- a/cmd/view_balance.go +++ b/cmd/view_balance.go @@ -48,11 +48,11 @@ address to specified as JSON allows for querying by SubAccountIdentifier.`, func runViewBalanceCmd(cmd *cobra.Command, args []string) error { account := &types.AccountIdentifier{} if err := json.Unmarshal([]byte(args[0]), account); err != nil { - return fmt.Errorf("%w: unable to unmarshal account %s", err, args[0]) + return fmt.Errorf("unable to unmarshal account %s: %w", args[0], err) } if err := asserter.AccountIdentifier(account); err != nil { - return fmt.Errorf("%w: invalid account identifier %s", err, types.PrintStruct(account)) + return fmt.Errorf("invalid account identifier %s: %w", types.PrintStruct(account), err) } // Create a new fetcher @@ -74,19 +74,19 @@ func runViewBalanceCmd(cmd *cobra.Command, args []string) error { // Initialize the fetcher's asserter _, _, fetchErr := newFetcher.InitializeAsserter(Context, Config.Network, Config.ValidationFile) if fetchErr != nil { - return fmt.Errorf("%w: unable to initialize asserter", fetchErr.Err) + return fmt.Errorf("unable to initialize asserter for fetcher: %w", fetchErr.Err) } _, err := utils.CheckNetworkSupported(Context, Config.Network, newFetcher) if err != nil { - return fmt.Errorf("%w: unable to confirm network is supported", err) + return fmt.Errorf("unable to confirm network %s is supported: %w", types.PrintStruct(Config.Network), err) } var lookupBlock *types.PartialBlockIdentifier if len(args) > 1 { index, err := strconv.ParseInt(args[1], 10, 64) if err != nil { - return fmt.Errorf("%w: unable to parse index %s", err, args[0]) + return fmt.Errorf("unable to parse index %s: %w", args[0], err) } lookupBlock = &types.PartialBlockIdentifier{Index: &index} @@ -100,7 +100,7 @@ func runViewBalanceCmd(cmd *cobra.Command, args []string) error { nil, ) if fetchErr != nil { - return fmt.Errorf("%w: unable to fetch account %+v", fetchErr.Err, account) + return fmt.Errorf("unable to fetch account balance for account %s: %w", types.PrintStruct(account), fetchErr.Err) } log.Printf("Amounts: %s\n", types.PrettyPrintStruct(amounts)) diff --git a/cmd/view_block.go b/cmd/view_block.go index be2de146..57834c46 100644 --- a/cmd/view_block.go +++ b/cmd/view_block.go @@ -16,10 +16,11 @@ package cmd import ( "fmt" - "github.com/coinbase/rosetta-cli/pkg/errors" "strconv" "time" + cliErrs "github.com/coinbase/rosetta-cli/pkg/errors" + "github.com/coinbase/rosetta-sdk-go/fetcher" "github.com/coinbase/rosetta-sdk-go/parser" "github.com/coinbase/rosetta-sdk-go/types" @@ -50,7 +51,7 @@ func printChanges(balanceChanges []*parser.BalanceChange) error { for _, balanceChange := range balanceChanges { parsedDiff, err := types.BigInt(balanceChange.Difference) if err != nil { - return fmt.Errorf("%w: unable to parse Difference", err) + return fmt.Errorf("unable to parse balance change difference: %w", err) } if parsedDiff.Sign() == 0 { @@ -70,7 +71,7 @@ func printChanges(balanceChanges []*parser.BalanceChange) error { func runViewBlockCmd(_ *cobra.Command, args []string) error { index, err := strconv.ParseInt(args[0], 10, 64) if err != nil { - return fmt.Errorf("%w: unable to parse index %s", err, args[0]) + return fmt.Errorf("unable to parse index %s: %w", args[0], err) } // Create a new fetcher @@ -96,12 +97,12 @@ func runViewBlockCmd(_ *cobra.Command, args []string) error { // the asserter what are valid responses. _, _, fetchErr := newFetcher.InitializeAsserter(Context, Config.Network, Config.ValidationFile) if fetchErr != nil { - return fmt.Errorf("%w: unable to initialize asserter", fetchErr.Err) + return fmt.Errorf("unable to initialize asserter for fetcher: %w", fetchErr.Err) } _, err = utils.CheckNetworkSupported(Context, Config.Network, newFetcher) if err != nil { - return fmt.Errorf("%w: unable to confirm network is supported", err) + return fmt.Errorf("unable to confirm network %s is supported: %w", types.PrintStruct(Config.Network), err) } // Fetch the specified block with retries (automatically @@ -121,11 +122,11 @@ func runViewBlockCmd(_ *cobra.Command, args []string) error { }, ) if fetchErr != nil { - return fmt.Errorf("%w: unable to fetch block", fetchErr.Err) + return fmt.Errorf("unable to fetch block %d: %w", index, fetchErr.Err) } // It's valid for a block to be omitted without triggering an error if block == nil { - return fmt.Errorf("%w: block not found, it might be omitted", errors.ErrBlockNotFound) + return cliErrs.ErrBlockNotFound } fmt.Printf("\n") @@ -140,10 +141,10 @@ func runViewBlockCmd(_ *cobra.Command, args []string) error { p := parser.New(newFetcher.Asserter, func(*types.Operation) bool { return false }, nil) balanceChanges, err := p.BalanceChanges(Context, block, false) if err != nil { - return fmt.Errorf("%w: unable to calculate balance changes", err) + return fmt.Errorf("unable to calculate balance changes: %w", err) } - fmt.Println("Cummulative:", block.BlockIdentifier.Hash) + fmt.Println("Cumulative:", block.BlockIdentifier.Hash) if err := printChanges(balanceChanges); err != nil { return err @@ -162,7 +163,7 @@ func runViewBlockCmd(_ *cobra.Command, args []string) error { }, }, false) if err != nil { - return fmt.Errorf("%w: unable to calculate balance changes", err) + return fmt.Errorf("unable to calculate balance changes: %w", err) } fmt.Println("Transaction:", tx.TransactionIdentifier.Hash) diff --git a/cmd/view_networks.go b/cmd/view_networks.go index c2d0a478..375e4db3 100644 --- a/cmd/view_networks.go +++ b/cmd/view_networks.go @@ -16,10 +16,11 @@ package cmd import ( "fmt" - "github.com/coinbase/rosetta-cli/pkg/errors" "log" "time" + "github.com/coinbase/rosetta-cli/pkg/errors" + "github.com/coinbase/rosetta-sdk-go/fetcher" "github.com/coinbase/rosetta-sdk-go/types" "github.com/fatih/color" @@ -59,7 +60,7 @@ func runViewNetworksCmd(cmd *cobra.Command, args []string) error { // Attempt to fetch network list networkList, fetchErr := f.NetworkListRetry(Context, nil) if fetchErr != nil { - return fmt.Errorf("%w: unable to fetch network list", fetchErr.Err) + return fmt.Errorf("unable to get network list: %w", fetchErr.Err) } if len(networkList.NetworkIdentifiers) == 0 { @@ -74,7 +75,7 @@ func runViewNetworksCmd(cmd *cobra.Command, args []string) error { nil, ) if fetchErr != nil { - return fmt.Errorf("%w: unable to get network options", fetchErr.Err) + return fmt.Errorf("unable to get network options: %w", fetchErr.Err) } log.Printf("Network options: %s\n", types.PrettyPrintStruct(networkOptions)) @@ -85,7 +86,7 @@ func runViewNetworksCmd(cmd *cobra.Command, args []string) error { nil, ) if fetchErr != nil { - return fmt.Errorf("%w: unable to get network status", fetchErr.Err) + return fmt.Errorf("unable to get network status: %w", fetchErr.Err) } log.Printf("Network status: %s\n", types.PrettyPrintStruct(networkStatus)) diff --git a/pkg/errors/errors.go b/pkg/errors/errors.go index 1b608be2..02e34a5b 100644 --- a/pkg/errors/errors.go +++ b/pkg/errors/errors.go @@ -40,14 +40,28 @@ var ( // Data check errors ErrDataCheckHalt = errors.New("data check halted") - ErrInitDataTester = errors.New("unexpected error occurred while trying to initialize data tester") ErrReconciliationFailure = errors.New("reconciliation failure") + // Spec check errors + ErrErrorEmptyMessage = errors.New("error object can't have empty message") + ErrErrorNegativeCode = errors.New("error object can't have negative code") + ErrAccountNullPointer = errors.New("account is nil") + ErrBlockNotIdempotent = errors.New("multiple calls with the same hash don't return the same block") + ErrBlockTip = errors.New("unspecified block_identifier doesn't give the tip block") + // Construction check errors ErrConstructionCheckHalt = errors.New("construction check halted") // Command errors - ErrBlockNotFound = errors.New("block not found") - ErrNoAvailableNetwork = errors.New("no networks available") - ErrAsserterConfigError = errors.New("asserter configuration validation failed") + ErrBlockNotFound = errors.New("block not found") + ErrNoAvailableNetwork = errors.New("no networks available") + ErrNetworkOptionsAllowlistIsNil = errors.New("network options allowlist is nil") + ErrAsserterConfigurationIsNil = errors.New("asserter configuration is nil") + ErrTimestampStartIndexMismatch = errors.New("timestamp start index mismatch") + ErrOperationTypeLengthMismatch = errors.New("operation type length mismatch") + ErrOperationTypeMismatch = errors.New("operation type mismatch") + ErrOperationStatusLengthMismatch = errors.New("operation status length mismatch") + ErrOperationStatusMismatch = errors.New("operation status mismatch") + ErrErrorLengthMismatch = errors.New("error length mismatch") + ErrErrorMismatch = errors.New("error mismatch") ) diff --git a/pkg/logger/logger.go b/pkg/logger/logger.go index 9fd9cce2..2bca8eda 100644 --- a/pkg/logger/logger.go +++ b/pkg/logger/logger.go @@ -98,7 +98,7 @@ func NewLogger( ) (*Logger, error) { zapLogger, err := buildZapLogger(checkType, network, fields...) if err != nil { - return nil, err + return nil, fmt.Errorf("failed to build zap logger: %w", err) } return &Logger{ logDir: logDir, @@ -118,7 +118,7 @@ func buildZapLogger( config := zap.NewDevelopmentConfig() config.EncoderConfig.EncodeLevel = zapcore.CapitalColorLevelEncoder - baseSlice := []zap.Field { + baseSlice := []zap.Field{ zap.String("blockchain", network.Blockchain), zap.String("network", network.Network), zap.String("check_type", string(checkType)), @@ -236,7 +236,7 @@ func (l *Logger) AddBlockStream( os.FileMode(utils.DefaultFilePermissions), ) if err != nil { - return err + return fmt.Errorf("failed to open file %s: %w", path.Join(l.logDir, blockStreamFile), err) } defer closeFile(f) @@ -251,7 +251,7 @@ func (l *Logger) AddBlockStream( ) fmt.Print(blockString) if _, err := f.WriteString(blockString); err != nil { - return err + return fmt.Errorf("failed to write block string %s: %w", blockString, err) } return l.TransactionStream(ctx, block) @@ -273,7 +273,7 @@ func (l *Logger) RemoveBlockStream( os.FileMode(utils.DefaultFilePermissions), ) if err != nil { - return err + return fmt.Errorf("failed to open file %s: %w", path.Join(l.logDir, blockStreamFile), err) } defer closeFile(f) @@ -286,7 +286,7 @@ func (l *Logger) RemoveBlockStream( ) fmt.Print(blockString) _, err = f.WriteString(blockString) - return err + return fmt.Errorf("failed to write block string %s: %w", blockString, err) } // TransactionStream writes the next processed block's transactions @@ -305,7 +305,7 @@ func (l *Logger) TransactionStream( os.FileMode(utils.DefaultFilePermissions), ) if err != nil { - return err + return fmt.Errorf("failed to open file %s: %w", path.Join(l.logDir, transactionStreamFile), err) } defer closeFile(f) @@ -317,12 +317,10 @@ func (l *Logger) TransactionStream( block.BlockIdentifier.Index, block.BlockIdentifier.Hash, ) - fmt.Print(transactionString) _, err = f.WriteString(transactionString) - if err != nil { - return err + return fmt.Errorf("failed to write transaction string %s: %w", transactionString, err) } for _, op := range tx.Operations { @@ -342,7 +340,7 @@ func (l *Logger) TransactionStream( networkIndex = *op.OperationIdentifier.NetworkIndex } - _, err = f.WriteString(fmt.Sprintf( + transactionOperationString := fmt.Sprintf( "TxOp %d(%d) %s %s %s %s %s\n", op.OperationIdentifier.Index, networkIndex, @@ -351,9 +349,10 @@ func (l *Logger) TransactionStream( amount, symbol, *op.Status, - )) + ) + _, err = f.WriteString(transactionOperationString) if err != nil { - return err + return fmt.Errorf("failed to write transaction operation string %s: %w", transactionOperationString, err) } } } @@ -377,7 +376,7 @@ func (l *Logger) BalanceStream( os.FileMode(utils.DefaultFilePermissions), ) if err != nil { - return err + return fmt.Errorf("failed to open file %s: %w", path.Join(l.logDir, balanceStreamFile), err) } defer closeFile(f) @@ -393,7 +392,7 @@ func (l *Logger) BalanceStream( ) if _, err := f.WriteString(fmt.Sprintf("%s\n", balanceLog)); err != nil { - return err + return fmt.Errorf("failed to write balance log %s: %w", balanceLog, err) } } return nil @@ -419,7 +418,7 @@ func (l *Logger) ReconcileSuccessStream( os.FileMode(utils.DefaultFilePermissions), ) if err != nil { - return err + return fmt.Errorf("failed to open file %s: %w", path.Join(l.logDir, reconcileSuccessStreamFile), err) } defer closeFile(f) @@ -431,7 +430,7 @@ func (l *Logger) ReconcileSuccessStream( block.Index, ) - _, err = f.WriteString(fmt.Sprintf( + reconciliationSuccessString := fmt.Sprintf( "Type:%s Account: %s Currency: %s Balance: %s Block: %d:%s\n", reconciliationType, types.AccountString(account), @@ -439,9 +438,10 @@ func (l *Logger) ReconcileSuccessStream( balance, block.Index, block.Hash, - )) + ) + _, err = f.WriteString(reconciliationSuccessString) if err != nil { - return err + return fmt.Errorf("failed to write reconciliation success string %s: %w", reconciliationSuccessString, err) } return nil @@ -490,12 +490,12 @@ func (l *Logger) ReconcileFailureStream( os.FileMode(utils.DefaultFilePermissions), ) if err != nil { - return err + return fmt.Errorf("failed to open file %s: %w", path.Join(l.logDir, reconcileFailureStreamFile), err) } defer closeFile(f) - _, err = f.WriteString(fmt.Sprintf( + reconciliationFailureString := fmt.Sprintf( "Type:%s Account: %s Currency: %s Block: %s:%d computed: %s live: %s\n", reconciliationType, types.AccountString(account), @@ -504,9 +504,10 @@ func (l *Logger) ReconcileFailureStream( block.Index, computedBalance, liveBalance, - )) + ) + _, err = f.WriteString(reconciliationFailureString) if err != nil { - return err + return fmt.Errorf("failed to write reconciliation failure string %s: %w", reconciliationFailureString, err) } return nil @@ -546,7 +547,7 @@ func (l *Logger) Fatal(msg string, fields ...zap.Field) { func closeFile(f *os.File) { err := f.Close() if err != nil { - log.Fatal(fmt.Errorf("%w: unable to close file", err)) + log.Fatal(fmt.Errorf("unable to close file: %w", err)) } }