From 7545d8f37fe4d21149c6215c2d0eee40e4a6a61a Mon Sep 17 00:00:00 2001 From: Goran Rojovic <100121253+goran-ethernal@users.noreply.github.com> Date: Tue, 12 Nov 2024 11:39:11 +0100 Subject: [PATCH] feat: unpack and log agglayer errors (#158) * feat: unpack and log agglayer errors * feat: agglayer error unpacking * fix: lint and UT --- agglayer/errors_test.go | 270 +++++++ agglayer/proof_generation_error.go | 657 ++++++++++++++++++ agglayer/proof_verification_error.go | 164 +++++ .../errors_with_declared_computed_data.json | 45 ++ .../errors_with_token_info.json | 29 + .../errors_without_inner_data.json | 38 + .../invalid_imported_bridge_exit_errors.json | 48 ++ .../invalid_signer_error.json | 21 + .../random_unmarshal_errors.json | 12 + .../errors_with_inner_data.json | 22 + .../errors_without_inner_data.json | 6 + .../errors_with_declared_computed_data.json | 6 + .../errors_with_token_info.json | 26 + .../errors_without_inner_data.json | 6 + ...ullifier_path_generation_failed_error.json | 20 + agglayer/type_conversion_error.go | 255 +++++++ agglayer/types.go | 116 +++- agglayer/types_test.go | 99 +++ sonar-project.properties | 4 +- 19 files changed, 1833 insertions(+), 11 deletions(-) create mode 100644 agglayer/errors_test.go create mode 100644 agglayer/proof_generation_error.go create mode 100644 agglayer/proof_verification_error.go create mode 100644 agglayer/testdata/proof_generation_errors/errors_with_declared_computed_data.json create mode 100644 agglayer/testdata/proof_generation_errors/errors_with_token_info.json create mode 100644 agglayer/testdata/proof_generation_errors/errors_without_inner_data.json create mode 100644 agglayer/testdata/proof_generation_errors/invalid_imported_bridge_exit_errors.json create mode 100644 agglayer/testdata/proof_generation_errors/invalid_signer_error.json create mode 100644 agglayer/testdata/proof_generation_errors/random_unmarshal_errors.json create mode 100644 agglayer/testdata/proof_verification_errors/errors_with_inner_data.json create mode 100644 agglayer/testdata/proof_verification_errors/errors_without_inner_data.json create mode 100644 agglayer/testdata/type_conversion_errors/errors_with_declared_computed_data.json create mode 100644 agglayer/testdata/type_conversion_errors/errors_with_token_info.json create mode 100644 agglayer/testdata/type_conversion_errors/errors_without_inner_data.json create mode 100644 agglayer/testdata/type_conversion_errors/nullifier_path_generation_failed_error.json create mode 100644 agglayer/type_conversion_error.go diff --git a/agglayer/errors_test.go b/agglayer/errors_test.go new file mode 100644 index 00000000..3ca7b7ed --- /dev/null +++ b/agglayer/errors_test.go @@ -0,0 +1,270 @@ +package agglayer + +import ( + "encoding/json" + "fmt" + "os" + "path/filepath" + "testing" + + "github.com/stretchr/testify/require" +) + +func TestErrorVectors(t *testing.T) { + t.Parallel() + + type testCase struct { + TestName string `json:"test_name"` + ExpectedError string `json:"expected_error"` + CertificateHeaderJSON string `json:"certificate_header"` + } + + files, err := filepath.Glob("testdata/*/*.json") + require.NoError(t, err) + + for _, file := range files { + file := file + + t.Run(file, func(t *testing.T) { + t.Parallel() + + data, err := os.ReadFile(file) + require.NoError(t, err) + + var testCases []*testCase + + require.NoError(t, json.Unmarshal(data, &testCases)) + + for _, tc := range testCases { + certificateHeader := &CertificateHeader{} + err = json.Unmarshal([]byte(tc.CertificateHeaderJSON), certificateHeader) + + if tc.ExpectedError == "" { + require.NoError(t, err, "Test: %s not expected any unmarshal error, but got: %v", tc.TestName, err) + require.NotNil(t, certificateHeader.Error, "Test: %s unpacked error is nil", tc.TestName) + fmt.Println(certificateHeader.Error.String()) + } else { + require.ErrorContains(t, err, tc.ExpectedError, "Test: %s expected error: %s. Got: %v", tc.TestName, tc.ExpectedError, err) + } + } + }) + } +} + +func TestConvertMapValue_String(t *testing.T) { + t.Parallel() + + tests := []struct { + name string + data map[string]interface{} + key string + want string + errString string + }{ + { + name: "Key exists and type matches", + data: map[string]interface{}{ + "key1": "value1", + }, + key: "key1", + want: "value1", + }, + { + name: "Key exists but type does not match", + data: map[string]interface{}{ + "key1": 1, + }, + key: "key1", + want: "", + errString: "is not of type", + }, + { + name: "Key does not exist", + data: map[string]interface{}{ + "key1": "value1", + }, + key: "key2", + want: "", + errString: "key key2 not found in map", + }, + } + + for _, tt := range tests { + tt := tt + + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + + got, err := convertMapValue[string](tt.data, tt.key) + if tt.errString != "" { + require.ErrorContains(t, err, tt.errString) + } else { + require.Equal(t, tt.want, got) + } + }) + } +} + +//nolint:dupl +func TestConvertMapValue_Uint32(t *testing.T) { + t.Parallel() + + tests := []struct { + name string + data map[string]interface{} + key string + want uint32 + errString string + }{ + { + name: "Key exists and type matches", + data: map[string]interface{}{ + "key1": uint32(123), + }, + key: "key1", + want: uint32(123), + }, + { + name: "Key exists but type does not match", + data: map[string]interface{}{ + "key1": "value1", + }, + key: "key1", + want: 0, + errString: "is not of type", + }, + { + name: "Key does not exist", + data: map[string]interface{}{ + "key1": uint32(123), + }, + key: "key2", + want: 0, + errString: "key key2 not found in map", + }, + } + + for _, tt := range tests { + tt := tt + + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + + got, err := convertMapValue[uint32](tt.data, tt.key) + if tt.errString != "" { + require.ErrorContains(t, err, tt.errString) + } else { + require.Equal(t, tt.want, got) + } + }) + } +} + +//nolint:dupl +func TestConvertMapValue_Uint64(t *testing.T) { + t.Parallel() + + tests := []struct { + name string + data map[string]interface{} + key string + want uint64 + errString string + }{ + { + name: "Key exists and type matches", + data: map[string]interface{}{ + "key1": uint64(3411), + }, + key: "key1", + want: uint64(3411), + }, + { + name: "Key exists but type does not match", + data: map[string]interface{}{ + "key1": "not a number", + }, + key: "key1", + want: 0, + errString: "is not of type", + }, + { + name: "Key does not exist", + data: map[string]interface{}{ + "key1": uint64(123555), + }, + key: "key22", + want: 0, + errString: "key key22 not found in map", + }, + } + + for _, tt := range tests { + tt := tt + + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + + got, err := convertMapValue[uint64](tt.data, tt.key) + if tt.errString != "" { + require.ErrorContains(t, err, tt.errString) + } else { + require.Equal(t, tt.want, got) + } + }) + } +} + +func TestConvertMapValue_Bool(t *testing.T) { + t.Parallel() + + tests := []struct { + name string + data map[string]interface{} + key string + want bool + errString string + }{ + { + name: "Key exists and type matches", + data: map[string]interface{}{ + "key1": true, + }, + key: "key1", + want: true, + }, + { + name: "Key exists but type does not match", + data: map[string]interface{}{ + "key1": "value1", + }, + key: "key1", + want: false, + errString: "is not of type", + }, + { + name: "Key does not exist", + data: map[string]interface{}{ + "key1": true, + }, + key: "key2", + want: false, + errString: "key key2 not found in map", + }, + } + + for _, tt := range tests { + tt := tt + + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + + got, err := convertMapValue[bool](tt.data, tt.key) + if tt.errString != "" { + require.ErrorContains(t, err, tt.errString) + } else { + require.Equal(t, tt.want, got) + } + }) + } +} diff --git a/agglayer/proof_generation_error.go b/agglayer/proof_generation_error.go new file mode 100644 index 00000000..fa7012f7 --- /dev/null +++ b/agglayer/proof_generation_error.go @@ -0,0 +1,657 @@ +package agglayer + +import ( + "errors" + "fmt" + "reflect" + + "github.com/ethereum/go-ethereum/common" +) + +var errNotMap = errors.New("inner error is not a map") + +const ( + InvalidSignerErrorType = "InvalidSigner" + InvalidPreviousLERErrorType = "InvalidPreviousLocalExitRoot" + InvalidPreviousBalanceRootErrorType = "InvalidPreviousBalanceRoot" + InvalidPreviousNullifierRootErrorType = "InvalidPreviousNullifierRoot" + InvalidNewLocalExitRootErrorType = "InvalidNewLocalExitRoot" + InvalidNewBalanceRootErrorType = "InvalidNewBalanceRoot" + InvalidNewNullifierRootErrorType = "InvalidNewNullifierRoot" + InvalidImportedExitsRootErrorType = "InvalidImportedExitsRoot" + MismatchImportedExitsRootErrorType = "MismatchImportedExitsRoot" + InvalidNullifierPathErrorType = "InvalidNullifierPath" + InvalidBalancePathErrorType = "InvalidBalancePath" + BalanceOverflowInBridgeExitErrorType = "BalanceOverflowInBridgeExit" + BalanceUnderflowInBridgeExitErrorType = "BalanceUnderflowInBridgeExit" + CannotExitToSameNetworkErrorType = "CannotExitToSameNetwork" + InvalidMessageOriginNetworkErrorType = "InvalidMessageOriginNetwork" + InvalidL1TokenInfoErrorType = "InvalidL1TokenInfo" + MissingTokenBalanceProofErrorType = "MissingTokenBalanceProof" + DuplicateTokenBalanceProofErrorType = "DuplicateTokenBalanceProof" + InvalidSignatureErrorType = "InvalidSignature" + InvalidImportedBridgeExitErrorType = "InvalidImportedBridgeExit" + UnknownErrorType = "UnknownError" +) + +type PPError interface { + String() string +} + +// ProofGenerationError is a struct that represents an error that occurs when generating a proof. +type ProofGenerationError struct { + GenerationType string + InnerErrors []PPError +} + +// String is the implementation of the Error interface +func (p *ProofGenerationError) String() string { + return fmt.Sprintf("Proof generation error: %s. %s", p.GenerationType, p.InnerErrors) +} + +// Unmarshal unmarshals the data from a map into a ProofGenerationError struct. +func (p *ProofGenerationError) Unmarshal(data interface{}) error { + dataMap, ok := data.(map[string]interface{}) + if !ok { + return errNotMap + } + + generationType, err := convertMapValue[string](dataMap, "generation_type") + if err != nil { + return err + } + + p.GenerationType = generationType + + getPPErrFn := func(key string, value interface{}) (PPError, error) { + switch key { + case InvalidSignerErrorType: + invalidSigner := &InvalidSignerError{} + if err := invalidSigner.UnmarshalFromMap(value); err != nil { + return nil, err + } + return invalidSigner, nil + case InvalidPreviousLERErrorType: + invalidPreviousLER := NewInvalidPreviousLocalExitRoot() + if err := invalidPreviousLER.UnmarshalFromMap(value); err != nil { + return nil, err + } + p.InnerErrors = append(p.InnerErrors, invalidPreviousLER) + case InvalidPreviousBalanceRootErrorType: + invalidPreviousBalanceRoot := NewInvalidPreviousBalanceRoot() + if err := invalidPreviousBalanceRoot.UnmarshalFromMap(value); err != nil { + return nil, err + } + p.InnerErrors = append(p.InnerErrors, invalidPreviousBalanceRoot) + case InvalidPreviousNullifierRootErrorType: + invalidPreviousNullifierRoot := NewInvalidPreviousNullifierRoot() + if err := invalidPreviousNullifierRoot.UnmarshalFromMap(value); err != nil { + return nil, err + } + p.InnerErrors = append(p.InnerErrors, invalidPreviousNullifierRoot) + case InvalidNewLocalExitRootErrorType: + invalidNewLocalExitRoot := NewInvalidNewLocalExitRoot() + if err := invalidNewLocalExitRoot.UnmarshalFromMap(value); err != nil { + return nil, err + } + p.InnerErrors = append(p.InnerErrors, invalidNewLocalExitRoot) + case InvalidNewBalanceRootErrorType: + invalidNewBalanceRoot := NewInvalidNewBalanceRoot() + if err := invalidNewBalanceRoot.UnmarshalFromMap(value); err != nil { + return nil, err + } + p.InnerErrors = append(p.InnerErrors, invalidNewBalanceRoot) + case InvalidNewNullifierRootErrorType: + invalidNewNullifierRoot := NewInvalidNewNullifierRoot() + if err := invalidNewNullifierRoot.UnmarshalFromMap(value); err != nil { + return nil, err + } + p.InnerErrors = append(p.InnerErrors, invalidNewNullifierRoot) + case InvalidImportedExitsRootErrorType: + invalidImportedExitsRoot := NewInvalidImportedExitsRoot() + if err := invalidImportedExitsRoot.UnmarshalFromMap(value); err != nil { + return nil, err + } + p.InnerErrors = append(p.InnerErrors, invalidImportedExitsRoot) + case MismatchImportedExitsRootErrorType: + p.InnerErrors = append(p.InnerErrors, &MismatchImportedExitsRoot{}) + case InvalidNullifierPathErrorType: + p.InnerErrors = append(p.InnerErrors, &InvalidNullifierPath{}) + case InvalidBalancePathErrorType: + p.InnerErrors = append(p.InnerErrors, &InvalidBalancePath{}) + case BalanceOverflowInBridgeExitErrorType: + p.InnerErrors = append(p.InnerErrors, &BalanceOverflowInBridgeExit{}) + case BalanceUnderflowInBridgeExitErrorType: + p.InnerErrors = append(p.InnerErrors, &BalanceUnderflowInBridgeExit{}) + case CannotExitToSameNetworkErrorType: + p.InnerErrors = append(p.InnerErrors, &CannotExitToSameNetwork{}) + case InvalidMessageOriginNetworkErrorType: + p.InnerErrors = append(p.InnerErrors, &InvalidMessageOriginNetwork{}) + case InvalidL1TokenInfoErrorType: + invalidL1TokenInfo := NewInvalidL1TokenInfo() + if err := invalidL1TokenInfo.UnmarshalFromMap(value); err != nil { + return nil, err + } + p.InnerErrors = append(p.InnerErrors, invalidL1TokenInfo) + case MissingTokenBalanceProofErrorType: + missingTokenBalanceProof := NewMissingTokenBalanceProof() + if err := missingTokenBalanceProof.UnmarshalFromMap(value); err != nil { + return nil, err + } + p.InnerErrors = append(p.InnerErrors, missingTokenBalanceProof) + case DuplicateTokenBalanceProofErrorType: + duplicateTokenBalanceProof := NewDuplicateTokenBalanceProof() + if err := duplicateTokenBalanceProof.UnmarshalFromMap(value); err != nil { + return nil, err + } + p.InnerErrors = append(p.InnerErrors, duplicateTokenBalanceProof) + case InvalidSignatureErrorType: + p.InnerErrors = append(p.InnerErrors, &InvalidSignature{}) + case InvalidImportedBridgeExitErrorType: + invalidImportedBridgeExit := &InvalidImportedBridgeExit{} + if err := invalidImportedBridgeExit.UnmarshalFromMap(value); err != nil { + return nil, err + } + p.InnerErrors = append(p.InnerErrors, invalidImportedBridgeExit) + case UnknownErrorType: + p.InnerErrors = append(p.InnerErrors, &UnknownError{}) + default: + return nil, fmt.Errorf("unknown proof generation error type: %s", key) + } + + return nil, nil + } + + errorSourceMap, err := convertMapValue[map[string]interface{}](dataMap, "source") + if err != nil { + // it can be a single error + errSourceString, err := convertMapValue[string](dataMap, "source") + if err != nil { + return err + } + + ppErr, err := getPPErrFn(errSourceString, nil) + if err != nil { + return err + } + + if ppErr != nil { + p.InnerErrors = append(p.InnerErrors, ppErr) + } + + return nil + } + + // there will always be only one key in the source map + for key, value := range errorSourceMap { + ppErr, err := getPPErrFn(key, value) + if err != nil { + return err + } + + if ppErr != nil { + p.InnerErrors = append(p.InnerErrors, ppErr) + } + } + + return nil +} + +// InvalidSignerError is a struct that represents an error that occurs when +// the signer of the certificate is invalid, or the hash that was signed was not valid. +type InvalidSignerError struct { + Declared common.Address `json:"declared"` + Recovered common.Address `json:"recovered"` +} + +// String is the implementation of the Error interface +func (e *InvalidSignerError) String() string { + return fmt.Sprintf("%s. Declared: %s, Computed: %s", + InvalidSignerErrorType, e.Declared.String(), e.Recovered.String()) +} + +func (e *InvalidSignerError) UnmarshalFromMap(data interface{}) error { + dataMap, ok := data.(map[string]interface{}) + if !ok { + return errNotMap + } + + declared, err := convertMapValue[string](dataMap, "declared") + if err != nil { + return err + } + + recovered, err := convertMapValue[string](dataMap, "recovered") + if err != nil { + return err + } + + e.Declared = common.HexToAddress(declared) + e.Recovered = common.HexToAddress(recovered) + + return nil +} + +// DeclaredComputedError is a base struct for errors that have both declared and computed values. +type DeclaredComputedError struct { + Declared common.Hash `json:"declared"` + Computed common.Hash `json:"computed"` + ErrType string +} + +// String is the implementation of the Error interface +func (e *DeclaredComputedError) String() string { + return fmt.Sprintf("%s. Declared: %s, Computed: %s", + e.ErrType, e.Declared.String(), e.Computed.String()) +} + +// UnmarshalFromMap is the implementation of the Error interface +func (e *DeclaredComputedError) UnmarshalFromMap(data interface{}) error { + dataMap, ok := data.(map[string]interface{}) + if !ok { + return errNotMap + } + + declared, err := convertMapValue[string](dataMap, "declared") + if err != nil { + return err + } + + computed, err := convertMapValue[string](dataMap, "computed") + if err != nil { + return err + } + + e.Declared = common.HexToHash(declared) + e.Computed = common.HexToHash(computed) + + return nil +} + +// InvalidPreviousLocalExitRoot is a struct that represents an error that occurs when +// the previous local exit root is invalid. +type InvalidPreviousLocalExitRoot struct { + *DeclaredComputedError +} + +func NewInvalidPreviousLocalExitRoot() *InvalidPreviousLocalExitRoot { + return &InvalidPreviousLocalExitRoot{ + DeclaredComputedError: &DeclaredComputedError{ErrType: InvalidPreviousLERErrorType}, + } +} + +// InvalidPreviousBalanceRoot is a struct that represents an error that occurs when +// the previous balance root is invalid. +type InvalidPreviousBalanceRoot struct { + *DeclaredComputedError +} + +func NewInvalidPreviousBalanceRoot() *InvalidPreviousBalanceRoot { + return &InvalidPreviousBalanceRoot{ + DeclaredComputedError: &DeclaredComputedError{ErrType: InvalidPreviousBalanceRootErrorType}, + } +} + +// InvalidPreviousNullifierRoot is a struct that represents an error that occurs when +// the previous nullifier root is invalid. +type InvalidPreviousNullifierRoot struct { + *DeclaredComputedError +} + +func NewInvalidPreviousNullifierRoot() *InvalidPreviousNullifierRoot { + return &InvalidPreviousNullifierRoot{ + DeclaredComputedError: &DeclaredComputedError{ErrType: InvalidPreviousNullifierRootErrorType}, + } +} + +// InvalidNewLocalExitRoot is a struct that represents an error that occurs when +// the new local exit root is invalid. +type InvalidNewLocalExitRoot struct { + *DeclaredComputedError +} + +func NewInvalidNewLocalExitRoot() *InvalidNewLocalExitRoot { + return &InvalidNewLocalExitRoot{ + DeclaredComputedError: &DeclaredComputedError{ErrType: InvalidNewLocalExitRootErrorType}, + } +} + +// InvalidNewBalanceRoot is a struct that represents an error that occurs when +// the new balance root is invalid. +type InvalidNewBalanceRoot struct { + *DeclaredComputedError +} + +func NewInvalidNewBalanceRoot() *InvalidNewBalanceRoot { + return &InvalidNewBalanceRoot{ + DeclaredComputedError: &DeclaredComputedError{ErrType: InvalidNewBalanceRootErrorType}, + } +} + +// InvalidNewNullifierRoot is a struct that represents an error that occurs when +// the new nullifier root is invalid. +type InvalidNewNullifierRoot struct { + *DeclaredComputedError +} + +func NewInvalidNewNullifierRoot() *InvalidNewNullifierRoot { + return &InvalidNewNullifierRoot{ + DeclaredComputedError: &DeclaredComputedError{ErrType: InvalidNewNullifierRootErrorType}, + } +} + +// InvalidImportedExitsRoot is a struct that represents an error that occurs when +// the imported exits root is invalid. +type InvalidImportedExitsRoot struct { + *DeclaredComputedError +} + +func NewInvalidImportedExitsRoot() *InvalidImportedExitsRoot { + return &InvalidImportedExitsRoot{ + DeclaredComputedError: &DeclaredComputedError{ErrType: InvalidImportedExitsRootErrorType}, + } +} + +// MismatchImportedExitsRoot is a struct that represents an error that occurs when +// the commitment to the list of imported bridge exits but the list of imported bridge exits is empty. +type MismatchImportedExitsRoot struct{} + +// String is the implementation of the Error interface +func (e *MismatchImportedExitsRoot) String() string { + return fmt.Sprintf(`%s: The commitment to the list of imported bridge exits + should be Some if and only if this list is non-empty, should be None otherwise.`, + MismatchImportedExitsRootErrorType) +} + +// InvalidNullifierPath is a struct that represents an error that occurs when +// the provided nullifier path is invalid. +type InvalidNullifierPath struct{} + +// String is the implementation of the Error interface +func (e *InvalidNullifierPath) String() string { + return fmt.Sprintf("%s: The provided nullifier path is invalid", InvalidNullifierPathErrorType) +} + +// InvalidBalancePath is a struct that represents an error that occurs when +// the provided balance path is invalid. +type InvalidBalancePath struct{} + +// String is the implementation of the Error interface +func (e *InvalidBalancePath) String() string { + return fmt.Sprintf("%s: The provided balance path is invalid", InvalidBalancePathErrorType) +} + +// BalanceOverflowInBridgeExit is a struct that represents an error that occurs when +// bridge exit led to balance overflow. +type BalanceOverflowInBridgeExit struct{} + +// String is the implementation of the Error interface +func (e *BalanceOverflowInBridgeExit) String() string { + return fmt.Sprintf("%s: The imported bridge exit led to balance overflow.", BalanceOverflowInBridgeExitErrorType) +} + +// BalanceUnderflowInBridgeExit is a struct that represents an error that occurs when +// bridge exit led to balance underflow. +type BalanceUnderflowInBridgeExit struct{} + +// String is the implementation of the Error interface +func (e *BalanceUnderflowInBridgeExit) String() string { + return fmt.Sprintf("%s: The imported bridge exit led to balance underflow.", BalanceUnderflowInBridgeExitErrorType) +} + +// CannotExitToSameNetwork is a struct that represents an error that occurs when +// the user tries to exit to the same network. +type CannotExitToSameNetwork struct{} + +// String is the implementation of the Error interface +func (e *CannotExitToSameNetwork) String() string { + return fmt.Sprintf("%s: The provided bridge exit goes to the sender’s own network which is not permitted.", + CannotExitToSameNetworkErrorType) +} + +// InvalidMessageOriginNetwork is a struct that represents an error that occurs when +// the origin network of the message is invalid. +type InvalidMessageOriginNetwork struct{} + +// String is the implementation of the Error interface +func (e *InvalidMessageOriginNetwork) String() string { + return fmt.Sprintf("%s: The origin network of the message is invalid.", InvalidMessageOriginNetworkErrorType) +} + +// TokenInfoError is a struct inherited by other errors that have a TokenInfo field. +type TokenInfoError struct { + TokenInfo *TokenInfo `json:"token_info"` + isNested bool +} + +func (e *TokenInfoError) UnmarshalFromMap(data interface{}) error { + dataMap, ok := data.(map[string]interface{}) + if !ok { + return errNotMap + } + + var ( + err error + tokenInfoMap map[string]interface{} + ) + + if e.isNested { + tokenInfoMap, err = convertMapValue[map[string]interface{}](dataMap, "TokenInfo") + if err != nil { + return err + } + } else { + tokenInfoMap = dataMap + } + + originNetwork, err := convertMapValue[uint32](tokenInfoMap, "origin_network") + if err != nil { + return err + } + + originAddress, err := convertMapValue[string](tokenInfoMap, "origin_token_address") + if err != nil { + return err + } + + e.TokenInfo = &TokenInfo{ + OriginNetwork: originNetwork, + OriginTokenAddress: common.HexToAddress(originAddress), + } + + return nil +} + +// InvalidL1TokenInfo is a struct that represents an error that occurs when +// the L1 token info is invalid. +type InvalidL1TokenInfo struct { + *TokenInfoError +} + +// NewInvalidL1TokenInfo returns a new instance of InvalidL1TokenInfo. +func NewInvalidL1TokenInfo() *InvalidL1TokenInfo { + return &InvalidL1TokenInfo{ + TokenInfoError: &TokenInfoError{isNested: true}, + } +} + +// String is the implementation of the Error interface +func (e *InvalidL1TokenInfo) String() string { + return fmt.Sprintf("%s: The L1 token info is invalid. %s", + InvalidL1TokenInfoErrorType, e.TokenInfo.String()) +} + +// MissingTokenBalanceProof is a struct that represents an error that occurs when +// the token balance proof is missing. +type MissingTokenBalanceProof struct { + *TokenInfoError +} + +// NewMissingTokenBalanceProof returns a new instance of MissingTokenBalanceProof. +func NewMissingTokenBalanceProof() *MissingTokenBalanceProof { + return &MissingTokenBalanceProof{ + TokenInfoError: &TokenInfoError{isNested: true}, + } +} + +// String is the implementation of the Error interface +func (e *MissingTokenBalanceProof) String() string { + return fmt.Sprintf("%s: The provided token is missing a balance proof. %s", + MissingTokenBalanceProofErrorType, e.TokenInfo.String()) +} + +// DuplicateTokenBalanceProof is a struct that represents an error that occurs when +// the token balance proof is duplicated. +type DuplicateTokenBalanceProof struct { + *TokenInfoError +} + +// NewDuplicateTokenBalanceProof returns a new instance of DuplicateTokenBalanceProof. +func NewDuplicateTokenBalanceProof() *DuplicateTokenBalanceProof { + return &DuplicateTokenBalanceProof{ + TokenInfoError: &TokenInfoError{isNested: true}, + } +} + +// String is the implementation of the Error interface +func (e *DuplicateTokenBalanceProof) String() string { + return fmt.Sprintf("%s: The provided token comes with multiple balance proofs. %s", + DuplicateTokenBalanceProofErrorType, e.TokenInfo.String()) +} + +// InvalidSignature is a struct that represents an error that occurs when +// the signature is invalid. +type InvalidSignature struct{} + +// String is the implementation of the Error interface +func (e *InvalidSignature) String() string { + return fmt.Sprintf("%s: The provided signature is invalid.", InvalidSignatureErrorType) +} + +// UnknownError is a struct that represents an error that occurs when +// an unknown error is encountered. +type UnknownError struct{} + +// String is the implementation of the Error interface +func (e *UnknownError) String() string { + return fmt.Sprintf("%s: An unknown error occurred.", UnknownErrorType) +} + +// InvalidImportedBridgeExit is a struct that represents an error that occurs when +// the imported bridge exit is invalid. +type InvalidImportedBridgeExit struct { + GlobalIndex *GlobalIndex `json:"global_index"` + ErrorType string `json:"error_type"` +} + +// String is the implementation of the Error interface +func (e *InvalidImportedBridgeExit) String() string { + var errorDescription string + switch e.ErrorType { + case "MismatchGlobalIndexInclusionProof": + errorDescription = "The global index and the inclusion proof do not both correspond " + + "to the same network type: mainnet or rollup." + case "MismatchL1Root": + errorDescription = "The provided L1 info root does not match the one provided in the inclusion proof." + case "MismatchMER": + errorDescription = "The provided MER does not match the one provided in the inclusion proof." + case "MismatchRER": + errorDescription = "The provided RER does not match the one provided in the inclusion proof." + case "InvalidMerklePathLeafToLER": + errorDescription = "The inclusion proof from the leaf to the LER is invalid." + case "InvalidMerklePathLERToRER": + errorDescription = "The inclusion proof from the LER to the RER is invalid." + case "InvalidMerklePathGERToL1Root": + errorDescription = "The inclusion proof from the GER to the L1 info Root is invalid." + case "InvalidExitNetwork": + errorDescription = "The provided imported bridge exit does not target the right destination network." + default: + errorDescription = "An unknown error occurred." + } + + return fmt.Sprintf("%s: Global index: %s. Error type: %s. %s", + InvalidImportedBridgeExitErrorType, e.GlobalIndex.String(), e.ErrorType, errorDescription) +} + +func (e *InvalidImportedBridgeExit) UnmarshalFromMap(data interface{}) error { + dataMap, ok := data.(map[string]interface{}) + if !ok { + return errNotMap + } + + sourceErr, err := convertMapValue[string](dataMap, "source") + if err != nil { + return err + } + + e.ErrorType = sourceErr + + globalIndexMap, err := convertMapValue[map[string]interface{}](dataMap, "global_index") + if err != nil { + return err + } + + e.GlobalIndex = &GlobalIndex{} + return e.GlobalIndex.UnmarshalFromMap(globalIndexMap) +} + +// convertMapValue converts the value of a key in a map to a target type. +func convertMapValue[T any](data map[string]interface{}, key string) (T, error) { + value, ok := data[key] + if !ok { + var zero T + return zero, fmt.Errorf("key %s not found in map", key) + } + + // Try a direct type assertion + if convertedValue, ok := value.(T); ok { + return convertedValue, nil + } + + // If direct assertion fails, handle numeric type conversions + var target T + targetType := reflect.TypeOf(target) + + // Check if value is a float64 (default JSON number type) and target is a numeric type + if floatValue, ok := value.(float64); ok && targetType.Kind() >= reflect.Int && targetType.Kind() <= reflect.Uint64 { + convertedValue, err := convertNumeric(floatValue, targetType) + if err != nil { + return target, fmt.Errorf("conversion error for key %s: %w", key, err) + } + return convertedValue.(T), nil //nolint:forcetypeassert + } + + return target, fmt.Errorf("value of key %s is not of type %T", key, target) +} + +// convertNumeric converts a float64 to the specified numeric type. +func convertNumeric(value float64, targetType reflect.Type) (interface{}, error) { + switch targetType.Kind() { + case reflect.Int: + return int(value), nil + case reflect.Int8: + return int8(value), nil + case reflect.Int16: + return int16(value), nil + case reflect.Int32: + return int32(value), nil + case reflect.Int64: + return int64(value), nil + case reflect.Uint: + return uint(value), nil + case reflect.Uint8: + return uint8(value), nil + case reflect.Uint16: + return uint16(value), nil + case reflect.Uint32: + return uint32(value), nil + case reflect.Uint64: + return uint64(value), nil + case reflect.Float32: + return float32(value), nil + case reflect.Float64: + return value, nil + default: + return nil, errors.New("unsupported target type") + } +} diff --git a/agglayer/proof_verification_error.go b/agglayer/proof_verification_error.go new file mode 100644 index 00000000..dd5c5f74 --- /dev/null +++ b/agglayer/proof_verification_error.go @@ -0,0 +1,164 @@ +package agglayer + +import "fmt" + +const ( + VersionMismatchErrorType = "VersionMismatch" + CoreErrorType = "Core" + RecursionErrorType = "Recursion" + PlankErrorType = "Plank" + Groth16ErrorType = "Groth16" + InvalidPublicValuesErrorType = "InvalidPublicValues" +) + +// ProofVerificationError is an error that is returned when verifying a proof +type ProofVerificationError struct { + InnerErrors []PPError +} + +// String is the implementation of the Error interface +func (p *ProofVerificationError) String() string { + return fmt.Sprintf("Proof verification error: %v", p.InnerErrors) +} + +// Unmarshal unmarshals the data from a map into a ProofVerificationError struct. +func (p *ProofVerificationError) Unmarshal(data interface{}) error { + getPPErrFn := func(key string, value interface{}) (PPError, error) { + switch key { + case VersionMismatchErrorType: + versionMismatch := &VersionMismatch{} + if err := versionMismatch.Unmarshal(value); err != nil { + return nil, err + } + return versionMismatch, nil + case CoreErrorType: + core := &Core{} + if err := core.Unmarshal(value); err != nil { + return nil, err + } + return core, nil + case RecursionErrorType: + recursion := &Recursion{} + if err := recursion.Unmarshal(value); err != nil { + return nil, err + } + return recursion, nil + case PlankErrorType: + plank := &Plank{} + if err := plank.Unmarshal(value); err != nil { + return nil, err + } + return plank, nil + case Groth16ErrorType: + groth16 := &Groth16{} + if err := groth16.Unmarshal(value); err != nil { + return nil, err + } + return groth16, nil + case InvalidPublicValuesErrorType: + return &InvalidPublicValues{}, nil + default: + return nil, fmt.Errorf("unknown proof verification error type: %v", key) + } + } + + getAndAddInnerErrorFn := func(key string, value interface{}) error { + ppErr, err := getPPErrFn(key, value) + if err != nil { + return err + } + + if ppErr != nil { + p.InnerErrors = append(p.InnerErrors, ppErr) + } + + return nil + } + + dataMap, ok := data.(map[string]interface{}) + if !ok { + // it can be a single error + return getAndAddInnerErrorFn(data.(string), nil) //nolint:forcetypeassert + } + + for key, value := range dataMap { + if err := getAndAddInnerErrorFn(key, value); err != nil { + return err + } + } + + return nil +} + +// StringError is an error that is inherited by other errors that expect a string +// field in the data. +type StringError string + +// Unmarshal unmarshals the data from an interface{} into a StringError. +func (e *StringError) Unmarshal(data interface{}) error { + str, ok := data.(string) + if !ok { + return fmt.Errorf("expected string for StringError, got %T", data) + } + *e = StringError(str) + return nil +} + +// VersionMismatch is an error that is returned when the version of the proof is +// different from the version of the core. +type VersionMismatch struct { + StringError +} + +// String is the implementation of the Error interface +func (e *VersionMismatch) String() string { + return fmt.Sprintf("%s: %s", VersionMismatchErrorType, e.StringError) +} + +// Core is an error that is returned when the core machine verification fails. +type Core struct { + StringError +} + +// String is the implementation of the Error interface +func (e *Core) String() string { + return fmt.Sprintf("%s: Core machine verification error: %s", CoreErrorType, e.StringError) +} + +// Recursion is an error that is returned when the recursion verification fails. +type Recursion struct { + StringError +} + +// String is the implementation of the Error interface +func (e *Recursion) String() string { + return fmt.Sprintf("%s: Recursion verification error: %s", RecursionErrorType, e.StringError) +} + +// Plank is an error that is returned when the plank verification fails. +type Plank struct { + StringError +} + +// String is the implementation of the Error interface +func (e *Plank) String() string { + return fmt.Sprintf("%s: Plank verification error: %s", PlankErrorType, e.StringError) +} + +// Groth16 is an error that is returned when the Groth16 verification fails. +type Groth16 struct { + StringError +} + +// String is the implementation of the Error interface +func (e *Groth16) String() string { + return fmt.Sprintf("%s: Groth16 verification error: %s", Groth16ErrorType, e.StringError) +} + +// InvalidPublicValues is an error that is returned when the public values are invalid. +type InvalidPublicValues struct{} + +// String is the implementation of the Error interface +func (e *InvalidPublicValues) String() string { + return fmt.Sprintf("%s: Invalid public values", InvalidPublicValuesErrorType) +} diff --git a/agglayer/testdata/proof_generation_errors/errors_with_declared_computed_data.json b/agglayer/testdata/proof_generation_errors/errors_with_declared_computed_data.json new file mode 100644 index 00000000..4b1b4029 --- /dev/null +++ b/agglayer/testdata/proof_generation_errors/errors_with_declared_computed_data.json @@ -0,0 +1,45 @@ +[ + { + "test_name": "InvalidImportedExitsRoot", + "certificate_header": "{\"network_id\":14,\"height\":1,\"epoch_number\":null,\"certificate_index\":null,\"certificate_id\":\"0xa80cd4abb016bbb3c3058f923a88be1ad49d68277366c55554d6f13d62428a1f\",\"new_local_exit_root\":\"0x566244fbf813b6926f6895142979f61ed6706184909cb8c819cd0216202a8aa9\",\"metadata\":\"0x000000000000000000000000000000000000000000000000000000000000001f\",\"status\":{\"InError\":{\"error\":{\"ProofGenerationError\":{\"generation_type\":\"Native\",\"source\":{\"InvalidImportedExitsRoot\":{\"declared\":\"0x1116837a43bdc3dd9f114558daf4b26ed4eeeeec\",\"computed\":\"0x20222bfbb55589f7fd0bec3666e3de469111ce3c\"}}}}}}}" + }, + { + "test_name": "InvalidNewBalanceRoot", + "certificate_header": "{\"network_id\":11,\"height\":31,\"epoch_number\":null,\"certificate_index\":null,\"certificate_id\":\"0xa80cd4abb016bbb3c3058f923a88be1ad49d68277366c55554d6f13d62428a1f\",\"new_local_exit_root\":\"0x566244fbf813b6926f6895142979f61ed6706184909cb8c819cd0216202a8aa9\",\"metadata\":\"0x000000000000000000000000000000000000000000000000000000000000001f\",\"status\":{\"InError\":{\"error\":{\"ProofGenerationError\":{\"generation_type\":\"Native\",\"source\":{\"InvalidNewBalanceRoot\":{\"declared\":\"0x5b06837a43bdc3dd9f114558daf4b26ed4eeeeec\",\"computed\":\"0x20e92bfbb55589f7fd0bec3666e3de469111ce3c\"}}}}}}}" + }, + { + "test_name": "InvalidNewLocalExitRoot", + "certificate_header": "{\"network_id\":3,\"height\":22,\"epoch_number\":null,\"certificate_index\":null,\"certificate_id\":\"0xa80cd4abb016bbb3c3058f923a88be1ad49d68277366c55554d6f13d62428a1f\",\"new_local_exit_root\":\"0x566244fbf813b6926f6895142979f61ed6706184909cb8c819cd0216202a8aa9\",\"metadata\":\"0x000000000000000000000000000000000000000000000000000000000000001f\",\"status\":{\"InError\":{\"error\":{\"ProofGenerationError\":{\"generation_type\":\"Native\",\"source\":{\"InvalidNewLocalExitRoot\":{\"declared\":\"0x5b06837a43bdc3dd9f114558daf4b26ed49831ec\",\"computed\":\"0x20e92bfbb55589f7fd0bec3666e3de469525ce3c\"}}}}}}}" + }, + { + "test_name": "InvalidNewNullifierRoot", + "certificate_header": "{\"network_id\":2,\"height\":12,\"epoch_number\":null,\"certificate_index\":null,\"certificate_id\":\"0xa80cd4abb016bbb3c3058f923a88be1ad49d68277366c55554d6f13d62428a1f\",\"new_local_exit_root\":\"0x566244fbf813b6926f6895142979f61ed6706184909cb8c819cd0216202a8aa9\",\"metadata\":\"0x000000000000000000000000000000000000000000000000000000000000001f\",\"status\":{\"InError\":{\"error\":{\"ProofGenerationError\":{\"generation_type\":\"Native\",\"source\":{\"InvalidNewNullifierRoot\":{\"declared\":\"0x5b06837a43bdc3dd9f114558daf4b26ed4ccceec\",\"computed\":\"0x20e92bfbb55589f7fd0bec3666e3de222111ce3c\"}}}}}}}" + }, + { + "test_name": "InvalidPreviousBalanceRoot", + "certificate_header": "{\"network_id\":2,\"height\":11,\"epoch_number\":null,\"certificate_index\":null,\"certificate_id\":\"0xa80cd4abb016bbb3c3058f923a88be1ad49d68277366c55554d6f13d62428a1f\",\"new_local_exit_root\":\"0x566244fbf813b6926f6895142979f61ed6706184909cb8c819cd0216202a8aa9\",\"metadata\":\"0x000000000000000000000000000000000000000000000000000000000000001f\",\"status\":{\"InError\":{\"error\":{\"ProofGenerationError\":{\"generation_type\":\"Native\",\"source\":{\"InvalidPreviousBalanceRoot\":{\"declared\":\"0x5b06837a43bdc3dd9f114558daf4b26ed49842ec\",\"computed\":\"0x20e92bfbb55589f7fd0bec3666e3de469526de3c\"}}}}}}}" + }, + { + "test_name": "InvalidPreviousLocalExitRoot", + "certificate_header": "{\"network_id\":1,\"height\":0,\"epoch_number\":null,\"certificate_index\":null,\"certificate_id\":\"0xa80cd4abb016bbb3c3058f923a88be1ad49d68277366c55554d6f13d62428a1f\",\"new_local_exit_root\":\"0x566244fbf813b6926f6895142979f61ed6706184909cb8c819cd0216202a8aa9\",\"metadata\":\"0x000000000000000000000000000000000000000000000000000000000000001f\",\"status\":{\"InError\":{\"error\":{\"ProofGenerationError\":{\"generation_type\":\"Native\",\"source\":{\"InvalidPreviousLocalExitRoot\":{\"declared\":\"0x5b06837a43bdc3dd9f114558daf4b26ed49842ed\",\"computed\":\"0x20e92bfbb55589f7fd0bec3666e3de469526de3e\"}}}}}}}" + }, + { + "test_name": "InvalidPreviousNullifierRoot", + "certificate_header": "{\"network_id\":21,\"height\":111,\"epoch_number\":null,\"certificate_index\":null,\"certificate_id\":\"0xa80cd4abb016bbb3c3058f923a88be1ad49d68277366c55554d6f13d62428a1f\",\"new_local_exit_root\":\"0x566244fbf813b6926f6895142979f61ed6706184909cb8c819cd0216202a8aa9\",\"metadata\":\"0x000000000000000000000000000000000000000000000000000000000000001f\",\"status\":{\"InError\":{\"error\":{\"ProofGenerationError\":{\"generation_type\":\"Native\",\"source\":{\"InvalidPreviousNullifierRoot\":{\"declared\":\"0x5b06837a43bdc3dd9f114558daf4b26ed49842ee\",\"computed\":\"0x20e92bfbb55589f7fd0bec3666e3de469526de3e\"}}}}}}}" + }, + { + "test_name": "InvalidPreviousNullifierRoot_missing_declared", + "expected_error": "key declared not found in map", + "certificate_header": "{\"network_id\":21,\"height\":111,\"epoch_number\":null,\"certificate_index\":null,\"certificate_id\":\"0xa80cd4abb016bbb3c3058f923a88be1ad49d68277366c55554d6f13d62428a1f\",\"new_local_exit_root\":\"0x566244fbf813b6926f6895142979f61ed6706184909cb8c819cd0216202a8aa9\",\"metadata\":\"0x000000000000000000000000000000000000000000000000000000000000001f\",\"status\":{\"InError\":{\"error\":{\"ProofGenerationError\":{\"generation_type\":\"Native\",\"source\":{\"InvalidPreviousNullifierRoot\":{\"computed\":\"0x20e92bfbb55589f7fd0bec3666e3de469526de3e\"}}}}}}}" + }, + { + "test_name": "InvalidPreviousNullifierRoot_missing_computed", + "expected_error": "key computed not found in map", + "certificate_header": "{\"network_id\":21,\"height\":111,\"epoch_number\":null,\"certificate_index\":null,\"certificate_id\":\"0xa80cd4abb016bbb3c3058f923a88be1ad49d68277366c55554d6f13d62428a1f\",\"new_local_exit_root\":\"0x566244fbf813b6926f6895142979f61ed6706184909cb8c819cd0216202a8aa9\",\"metadata\":\"0x000000000000000000000000000000000000000000000000000000000000001f\",\"status\":{\"InError\":{\"error\":{\"ProofGenerationError\":{\"generation_type\":\"Native\",\"source\":{\"InvalidPreviousNullifierRoot\":{\"declared\":\"0x5b06837a43bdc3dd9f114558daf4b26ed49842ee\"}}}}}}}" + }, + { + "test_name": "InvalidPreviousNullifierRoot_missing_inner_error", + "expected_error": "not a map", + "certificate_header": "{\"network_id\":21,\"height\":111,\"epoch_number\":null,\"certificate_index\":null,\"certificate_id\":\"0xa80cd4abb016bbb3c3058f923a88be1ad49d68277366c55554d6f13d62428a1f\",\"new_local_exit_root\":\"0x566244fbf813b6926f6895142979f61ed6706184909cb8c819cd0216202a8aa9\",\"metadata\":\"0x000000000000000000000000000000000000000000000000000000000000001f\",\"status\":{\"InError\":{\"error\":{\"ProofGenerationError\":{\"generation_type\":\"Native\",\"source\":\"InvalidPreviousNullifierRoot\"}}}}}" + } +] \ No newline at end of file diff --git a/agglayer/testdata/proof_generation_errors/errors_with_token_info.json b/agglayer/testdata/proof_generation_errors/errors_with_token_info.json new file mode 100644 index 00000000..6884676a --- /dev/null +++ b/agglayer/testdata/proof_generation_errors/errors_with_token_info.json @@ -0,0 +1,29 @@ +[ + { + "test_name": "InvalidL1TokenInfo", + "certificate_header":"{\"network_id\":1,\"height\":0,\"epoch_number\":null,\"certificate_index\":null,\"certificate_id\":\"0xa80cd4abb016bbb3c3058f923a88be1ad49d68277366c55554d6f13d62428a1f\",\"new_local_exit_root\":\"0x566244fbf813b6926f6895142979f61ed6706184909cb8c819cd0216202a8aa9\",\"metadata\":\"0x000000000000000000000000000000000000000000000000000000000000001f\",\"status\":{\"InError\":{\"error\":{\"ProofGenerationError\":{\"generation_type\":\"Native\",\"source\":{\"InvalidL1TokenInfo\":{\"TokenInfo\":{\"origin_network\":1,\"origin_token_address\":\"0x566244fbf813b6926f6895142979f61ed6706184909cb8c819cd0216202a8aa2\"}}}}}}}}" + }, + { + "test_name": "MissingTokenBalanceProof", + "certificate_header": "{\"network_id\":2111,\"height\":1,\"epoch_number\":null,\"certificate_index\":null,\"certificate_id\":\"0xa80cd4abb016bbb3c3058f923a88be1ad49d68277366c55554d6f13d62428a1f\",\"new_local_exit_root\":\"0x566244fbf813b6926f6895142979f61ed6706184909cb8c819cd0216202a8aa9\",\"metadata\":\"0x000000000000000000000000000000000000000000000000000000000000001f\",\"status\":{\"InError\":{\"error\":{\"ProofGenerationError\":{\"generation_type\":\"Native\",\"source\":{\"MissingTokenBalanceProof\":{\"TokenInfo\":{\"origin_network\":2111,\"origin_token_address\":\"0x566244fbf813b6926f6895142979f61ed6706184909cb8c819cd0216202a8aa2\"}}}}}}}}" + }, + { + "test_name": "DuplicateTokenBalanceProof", + "certificate_header": "{\"network_id\":100000000,\"height\":18446744073709551615,\"epoch_number\":null,\"certificate_index\":null,\"certificate_id\":\"0xa80cd4abb016bbb3c3058f923a88be1ad49d68277366c55554d6f13d62428a1f\",\"new_local_exit_root\":\"0x566244fbf813b6926f6895142979f61ed6706184909cb8c819cd0216202a8aa9\",\"metadata\":\"0x000000000000000000000000000000000000000000000000000000000000001f\",\"status\":{\"InError\":{\"error\":{\"ProofGenerationError\":{\"generation_type\":\"Native\",\"source\":{\"DuplicateTokenBalanceProof\":{\"TokenInfo\":{\"origin_network\":10000000000,\"origin_token_address\":\"0x566244fbf813b6926f6895142979f61ed6706184909cb8c819cd0216202a8aa2\"}}}}}}}}" + }, + { + "test_name": "DuplicateTokenBalanceProof_missing_token_info", + "expected_error": "key TokenInfo not found in map", + "certificate_header": "{\"network_id\":100000000,\"height\":18446744073709551615,\"epoch_number\":null,\"certificate_index\":null,\"certificate_id\":\"0xa80cd4abb016bbb3c3058f923a88be1ad49d68277366c55554d6f13d62428a1f\",\"new_local_exit_root\":\"0x566244fbf813b6926f6895142979f61ed6706184909cb8c819cd0216202a8aa9\",\"metadata\":\"0x000000000000000000000000000000000000000000000000000000000000001f\",\"status\":{\"InError\":{\"error\":{\"ProofGenerationError\":{\"generation_type\":\"Native\",\"source\":{\"DuplicateTokenBalanceProof\":{}}}}}}}" + }, + { + "test_name": "DuplicateTokenBalanceProof_missing_origin_network", + "expected_error": "key origin_network not found in map", + "certificate_header": "{\"network_id\":100000000,\"height\":18446744073709551615,\"epoch_number\":null,\"certificate_index\":null,\"certificate_id\":\"0xa80cd4abb016bbb3c3058f923a88be1ad49d68277366c55554d6f13d62428a1f\",\"new_local_exit_root\":\"0x566244fbf813b6926f6895142979f61ed6706184909cb8c819cd0216202a8aa9\",\"metadata\":\"0x000000000000000000000000000000000000000000000000000000000000001f\",\"status\":{\"InError\":{\"error\":{\"ProofGenerationError\":{\"generation_type\":\"Native\",\"source\":{\"DuplicateTokenBalanceProof\":{\"TokenInfo\":{\"origin_token_address\":\"0x566244fbf813b6926f6895142979f61ed6706184909cb8c819cd0216202a8aa2\"}}}}}}}}" + }, + { + "test_name": "DuplicateTokenBalanceProof_missing_origin_token_address", + "expected_error": "key origin_token_address not found in map", + "certificate_header": "{\"network_id\":100000000,\"height\":18446744073709551615,\"epoch_number\":null,\"certificate_index\":null,\"certificate_id\":\"0xa80cd4abb016bbb3c3058f923a88be1ad49d68277366c55554d6f13d62428a1f\",\"new_local_exit_root\":\"0x566244fbf813b6926f6895142979f61ed6706184909cb8c819cd0216202a8aa9\",\"metadata\":\"0x000000000000000000000000000000000000000000000000000000000000001f\",\"status\":{\"InError\":{\"error\":{\"ProofGenerationError\":{\"generation_type\":\"Native\",\"source\":{\"DuplicateTokenBalanceProof\":{\"TokenInfo\":{\"origin_network\":10000000000}}}}}}}}" + } +] \ No newline at end of file diff --git a/agglayer/testdata/proof_generation_errors/errors_without_inner_data.json b/agglayer/testdata/proof_generation_errors/errors_without_inner_data.json new file mode 100644 index 00000000..87946f16 --- /dev/null +++ b/agglayer/testdata/proof_generation_errors/errors_without_inner_data.json @@ -0,0 +1,38 @@ +[ + { + "test_name": "MismatchImportedExitsRoot", + "certificate_header": "{\"network_id\":14,\"height\":1,\"epoch_number\":null,\"certificate_index\":null,\"certificate_id\":\"0xa80cd4abb016bbb3c3058f923a88be1ad49d68277366c55554d6f13d62428a1f\",\"new_local_exit_root\":\"0x566244fbf813b6926f6895142979f61ed6706184909cb8c819cd0216202a8aa9\",\"metadata\":\"0x000000000000000000000000000000000000000000000000000000000000001f\",\"status\":{\"InError\":{\"error\":{\"ProofGenerationError\":{\"generation_type\":\"Native\",\"source\":\"MismatchImportedExitsRoot\"}}}}}" + }, + { + "test_name": "InvalidNullifierPath", + "certificate_header": "{\"network_id\":15,\"height\":2,\"epoch_number\":null,\"certificate_index\":null,\"certificate_id\":\"0xa80cd4abb016bbb3c3058f923a88be1ad49d68277366c55554d6f13d62428a1f\",\"new_local_exit_root\":\"0x566244fbf813b6926f6895142979f61ed6706184909cb8c819cd0216202a8aa9\",\"metadata\":\"0x000000000000000000000000000000000000000000000000000000000000001f\",\"status\":{\"InError\":{\"error\":{\"ProofGenerationError\":{\"generation_type\":\"Native\",\"source\":\"InvalidNullifierPath\"}}}}}" + }, + { + "test_name": "InvalidBalancePath", + "certificate_header": "{\"network_id\":16,\"height\":3,\"epoch_number\":null,\"certificate_index\":null,\"certificate_id\":\"0xa80cd4abb016bbb3c3058f923a88be1ad49d68277366c55554d6f13d62428a1f\",\"new_local_exit_root\":\"0x566244fbf813b6926f6895142979f61ed6706184909cb8c819cd0216202a8aa9\",\"metadata\":\"0x000000000000000000000000000000000000000000000000000000000000001f\",\"status\":{\"InError\":{\"error\":{\"ProofGenerationError\":{\"generation_type\":\"Native\",\"source\":\"InvalidBalancePath\"}}}}}" + }, + { + "test_name": "BalanceOverflowInBridgeExit", + "certificate_header": "{\"network_id\":17,\"height\":4,\"epoch_number\":null,\"certificate_index\":null,\"certificate_id\":\"0xa80cd4abb016bbb3c3058f923a88be1ad49d68277366c55554d6f13d62428a1f\",\"new_local_exit_root\":\"0x566244fbf813b6926f6895142979f61ed6706184909cb8c819cd0216202a8aa9\",\"metadata\":\"0x000000000000000000000000000000000000000000000000000000000000001f\",\"status\":{\"InError\":{\"error\":{\"ProofGenerationError\":{\"generation_type\":\"Native\",\"source\":\"BalanceOverflowInBridgeExit\"}}}}}" + }, + { + "test_name": "BalanceUnderflowInBridgeExit", + "certificate_header": "{\"network_id\":18,\"height\":5,\"epoch_number\":null,\"certificate_index\":null,\"certificate_id\":\"0xa80cd4abb016bbb3c3058f923a88be1ad49d68277366c55554d6f13d62428a1f\",\"new_local_exit_root\":\"0x566244fbf813b6926f6895142979f61ed6706184909cb8c819cd0216202a8aa9\",\"metadata\":\"0x000000000000000000000000000000000000000000000000000000000000001f\",\"status\":{\"InError\":{\"error\":{\"ProofGenerationError\":{\"generation_type\":\"Native\",\"source\":\"BalanceUnderflowInBridgeExit\"}}}}}" + }, + { + "test_name": "CannotExitToSameNetwork", + "certificate_header": "{\"network_id\":19,\"height\":6,\"epoch_number\":null,\"certificate_index\":null,\"certificate_id\":\"0xa80cd4abb016bbb3c3058f923a88be1ad49d68277366c55554d6f13d62428a1f\",\"new_local_exit_root\":\"0x566244fbf813b6926f6895142979f61ed6706184909cb8c819cd0216202a8aa9\",\"metadata\":\"0x000000000000000000000000000000000000000000000000000000000000001f\",\"status\":{\"InError\":{\"error\":{\"ProofGenerationError\":{\"generation_type\":\"Native\",\"source\":\"CannotExitToSameNetwork\"}}}}}" + }, + { + "test_name": "InvalidMessageOriginNetwork", + "certificate_header": "{\"network_id\":20,\"height\":7,\"epoch_number\":null,\"certificate_index\":null,\"certificate_id\":\"0xa80cd4abb016bbb3c3058f923a88be1ad49d68277366c55554d6f13d62428a1f\",\"new_local_exit_root\":\"0x566244fbf813b6926f6895142979f61ed6706184909cb8c819cd0216202a8aa9\",\"metadata\":\"0x000000000000000000000000000000000000000000000000000000000000001f\",\"status\":{\"InError\":{\"error\":{\"ProofGenerationError\":{\"generation_type\":\"Native\",\"source\":\"InvalidMessageOriginNetwork\"}}}}}" + }, + { + "test_name": "UnknownError", + "certificate_header": "{\"network_id\":21,\"height\":8,\"epoch_number\":null,\"certificate_index\":null,\"certificate_id\":\"0xa80cd4abb016bbb3c3058f923a88be1ad49d68277366c55554d6f13d62428a1f\",\"new_local_exit_root\":\"0x566244fbf813b6926f6895142979f61ed6706184909cb8c819cd0216202a8aa9\",\"metadata\":\"0x000000000000000000000000000000000000000000000000000000000000001f\",\"status\":{\"InError\":{\"error\":{\"ProofGenerationError\":{\"generation_type\":\"Native\",\"source\":\"UnknownError\"}}}}}" + }, + { + "test_name": "InvalidSignature", + "certificate_header": "{\"network_id\":22,\"height\":9,\"epoch_number\":null,\"certificate_index\":null,\"certificate_id\":\"0xa80cd4abb016bbb3c3058f923a88be1ad49d68277366c55554d6f13d62428a1f\",\"new_local_exit_root\":\"0x566244fbf813b6926f6895142979f61ed6706184909cb8c819cd0216202a8aa9\",\"metadata\":\"0x000000000000000000000000000000000000000000000000000000000000001f\",\"status\":{\"InError\":{\"error\":{\"ProofGenerationError\":{\"generation_type\":\"Native\",\"source\":\"InvalidSignature\"}}}}}" + } +] \ No newline at end of file diff --git a/agglayer/testdata/proof_generation_errors/invalid_imported_bridge_exit_errors.json b/agglayer/testdata/proof_generation_errors/invalid_imported_bridge_exit_errors.json new file mode 100644 index 00000000..dc6b8cad --- /dev/null +++ b/agglayer/testdata/proof_generation_errors/invalid_imported_bridge_exit_errors.json @@ -0,0 +1,48 @@ +[ + { + "test_name": "MismatchGlobalIndexInclusionProof", + "certificate_header": "{\"network_id\":1,\"height\":0,\"epoch_number\":null,\"certificate_index\":null,\"certificate_id\":\"0xa80cd4abb016bbb3c3058f923a88be1ad49d68277366c55554d6f13d62428a1f\",\"new_local_exit_root\":\"0x566244fbf813b6926f6895142979f61ed6706184909cb8c819cd0216202a8aa9\",\"metadata\":\"0x000000000000000000000000000000000000000000000000000000000000001f\",\"status\":{\"InError\":{\"error\":{\"ProofGenerationError\":{\"generation_type\":\"Native\",\"source\":{\"InvalidImportedBridgeExit\":{\"source\":\"MismatchGlobalIndexInclusionProof\",\"global_index\":{\"mainnet_flag\":true,\"rollup_index\":0,\"leaf_index\":1}}}}}}}}" + }, + { + "test_name": "MismatchL1Root", + "certificate_header": "{\"network_id\":1,\"height\":1,\"epoch_number\":null,\"certificate_index\":null,\"certificate_id\":\"0xa80cd4abb016bbb3c3058f923a88be1ad49d68277366c55554d6f13d62428a1f\",\"new_local_exit_root\":\"0x566244fbf813b6926f6895142979f61ed6706184909cb8c819cd0216202a8aa9\",\"metadata\":\"0x000000000000000000000000000000000000000000000000000000000000001f\",\"status\":{\"InError\":{\"error\":{\"ProofGenerationError\":{\"generation_type\":\"Native\",\"source\":{\"InvalidImportedBridgeExit\":{\"source\":\"MismatchL1Root\",\"global_index\":{\"mainnet_flag\":true,\"rollup_index\":0,\"leaf_index\":2}}}}}}}}" + }, + { + "test_name": "MismatchMER", + "certificate_header": "{\"network_id\":1,\"height\":2,\"epoch_number\":null,\"certificate_index\":null,\"certificate_id\":\"0xa80cd4abb016bbb3c3058f923a88be1ad49d68277366c55554d6f13d62428a1f\",\"new_local_exit_root\":\"0x566244fbf813b6926f6895142979f61ed6706184909cb8c819cd0216202a8aa9\",\"metadata\":\"0x000000000000000000000000000000000000000000000000000000000000001f\",\"status\":{\"InError\":{\"error\":{\"ProofGenerationError\":{\"generation_type\":\"Native\",\"source\":{\"InvalidImportedBridgeExit\":{\"source\":\"MismatchMER\",\"global_index\":{\"mainnet_flag\":true,\"rollup_index\":0,\"leaf_index\":3}}}}}}}}" + }, + { + "test_name": "MismatchRER", + "certificate_header": "{\"network_id\":1,\"height\":3,\"epoch_number\":null,\"certificate_index\":null,\"certificate_id\":\"0xa80cd4abb016bbb3c3058f923a88be1ad49d68277366c55554d6f13d62428a1f\",\"new_local_exit_root\":\"0x566244fbf813b6926f6895142979f61ed6706184909cb8c819cd0216202a8aa9\",\"metadata\":\"0x000000000000000000000000000000000000000000000000000000000000001f\",\"status\":{\"InError\":{\"error\":{\"ProofGenerationError\":{\"generation_type\":\"Native\",\"source\":{\"InvalidImportedBridgeExit\":{\"source\":\"MismatchRER\",\"global_index\":{\"mainnet_flag\":false,\"rollup_index\":1,\"leaf_index\":4}}}}}}}}" + }, + { + "test_name": "InvalidMerklePathLeafToLER", + "certificate_header": "{\"network_id\":1,\"height\":4,\"epoch_number\":null,\"certificate_index\":null,\"certificate_id\":\"0xa80cd4abb016bbb3c3058f923a88be1ad49d68277366c55554d6f13d62428a1f\",\"new_local_exit_root\":\"0x566244fbf813b6926f6895142979f61ed6706184909cb8c819cd0216202a8aa9\",\"metadata\":\"0x000000000000000000000000000000000000000000000000000000000000001f\",\"status\":{\"InError\":{\"error\":{\"ProofGenerationError\":{\"generation_type\":\"Native\",\"source\":{\"InvalidImportedBridgeExit\":{\"source\":\"InvalidMerklePathLeafToLER\",\"global_index\":{\"mainnet_flag\":true,\"rollup_index\":0,\"leaf_index\":5}}}}}}}}" + }, + { + "test_name": "InvalidMerklePathLERToRER", + "certificate_header": "{\"network_id\":1,\"height\":5,\"epoch_number\":null,\"certificate_index\":null,\"certificate_id\":\"0xa80cd4abb016bbb3c3058f923a88be1ad49d68277366c55554d6f13d62428a1f\",\"new_local_exit_root\":\"0x566244fbf813b6926f6895142979f61ed6706184909cb8c819cd0216202a8aa9\",\"metadata\":\"0x000000000000000000000000000000000000000000000000000000000000001f\",\"status\":{\"InError\":{\"error\":{\"ProofGenerationError\":{\"generation_type\":\"Native\",\"source\":{\"InvalidImportedBridgeExit\":{\"source\":\"InvalidMerklePathLERToRER\",\"global_index\":{\"mainnet_flag\":false,\"rollup_index\":2,\"leaf_index\":6}}}}}}}}" + }, + { + "test_name": "InvalidMerklePathGERToL1Root", + "certificate_header": "{\"network_id\":1,\"height\":6,\"epoch_number\":null,\"certificate_index\":null,\"certificate_id\":\"0xa80cd4abb016bbb3c3058f923a88be1ad49d68277366c55554d6f13d62428a1f\",\"new_local_exit_root\":\"0x566244fbf813b6926f6895142979f61ed6706184909cb8c819cd0216202a8aa9\",\"metadata\":\"0x000000000000000000000000000000000000000000000000000000000000001f\",\"status\":{\"InError\":{\"error\":{\"ProofGenerationError\":{\"generation_type\":\"Native\",\"source\":{\"InvalidImportedBridgeExit\":{\"source\":\"InvalidMerklePathGERToL1Root\",\"global_index\":{\"mainnet_flag\":true,\"rollup_index\":0,\"leaf_index\":7}}}}}}}}" + }, + { + "test_name": "InvalidExitNetwork", + "certificate_header": "{\"network_id\":1,\"height\":7,\"epoch_number\":null,\"certificate_index\":null,\"certificate_id\":\"0xa80cd4abb016bbb3c3058f923a88be1ad49d68277366c55554d6f13d62428a1f\",\"new_local_exit_root\":\"0x566244fbf813b6926f6895142979f61ed6706184909cb8c819cd0216202a8aa9\",\"metadata\":\"0x000000000000000000000000000000000000000000000000000000000000001f\",\"status\":{\"InError\":{\"error\":{\"ProofGenerationError\":{\"generation_type\":\"Native\",\"source\":{\"InvalidImportedBridgeExit\":{\"source\":\"InvalidExitNetwork\",\"global_index\":{\"mainnet_flag\":false,\"rollup_index\":1,\"leaf_index\":8}}}}}}}}" + }, + { + "test_name": "InvalidExitNetwork_missing_source", + "expected_error": "key source not found in map", + "certificate_header": "{\"network_id\":1,\"height\":7,\"epoch_number\":null,\"certificate_index\":null,\"certificate_id\":\"0xa80cd4abb016bbb3c3058f923a88be1ad49d68277366c55554d6f13d62428a1f\",\"new_local_exit_root\":\"0x566244fbf813b6926f6895142979f61ed6706184909cb8c819cd0216202a8aa9\",\"metadata\":\"0x000000000000000000000000000000000000000000000000000000000000001f\",\"status\":{\"InError\":{\"error\":{\"ProofGenerationError\":{\"generation_type\":\"Native\",\"source\":{\"InvalidImportedBridgeExit\":{\"global_index\":{\"mainnet_flag\":false,\"rollup_index\":1,\"leaf_index\":8}}}}}}}}" + }, + { + "test_name": "InvalidExitNetwork_missing_global_index", + "expected_error": "key global_index not found in map", + "certificate_header": "{\"network_id\":1,\"height\":7,\"epoch_number\":null,\"certificate_index\":null,\"certificate_id\":\"0xa80cd4abb016bbb3c3058f923a88be1ad49d68277366c55554d6f13d62428a1f\",\"new_local_exit_root\":\"0x566244fbf813b6926f6895142979f61ed6706184909cb8c819cd0216202a8aa9\",\"metadata\":\"0x000000000000000000000000000000000000000000000000000000000000001f\",\"status\":{\"InError\":{\"error\":{\"ProofGenerationError\":{\"generation_type\":\"Native\",\"source\":{\"InvalidImportedBridgeExit\":{\"source\":\"InvalidExitNetwork\"}}}}}}}" + }, + { + "test_name": "UnknownError", + "certificate_header": "{\"network_id\":1,\"height\":7,\"epoch_number\":null,\"certificate_index\":null,\"certificate_id\":\"0xa80cd4abb016bbb3c3058f923a88be1ad49d68277366c55554d6f13d62428a1f\",\"new_local_exit_root\":\"0x566244fbf813b6926f6895142979f61ed6706184909cb8c819cd0216202a8aa9\",\"metadata\":\"0x000000000000000000000000000000000000000000000000000000000000001f\",\"status\":{\"InError\":{\"error\":{\"ProofGenerationError\":{\"generation_type\":\"Native\",\"source\":{\"InvalidImportedBridgeExit\":{\"source\":\"UnknownError\",\"global_index\":{\"mainnet_flag\":false,\"rollup_index\":1,\"leaf_index\":8}}}}}}}}" + } +] \ No newline at end of file diff --git a/agglayer/testdata/proof_generation_errors/invalid_signer_error.json b/agglayer/testdata/proof_generation_errors/invalid_signer_error.json new file mode 100644 index 00000000..62c5578c --- /dev/null +++ b/agglayer/testdata/proof_generation_errors/invalid_signer_error.json @@ -0,0 +1,21 @@ +[ + { + "test_name": "InvalidSignerError", + "certificate_header": "{\"network_id\":1,\"height\":0,\"epoch_number\":null,\"certificate_index\":null,\"certificate_id\":\"0xa80cd4abb016bbb3c3058f923a88be1ad49d68277366c55554d6f13d62428a1f\",\"new_local_exit_root\":\"0x566244fbf813b6926f6895142979f61ed6706184909cb8c819cd0216202a8aa9\",\"metadata\":\"0x000000000000000000000000000000000000000000000000000000000000001f\",\"status\":{\"InError\":{\"error\":{\"ProofGenerationError\":{\"generation_type\":\"Native\",\"source\":{\"InvalidSigner\":{\"declared\":\"0x5b06837a43bdc3dd9f114558daf4b26ed49842ed\",\"recovered\":\"0x20e92bfbb55589f7fd0bec3666e3de469526de3e\"}}}}}}}" + }, + { + "test_name": "InvalidSignerError_missing_declared", + "expected_error": "key declared not found in map", + "certificate_header": "{\"network_id\":1,\"height\":0,\"epoch_number\":null,\"certificate_index\":null,\"certificate_id\":\"0xa80cd4abb016bbb3c3058f923a88be1ad49d68277366c55554d6f13d62428a1f\",\"new_local_exit_root\":\"0x566244fbf813b6926f6895142979f61ed6706184909cb8c819cd0216202a8aa9\",\"metadata\":\"0x000000000000000000000000000000000000000000000000000000000000001f\",\"status\":{\"InError\":{\"error\":{\"ProofGenerationError\":{\"generation_type\":\"Native\",\"source\":{\"InvalidSigner\":{\"recovered\":\"0x20e92bfbb55589f7fd0bec3666e3de469526de3e\"}}}}}}}" + }, + { + "test_name": "InvalidSignerError_missing_recovered", + "expected_error": "key recovered not found in map", + "certificate_header": "{\"network_id\":1,\"height\":0,\"epoch_number\":null,\"certificate_index\":null,\"certificate_id\":\"0xa80cd4abb016bbb3c3058f923a88be1ad49d68277366c55554d6f13d62428a1f\",\"new_local_exit_root\":\"0x566244fbf813b6926f6895142979f61ed6706184909cb8c819cd0216202a8aa9\",\"metadata\":\"0x000000000000000000000000000000000000000000000000000000000000001f\",\"status\":{\"InError\":{\"error\":{\"ProofGenerationError\":{\"generation_type\":\"Native\",\"source\":{\"InvalidSigner\":{\"declared\":\"0x5b06837a43bdc3dd9f114558daf4b26ed49842ed\"}}}}}}}" + }, + { + "test_name": "InvalidSignerError_missing_inner_error", + "expected_error": "not a map", + "certificate_header": "{\"network_id\":1,\"height\":0,\"epoch_number\":null,\"certificate_index\":null,\"certificate_id\":\"0xa80cd4abb016bbb3c3058f923a88be1ad49d68277366c55554d6f13d62428a1f\",\"new_local_exit_root\":\"0x566244fbf813b6926f6895142979f61ed6706184909cb8c819cd0216202a8aa9\",\"metadata\":\"0x000000000000000000000000000000000000000000000000000000000000001f\",\"status\":{\"InError\":{\"error\":{\"ProofGenerationError\":{\"generation_type\":\"Native\",\"source\":\"InvalidSigner\"}}}}}" + } +] \ No newline at end of file diff --git a/agglayer/testdata/proof_generation_errors/random_unmarshal_errors.json b/agglayer/testdata/proof_generation_errors/random_unmarshal_errors.json new file mode 100644 index 00000000..680370e2 --- /dev/null +++ b/agglayer/testdata/proof_generation_errors/random_unmarshal_errors.json @@ -0,0 +1,12 @@ +[ + { + "test_name": "missing_proof_generation_type", + "certificate_header": "{\"network_id\":14,\"height\":1,\"epoch_number\":null,\"certificate_index\":null,\"certificate_id\":\"0xa80cd4abb016bbb3c3058f923a88be1ad49d68277366c55554d6f13d62428a1f\",\"new_local_exit_root\":\"0x566244fbf813b6926f6895142979f61ed6706184909cb8c819cd0216202a8aa9\",\"metadata\":\"0x000000000000000000000000000000000000000000000000000000000000001f\",\"status\":{\"InError\":{\"error\":{\"ProofGenerationError\":{\"source\":{\"InvalidImportedExitsRoot\":{\"declared\":\"0x1116837a43bdc3dd9f114558daf4b26ed4eeeeec\",\"computed\":\"0x20222bfbb55589f7fd0bec3666e3de469111ce3c\"}}}}}}}", + "expected_error": "key generation_type not found in map" + }, + { + "test_name": "missing_source", + "certificate_header": "{\"network_id\":14,\"height\":1,\"epoch_number\":null,\"certificate_index\":null,\"certificate_id\":\"0xa80cd4abb016bbb3c3058f923a88be1ad49d68277366c55554d6f13d62428a1f\",\"new_local_exit_root\":\"0x566244fbf813b6926f6895142979f61ed6706184909cb8c819cd0216202a8aa9\",\"metadata\":\"0x000000000000000000000000000000000000000000000000000000000000001f\",\"status\":{\"InError\":{\"error\":{\"ProofGenerationError\":{\"generation_type\":\"Native\",\"unknown\":{\"InvalidImportedExitsRoot\":{\"declared\":\"0x1116837a43bdc3dd9f114558daf4b26ed4eeeeec\",\"computed\":\"0x20222bfbb55589f7fd0bec3666e3de469111ce3c\"}}}}}}}", + "expected_error": "key source not found in map" + } +] \ No newline at end of file diff --git a/agglayer/testdata/proof_verification_errors/errors_with_inner_data.json b/agglayer/testdata/proof_verification_errors/errors_with_inner_data.json new file mode 100644 index 00000000..2060d2ee --- /dev/null +++ b/agglayer/testdata/proof_verification_errors/errors_with_inner_data.json @@ -0,0 +1,22 @@ +[ + { + "test_name": "VersionMismatch", + "certificate_header": "{\"network_id\":1,\"height\":4,\"epoch_number\":null,\"certificate_index\":null,\"certificate_id\":\"0xa80cd4abb016bbb3c3058f923a88be1ad49d68277366c55554d6f13d62428a1f\",\"new_local_exit_root\":\"0x566244fbf813b6926f6895142979f61ed6706184909cb8c819cd0216202a8aa9\",\"metadata\":\"0x000000000000000000000000000000000000000000000000000000000000001f\",\"status\":{\"InError\":{\"error\":{\"ProofVerificationError\":{\"VersionMismatch\":\"version1-1\"}}}}}" + }, + { + "test_name": "Core", + "certificate_header": "{\"network_id\":1,\"height\":4,\"epoch_number\":null,\"certificate_index\":null,\"certificate_id\":\"0xa80cd4abb016bbb3c3058f923a88be1ad49d68277366c55554d6f13d62428a1f\",\"new_local_exit_root\":\"0x566244fbf813b6926f6895142979f61ed6706184909cb8c819cd0216202a8aa9\",\"metadata\":\"0x000000000000000000000000000000000000000000000000000000000000001f\",\"status\":{\"InError\":{\"error\":{\"ProofVerificationError\":{\"Core\":\"coreexample\"}}}}}" + }, + { + "test_name": "Recursion", + "certificate_header": "{\"network_id\":1,\"height\":4,\"epoch_number\":null,\"certificate_index\":null,\"certificate_id\":\"0xa80cd4abb016bbb3c3058f923a88be1ad49d68277366c55554d6f13d62428a1f\",\"new_local_exit_root\":\"0x566244fbf813b6926f6895142979f61ed6706184909cb8c819cd0216202a8aa9\",\"metadata\":\"0x000000000000000000000000000000000000000000000000000000000000001f\",\"status\":{\"InError\":{\"error\":{\"ProofVerificationError\":{\"Recursion\":\"recursion error\"}}}}}" + }, + { + "test_name": "Plank", + "certificate_header": "{\"network_id\":1,\"height\":4,\"epoch_number\":null,\"certificate_index\":null,\"certificate_id\":\"0xa80cd4abb016bbb3c3058f923a88be1ad49d68277366c55554d6f13d62428a1f\",\"new_local_exit_root\":\"0x566244fbf813b6926f6895142979f61ed6706184909cb8c819cd0216202a8aa9\",\"metadata\":\"0x000000000000000000000000000000000000000000000000000000000000001f\",\"status\":{\"InError\":{\"error\":{\"ProofVerificationError\":{\"Plank\":\"plank error\"}}}}}" + }, + { + "test_name": "Groth16", + "certificate_header": "{\"network_id\":1,\"height\":4,\"epoch_number\":null,\"certificate_index\":null,\"certificate_id\":\"0xa80cd4abb016bbb3c3058f923a88be1ad49d68277366c55554d6f13d62428a1f\",\"new_local_exit_root\":\"0x566244fbf813b6926f6895142979f61ed6706184909cb8c819cd0216202a8aa9\",\"metadata\":\"0x000000000000000000000000000000000000000000000000000000000000001f\",\"status\":{\"InError\":{\"error\":{\"ProofVerificationError\":{\"Groth16\":\"Groth16 error\"}}}}}" + } +] \ No newline at end of file diff --git a/agglayer/testdata/proof_verification_errors/errors_without_inner_data.json b/agglayer/testdata/proof_verification_errors/errors_without_inner_data.json new file mode 100644 index 00000000..458b07c0 --- /dev/null +++ b/agglayer/testdata/proof_verification_errors/errors_without_inner_data.json @@ -0,0 +1,6 @@ +[ + { + "test_name": "InvalidPublicValues", + "certificate_header": "{\"network_id\":1,\"height\":4,\"epoch_number\":null,\"certificate_index\":null,\"certificate_id\":\"0xa80cd4abb016bbb3c3058f923a88be1ad49d68277366c55554d6f13d62428a1f\",\"new_local_exit_root\":\"0x566244fbf813b6926f6895142979f61ed6706184909cb8c819cd0216202a8aa9\",\"metadata\":\"0x000000000000000000000000000000000000000000000000000000000000001f\",\"status\":{\"InError\":{\"error\":{\"ProofVerificationError\":\"InvalidPublicValues\"}}}}" + } +] \ No newline at end of file diff --git a/agglayer/testdata/type_conversion_errors/errors_with_declared_computed_data.json b/agglayer/testdata/type_conversion_errors/errors_with_declared_computed_data.json new file mode 100644 index 00000000..348ffa5f --- /dev/null +++ b/agglayer/testdata/type_conversion_errors/errors_with_declared_computed_data.json @@ -0,0 +1,6 @@ +[ + { + "test_name": "MultipleL1InfoRoot", + "certificate_header": "{\"network_id\":1,\"height\":4,\"epoch_number\":null,\"certificate_index\":null,\"certificate_id\":\"0xa80cd4abb016bbb3c3058f923a88be1ad49d68277366c55554d6f13d62428a1f\",\"new_local_exit_root\":\"0x566244fbf813b6926f6895142979f61ed6706184909cb8c819cd0216202a8aa9\",\"metadata\":\"0x000000000000000000000000000000000000000000000000000000000000001f\",\"status\":{\"InError\":{\"error\":{\"TypeConversionError\":{\"MismatchNewLocalExitRoot\":{\"declared\":\"0xa80cd4abb016bbb3c3058f923a88be1ad49d68277366c55554d6f13d62428a1f\",\"computed\":\"0x5b06837a43bdc3dd9f114558daf4b26ed49842ee\"}}}}}}" + } +] \ No newline at end of file diff --git a/agglayer/testdata/type_conversion_errors/errors_with_token_info.json b/agglayer/testdata/type_conversion_errors/errors_with_token_info.json new file mode 100644 index 00000000..06d739a9 --- /dev/null +++ b/agglayer/testdata/type_conversion_errors/errors_with_token_info.json @@ -0,0 +1,26 @@ +[ + { + "test_name": "MultipleL1InfoRoot", + "certificate_header": "{\"network_id\":1,\"height\":0,\"epoch_number\":null,\"certificate_index\":null,\"certificate_id\":\"0xa80cd4abb016bbb3c3058f923a88be1ad49d68277366c55554d6f13d62428a1f\",\"new_local_exit_root\":\"0x566244fbf813b6926f6895142979f61ed6706184909cb8c819cd0216202a8aa9\",\"metadata\":\"0x000000000000000000000000000000000000000000000000000000000000001f\",\"status\":{\"InError\":{\"error\":{\"TypeConversionError\":{\"MultipleL1InfoRoot\":{\"origin_network\":1,\"origin_token_address\":\"0x566244fbf813b6926f6895142979f61ed6706184909cb8c819cd0216202a8aa2\"}}}}}}" + }, + { + "test_name": "MismatchNewLocalExitRoot", + "certificate_header": "{\"network_id\":1,\"height\":1,\"epoch_number\":null,\"certificate_index\":null,\"certificate_id\":\"0xa80cd4abb016bbb3c3058f923a88be1ad49d68277366c55554d6f13d62428a1f\",\"new_local_exit_root\":\"0x566244fbf813b6926f6895142979f61ed6706184909cb8c819cd0216202a8aa9\",\"metadata\":\"0x000000000000000000000000000000000000000000000000000000000000001f\",\"status\":{\"InError\":{\"error\":{\"TypeConversionError\":{\"MismatchNewLocalExitRoot\":{\"origin_network\":1,\"origin_token_address\":\"0x566244fbf813b6926f6895142979f61ed6706184909cb8c819cd0216202a8aa2\"}}}}}}" + }, + { + "test_name": "BalanceOverflow", + "certificate_header": "{\"network_id\":1,\"height\":2,\"epoch_number\":null,\"certificate_index\":null,\"certificate_id\":\"0xa80cd4abb016bbb3c3058f923a88be1ad49d68277366c55554d6f13d62428a1f\",\"new_local_exit_root\":\"0x566244fbf813b6926f6895142979f61ed6706184909cb8c819cd0216202a8aa9\",\"metadata\":\"0x000000000000000000000000000000000000000000000000000000000000001f\",\"status\":{\"InError\":{\"error\":{\"TypeConversionError\":{\"BalanceOverflow\":{\"origin_network\":1,\"origin_token_address\":\"0x566244fbf813b6926f6895142979f61ed6706184909cb8c819cd0216202a8aa2\"}}}}}}" + }, + { + "test_name": "BalanceUnderflow", + "certificate_header": "{\"network_id\":1,\"height\":3,\"epoch_number\":null,\"certificate_index\":null,\"certificate_id\":\"0xa80cd4abb016bbb3c3058f923a88be1ad49d68277366c55554d6f13d62428a1f\",\"new_local_exit_root\":\"0x566244fbf813b6926f6895142979f61ed6706184909cb8c819cd0216202a8aa9\",\"metadata\":\"0x000000000000000000000000000000000000000000000000000000000000001f\",\"status\":{\"InError\":{\"error\":{\"TypeConversionError\":{\"BalanceUnderflow\":{\"origin_network\":1,\"origin_token_address\":\"0x566244fbf813b6926f6895142979f61ed6706184909cb8c819cd0216202a8aa2\"}}}}}}" + }, + { + "test_name": "BalanceProofGenerationFailed - KeyAlreadyPresent", + "certificate_header": "{\"network_id\":1,\"height\":4,\"epoch_number\":null,\"certificate_index\":null,\"certificate_id\":\"0xa80cd4abb016bbb3c3058f923a88be1ad49d68277366c55554d6f13d62428a1f\",\"new_local_exit_root\":\"0x566244fbf813b6926f6895142979f61ed6706184909cb8c819cd0216202a8aa9\",\"metadata\":\"0x000000000000000000000000000000000000000000000000000000000000001f\",\"status\":{\"InError\":{\"error\":{\"TypeConversionError\":{\"BalanceProofGenerationFailed\":{\"source\":\"KeyAlreadyPresent\",\"token\":{\"origin_network\":1,\"origin_token_address\":\"0x566244fbf813b6926f6895142979f61ed6706184909cb8c819cd0216202a8aa2\"}}}}}}}" + }, + { + "test_name": "BalanceProofGenerationFailed - KeyNotPresent", + "certificate_header": "{\"network_id\":1,\"height\":5,\"epoch_number\":null,\"certificate_index\":null,\"certificate_id\":\"0xa80cd4abb016bbb3c3058f923a88be1ad49d68277366c55554d6f13d62428a1f\",\"new_local_exit_root\":\"0x566244fbf813b6926f6895142979f61ed6706184909cb8c819cd0216202a8aa9\",\"metadata\":\"0x000000000000000000000000000000000000000000000000000000000000001f\",\"status\":{\"InError\":{\"error\":{\"TypeConversionError\":{\"BalanceProofGenerationFailed\":{\"source\":\"KeyNotPresent\",\"token\":{\"origin_network\":11,\"origin_token_address\":\"0x566244fbf813b6926f6895142979f61ed6706184909cb8c819cd0216202a8aa2\"}}}}}}}" + } +] \ No newline at end of file diff --git a/agglayer/testdata/type_conversion_errors/errors_without_inner_data.json b/agglayer/testdata/type_conversion_errors/errors_without_inner_data.json new file mode 100644 index 00000000..a92aca80 --- /dev/null +++ b/agglayer/testdata/type_conversion_errors/errors_without_inner_data.json @@ -0,0 +1,6 @@ +[ + { + "test_name": "MultipleL1InfoRoot", + "certificate_header": "{\"network_id\":1,\"height\":0,\"epoch_number\":null,\"certificate_index\":null,\"certificate_id\":\"0xa80cd4abb016bbb3c3058f923a88be1ad49d68277366c55554d6f13d62428a1f\",\"new_local_exit_root\":\"0x566244fbf813b6926f6895142979f61ed6706184909cb8c819cd0216202a8aa9\",\"metadata\":\"0x000000000000000000000000000000000000000000000000000000000000001f\",\"status\":{\"InError\":{\"error\":{\"TypeConversionError\":\"MultipleL1InfoRoot\"}}}}" + } +] \ No newline at end of file diff --git a/agglayer/testdata/type_conversion_errors/nullifier_path_generation_failed_error.json b/agglayer/testdata/type_conversion_errors/nullifier_path_generation_failed_error.json new file mode 100644 index 00000000..b52cd73f --- /dev/null +++ b/agglayer/testdata/type_conversion_errors/nullifier_path_generation_failed_error.json @@ -0,0 +1,20 @@ +[ + { + "test_name": "NullifierPathGenerationFailed - KeyPresent", + "certificate_header": "{\"network_id\":1,\"height\":6,\"epoch_number\":null,\"certificate_index\":null,\"certificate_id\":\"0xa80cd4abb016bbb3c3058f923a88be1ad49d68277366c55554d6f13d62428a1f\",\"new_local_exit_root\":\"0x566244fbf813b6926f6895142979f61ed6706184909cb8c819cd0216202a8aa9\",\"metadata\":\"0x000000000000000000000000000000000000000000000000000000000000001f\",\"status\":{\"InError\":{\"error\":{\"TypeConversionError\":{\"NullifierPathGenerationFailed\":{\"source\":\"KeyPresent\",\"global_index\":{\"mainnet_flag\":true,\"rollup_index\":0,\"leaf_index\":1}}}}}}}" + }, + { + "test_name": "NullifierPathGenerationFailed - DepthOutOfBounds", + "certificate_header": "{\"network_id\":1,\"height\":7,\"epoch_number\":null,\"certificate_index\":null,\"certificate_id\":\"0xa80cd4abb016bbb3c3058f923a88be1ad49d68277366c55554d6f13d62428a1f\",\"new_local_exit_root\":\"0x566244fbf813b6926f6895142979f61ed6706184909cb8c819cd0216202a8aa9\",\"metadata\":\"0x000000000000000000000000000000000000000000000000000000000000001f\",\"status\":{\"InError\":{\"error\":{\"TypeConversionError\":{\"NullifierPathGenerationFailed\":{\"source\":\"DepthOutOfBounds\",\"global_index\":{\"mainnet_flag\":false,\"rollup_index\":11,\"leaf_index\":123}}}}}}}" + }, + { + "test_name": "NullifierPathGenerationFailed_unknown_SMT_error_code", + "expected_error": "unknown SMT error code", + "certificate_header": "{\"network_id\":1,\"height\":7,\"epoch_number\":null,\"certificate_index\":null,\"certificate_id\":\"0xa80cd4abb016bbb3c3058f923a88be1ad49d68277366c55554d6f13d62428a1f\",\"new_local_exit_root\":\"0x566244fbf813b6926f6895142979f61ed6706184909cb8c819cd0216202a8aa9\",\"metadata\":\"0x000000000000000000000000000000000000000000000000000000000000001f\",\"status\":{\"InError\":{\"error\":{\"TypeConversionError\":{\"NullifierPathGenerationFailed\":{\"source\":\"UnknownCode\",\"global_index\":{\"mainnet_flag\":false,\"rollup_index\":11,\"leaf_index\":123}}}}}}}" + }, + { + "test_name": "NullifierPathGenerationFailed_missing_SMT_source", + "expected_error": "error code is not a string", + "certificate_header": "{\"network_id\":1,\"height\":7,\"epoch_number\":null,\"certificate_index\":null,\"certificate_id\":\"0xa80cd4abb016bbb3c3058f923a88be1ad49d68277366c55554d6f13d62428a1f\",\"new_local_exit_root\":\"0x566244fbf813b6926f6895142979f61ed6706184909cb8c819cd0216202a8aa9\",\"metadata\":\"0x000000000000000000000000000000000000000000000000000000000000001f\",\"status\":{\"InError\":{\"error\":{\"TypeConversionError\":{\"NullifierPathGenerationFailed\":{\"unknown\":\"DepthOutOfBounds\",\"global_index\":{\"mainnet_flag\":false,\"rollup_index\":11,\"leaf_index\":123}}}}}}}" + } +] \ No newline at end of file diff --git a/agglayer/type_conversion_error.go b/agglayer/type_conversion_error.go new file mode 100644 index 00000000..89129253 --- /dev/null +++ b/agglayer/type_conversion_error.go @@ -0,0 +1,255 @@ +package agglayer + +import ( + "errors" + "fmt" +) + +const ( + MultipleL1InfoRootErrorType = "MultipleL1InfoRoot" + MismatchNewLocalExitRootErrorType = "MismatchNewLocalExitRoot" + BalanceOverflowErrorType = "BalanceOverflow" + BalanceUnderflowErrorType = "BalanceUnderflow" + BalanceProofGenerationFailedErrorType = "BalanceProofGenerationFailed" + NullifierPathGenerationFailedErrorType = "NullifierPathGenerationFailed" +) + +// TypeConversionError is an error that is returned when verifying a certficate +// before generating its proof. +type TypeConversionError struct { + InnerErrors []PPError +} + +// String is the implementation of the Error interface +func (p *TypeConversionError) String() string { + return fmt.Sprintf("Type conversion error: %v", p.InnerErrors) +} + +// Unmarshal unmarshals the data from a map into a ProofGenerationError struct. +func (p *TypeConversionError) Unmarshal(data interface{}) error { + getPPErrFn := func(key string, value interface{}) (PPError, error) { + switch key { + case MultipleL1InfoRootErrorType: + p.InnerErrors = append(p.InnerErrors, &MultipleL1InfoRoot{}) + case MismatchNewLocalExitRootErrorType: + p.InnerErrors = append(p.InnerErrors, NewMismatchNewLocalExitRoot()) + case BalanceOverflowErrorType: + balanceOverflow := NewBalanceOverflow() + if err := balanceOverflow.UnmarshalFromMap(value); err != nil { + return nil, err + } + return balanceOverflow, nil + case BalanceUnderflowErrorType: + balanceUnderflow := NewBalanceUnderflow() + if err := balanceUnderflow.UnmarshalFromMap(value); err != nil { + return nil, err + } + return balanceUnderflow, nil + case BalanceProofGenerationFailedErrorType: + balanceProofGenerationFailed := NewBalanceProofGenerationFailed() + if err := balanceProofGenerationFailed.UnmarshalFromMap(value); err != nil { + return nil, err + } + return balanceProofGenerationFailed, nil + case NullifierPathGenerationFailedErrorType: + nullifierPathGenerationFailed := NewNullifierPathGenerationFailed() + if err := nullifierPathGenerationFailed.UnmarshalFromMap(value); err != nil { + return nil, err + } + return nullifierPathGenerationFailed, nil + default: + return nil, fmt.Errorf("unknown type conversion error type: %v", key) + } + + return nil, nil + } + + getAndAddInnerErrorFn := func(key string, value interface{}) error { + ppErr, err := getPPErrFn(key, value) + if err != nil { + return err + } + + if ppErr != nil { + p.InnerErrors = append(p.InnerErrors, ppErr) + } + + return nil + } + + errorSourceMap, ok := data.(map[string]interface{}) + if !ok { + // it can be a single error + return getAndAddInnerErrorFn(data.(string), nil) //nolint:forcetypeassert + } + + for key, value := range errorSourceMap { + if err := getAndAddInnerErrorFn(key, value); err != nil { + return err + } + } + + return nil +} + +// MultipleL1InfoRoot is an error that is returned when the imported bridge exits +// refer to different L1 info roots. +type MultipleL1InfoRoot struct{} + +// String is the implementation of the Error interface +func (e *MultipleL1InfoRoot) String() string { + return fmt.Sprintf(`%s: The imported bridge exits should refer to one and the same L1 info root.`, + MultipleL1InfoRootErrorType) +} + +// MissingNewLocalExitRoot is an error that is returned when the certificate refers to +// a new local exit root which differ from the one computed by the agglayer. +type MismatchNewLocalExitRoot struct { + *DeclaredComputedError +} + +func NewMismatchNewLocalExitRoot() *MismatchNewLocalExitRoot { + return &MismatchNewLocalExitRoot{ + DeclaredComputedError: &DeclaredComputedError{ErrType: MismatchNewLocalExitRootErrorType}, + } +} + +// BalanceOverflow is an error that is returned when the given token balance cannot overflow. +type BalanceOverflow struct { + *TokenInfoError +} + +// NewBalanceOverflow returns a new BalanceOverflow error. +func NewBalanceOverflow() *BalanceOverflow { + return &BalanceOverflow{ + TokenInfoError: &TokenInfoError{}, + } +} + +// String is the implementation of the Error interface +func (e *BalanceOverflow) String() string { + return fmt.Sprintf("%s: The given token balance cannot overflow. %s", + BalanceOverflowErrorType, e.TokenInfo.String()) +} + +// BalanceUnderflow is an error that is returned when the given token balance cannot be negative. +type BalanceUnderflow struct { + *TokenInfoError +} + +// NewBalanceOverflow returns a new BalanceOverflow error. +func NewBalanceUnderflow() *BalanceUnderflow { + return &BalanceUnderflow{ + TokenInfoError: &TokenInfoError{}, + } +} + +// String is the implementation of the Error interface +func (e *BalanceUnderflow) String() string { + return fmt.Sprintf("%s: The given token balance cannot be negative. %s", + BalanceUnderflowErrorType, e.TokenInfo.String()) +} + +// SmtError is a type that is inherited by all errors that occur during SMT operations. +type SmtError struct { + ErrorCode string + Error string +} + +func (e *SmtError) Unmarshal(data interface{}) error { + errCode, ok := data.(string) + if !ok { + return errors.New("error code is not a string") + } + + e.ErrorCode = errCode + + switch errCode { + case "KeyAlreadyPresent": + e.Error = "trying to insert a key already in the SMT" + case "KeyNotPresent": + e.Error = "trying to generate a Merkle proof for a key not in the SMT" + case "KeyPresent": + e.Error = "trying to generate a non-inclusion proof for a key present in the SMT" + case "DepthOutOfBounds": + e.Error = "depth out of bounds" + default: + return fmt.Errorf("unknown SMT error code: %s", errCode) + } + + return nil +} + +// BalanceProofGenerationFailed is a struct that represents an error that occurs when +// the balance proof for the given token cannot be generated. +type BalanceProofGenerationFailed struct { + *TokenInfoError + *SmtError +} + +func NewBalanceProofGenerationFailed() *BalanceProofGenerationFailed { + return &BalanceProofGenerationFailed{ + TokenInfoError: &TokenInfoError{}, + SmtError: &SmtError{}, + } +} + +// String is the implementation of the Error interface +func (e *BalanceProofGenerationFailed) String() string { + return fmt.Sprintf("%s: The balance proof for the given token cannot be generated. TokenInfo: %s. Error type: %s. %s", + BalanceProofGenerationFailedErrorType, e.TokenInfo.String(), + e.SmtError.ErrorCode, e.SmtError.Error) +} + +func (e *BalanceProofGenerationFailed) UnmarshalFromMap(data interface{}) error { + dataMap, ok := data.(map[string]interface{}) + if !ok { + return errNotMap + } + + if err := e.TokenInfoError.UnmarshalFromMap(dataMap["token"]); err != nil { + return err + } + + return e.SmtError.Unmarshal(dataMap["source"]) +} + +// NullifierPathGenerationFailed is a struct that represents an error that occurs when +// the nullifier path for the given imported bridge exit cannot be generated.. +type NullifierPathGenerationFailed struct { + GlobalIndex *GlobalIndex `json:"global_index"` + *SmtError +} + +func NewNullifierPathGenerationFailed() *NullifierPathGenerationFailed { + return &NullifierPathGenerationFailed{ + SmtError: &SmtError{}, + } +} + +// String is the implementation of the Error interface +func (e *NullifierPathGenerationFailed) String() string { + return fmt.Sprintf("%s: The nullifier path for the given imported bridge exit cannot be generated. "+ + "GlobalIndex: %s. Error type: %s. %s", + NullifierPathGenerationFailedErrorType, e.GlobalIndex.String(), + e.SmtError.ErrorCode, e.SmtError.Error) +} + +func (e *NullifierPathGenerationFailed) UnmarshalFromMap(data interface{}) error { + dataMap, ok := data.(map[string]interface{}) + if !ok { + return errNotMap + } + + if err := e.SmtError.Unmarshal(dataMap["source"]); err != nil { + return err + } + + globalIndexMap, err := convertMapValue[map[string]interface{}](dataMap, "global_index") + if err != nil { + return err + } + + e.GlobalIndex = &GlobalIndex{} + return e.GlobalIndex.UnmarshalFromMap(globalIndexMap) +} diff --git a/agglayer/types.go b/agglayer/types.go index 9350e791..55974763 100644 --- a/agglayer/types.go +++ b/agglayer/types.go @@ -2,6 +2,7 @@ package agglayer import ( "encoding/json" + "errors" "fmt" "math/big" "strings" @@ -36,10 +37,7 @@ func (c *CertificateStatus) UnmarshalJSON(data []byte) error { if strings.Contains(dataStr, "InError") { status = "InError" } else { - err := json.Unmarshal(data, &status) - if err != nil { - return err - } + status = string(data) } switch status { @@ -199,6 +197,7 @@ type TokenInfo struct { OriginTokenAddress common.Address `json:"origin_token_address"` } +// String returns a string representation of the TokenInfo struct func (t *TokenInfo) String() string { return fmt.Sprintf("OriginNetwork: %d, OriginTokenAddress: %s", t.OriginNetwork, t.OriginTokenAddress.String()) } @@ -210,6 +209,11 @@ type GlobalIndex struct { LeafIndex uint32 `json:"leaf_index"` } +// String returns a string representation of the GlobalIndex struct +func (g *GlobalIndex) String() string { + return fmt.Sprintf("MainnetFlag: %t, RollupIndex: %d, LeafIndex: %d", g.MainnetFlag, g.RollupIndex, g.LeafIndex) +} + func (g *GlobalIndex) Hash() common.Hash { return crypto.Keccak256Hash( cdkcommon.BigIntToLittleEndianBytes( @@ -218,9 +222,27 @@ func (g *GlobalIndex) Hash() common.Hash { ) } -func (g *GlobalIndex) String() string { - return fmt.Sprintf("MainnetFlag: %t, RollupIndex: %d, LeafIndex: %d", - g.MainnetFlag, g.RollupIndex, g.LeafIndex) +func (g *GlobalIndex) UnmarshalFromMap(data map[string]interface{}) error { + rollupIndex, err := convertMapValue[uint32](data, "rollup_index") + if err != nil { + return err + } + + leafIndex, err := convertMapValue[uint32](data, "leaf_index") + if err != nil { + return err + } + + mainnetFlag, err := convertMapValue[bool](data, "mainnet_flag") + if err != nil { + return err + } + + g.RollupIndex = rollupIndex + g.LeafIndex = leafIndex + g.MainnetFlag = mainnetFlag + + return nil } // BridgeExit represents a token bridge exit @@ -525,9 +547,85 @@ type CertificateHeader struct { NewLocalExitRoot common.Hash `json:"new_local_exit_root"` Status CertificateStatus `json:"status"` Metadata common.Hash `json:"metadata"` + Error PPError `json:"-"` } func (c CertificateHeader) String() string { - return fmt.Sprintf("Height: %d, CertificateID: %s, NewLocalExitRoot: %s", - c.Height, c.CertificateID.String(), c.NewLocalExitRoot.String()) + errors := "" + if c.Error != nil { + errors = c.Error.String() + } + + return fmt.Sprintf("Height: %d, CertificateID: %s, NewLocalExitRoot: %s. Status: %s. Errors: %s", + c.Height, c.CertificateID.String(), c.NewLocalExitRoot.String(), c.Status.String(), errors) +} + +func (c *CertificateHeader) UnmarshalJSON(data []byte) error { + // we define an alias to avoid infinite recursion + type Alias CertificateHeader + aux := &struct { + Status interface{} `json:"status"` + *Alias + }{ + Alias: (*Alias)(c), + } + + if err := json.Unmarshal(data, &aux); err != nil { + return err + } + + // Process Status field + switch status := aux.Status.(type) { + case string: // certificate not InError + if err := c.Status.UnmarshalJSON([]byte(status)); err != nil { + return err + } + case map[string]interface{}: // certificate has errors + inErrMap, err := convertMapValue[map[string]interface{}](status, "InError") + if err != nil { + return err + } + + inErrDataMap, err := convertMapValue[map[string]interface{}](inErrMap, "error") + if err != nil { + return err + } + + var ppError PPError + + for key, value := range inErrDataMap { + switch key { + case "ProofGenerationError": + p := &ProofGenerationError{} + if err := p.Unmarshal(value); err != nil { + return err + } + + ppError = p + case "TypeConversionError": + t := &TypeConversionError{} + if err := t.Unmarshal(value); err != nil { + return err + } + + ppError = t + case "ProofVerificationError": + p := &ProofVerificationError{} + if err := p.Unmarshal(value); err != nil { + return err + } + + ppError = p + default: + return fmt.Errorf("invalid error type: %s", key) + } + } + + c.Status = InError + c.Error = ppError + default: + return errors.New("invalid status type") + } + + return nil } diff --git a/agglayer/types_test.go b/agglayer/types_test.go index 95033141..f2133923 100644 --- a/agglayer/types_test.go +++ b/agglayer/types_test.go @@ -152,3 +152,102 @@ func TestSignedCertificate_Copy(t *testing.T) { require.Empty(t, certificateCopy.ImportedBridgeExits) }) } + +func TestGlobalIndex_UnmarshalFromMap(t *testing.T) { + t.Parallel() + + tests := []struct { + name string + data map[string]interface{} + want *GlobalIndex + wantErr bool + }{ + { + name: "valid data", + data: map[string]interface{}{ + "rollup_index": uint32(0), + "leaf_index": uint32(2), + "mainnet_flag": true, + }, + want: &GlobalIndex{ + RollupIndex: 0, + LeafIndex: 2, + MainnetFlag: true, + }, + wantErr: false, + }, + { + name: "missing rollup_index", + data: map[string]interface{}{ + "leaf_index": uint32(2), + "mainnet_flag": true, + }, + want: &GlobalIndex{}, + wantErr: true, + }, + { + name: "invalid rollup_index type", + data: map[string]interface{}{ + "rollup_index": "invalid", + "leaf_index": uint32(2), + "mainnet_flag": true, + }, + want: &GlobalIndex{}, + wantErr: true, + }, + { + name: "missing leaf_index", + data: map[string]interface{}{ + "rollup_index": uint32(1), + "mainnet_flag": true, + }, + want: &GlobalIndex{}, + wantErr: true, + }, + { + name: "invalid leaf_index type", + data: map[string]interface{}{ + "rollup_index": uint32(1), + "leaf_index": "invalid", + "mainnet_flag": true, + }, + want: &GlobalIndex{}, + wantErr: true, + }, + { + name: "missing mainnet_flag", + data: map[string]interface{}{ + "rollup_index": uint32(1), + "leaf_index": uint32(2), + }, + want: &GlobalIndex{}, + wantErr: true, + }, + { + name: "invalid mainnet_flag type", + data: map[string]interface{}{ + "rollup_index": uint32(1), + "leaf_index": uint32(2), + "mainnet_flag": "invalid", + }, + want: &GlobalIndex{}, + wantErr: true, + }, + } + + for _, tt := range tests { + tt := tt + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + + g := &GlobalIndex{} + err := g.UnmarshalFromMap(tt.data) + if tt.wantErr { + require.Error(t, err) + } else { + require.NoError(t, err) + require.Equal(t, tt.want, g) + } + }) + } +} diff --git a/sonar-project.properties b/sonar-project.properties index a6245819..3b6ddc8a 100644 --- a/sonar-project.properties +++ b/sonar-project.properties @@ -7,11 +7,11 @@ sonar.projectName=cdk sonar.organization=0xpolygon sonar.sources=. -sonar.exclusions=**/test/**,**/vendor/**,**/mocks/**,**/build/**,**/target/**,**/proto/include/**,**/*.pb.go,**/docs/**,**/*.sql,**/mocks_*/*,scripts/**,**/mock_*.go,**/agglayer/**,**/cmd/** +sonar.exclusions=**/test/**,**/vendor/**,**/mocks/**,**/build/**,**/target/**,**/proto/include/**,**/*.pb.go,**/docs/**,**/*.sql,**/mocks_*/*,scripts/**,**/mock_*.go,**/cmd/** sonar.tests=. sonar.test.inclusions=**/*_test.go -sonar.test.exclusions=test/contracts/**,**/vendor/**,**/docs/**,**/mocks/**,**/*.pb.go,**/*.yml,**/*.yaml,**/*.json,**/*.xml,**/*.toml,**/mocks_*/*,**/mock_*.go,**/agglayer/**,**/cmd/** +sonar.test.exclusions=test/contracts/**,**/vendor/**,**/docs/**,**/mocks/**,**/*.pb.go,**/*.yml,**/*.yaml,**/*.json,**/*.xml,**/*.toml,**/mocks_*/*,**/mock_*.go,**/cmd/** sonar.issue.enforceSemantic=true # =====================================================