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 01/23] 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 # ===================================================== From dbc47e11059a62aca66b39c43148946d985475f7 Mon Sep 17 00:00:00 2001 From: Joan Esteban <129153821+joanestebanr@users.noreply.github.com> Date: Tue, 12 Nov 2024 12:20:50 +0100 Subject: [PATCH 02/23] feat: epoch notifier (#144) - Send certificates after a percentage of epoch - Require epoch configuration to AggLayer - Change config of `aggsender` adding: `BlockFinality` and `EpochNotificationPercentage` --- agglayer/client.go | 30 +- agglayer/client_test.go | 76 ++++ agglayer/mock_agglayer_client.go | 32 +- agglayer/types.go | 11 + aggsender/aggsender.go | 79 ++-- aggsender/aggsender_test.go | 84 ++-- aggsender/block_notifier_polling.go | 219 +++++++++ aggsender/block_notifier_polling_test.go | 211 +++++++++ aggsender/config.go | 11 + aggsender/epoch_notifier_per_block.go | 204 +++++++++ aggsender/epoch_notifier_per_block_test.go | 219 +++++++++ aggsender/generic_subscriber_impl.go | 33 ++ aggsender/mocks/agg_sender_storage.go | 351 +++++++++++++++ aggsender/mocks/block_notifier.go | 128 ++++++ aggsender/mocks/epoch_notifier.go | 163 +++++++ .../{mock_eth_client.go => eth_client.go} | 50 +-- aggsender/mocks/generic_subscriber.go | 113 +++++ aggsender/mocks/l1_info_tree_syncer.go | 217 +++++++++ aggsender/mocks/l2_bridge_syncer.go | 423 ++++++++++++++++++ aggsender/mocks/logger.go | 376 ++++++++++++++++ aggsender/mocks/mock_aggsender_storage.go | 351 --------------- aggsender/mocks/mock_l1infotree_syncer.go | 217 --------- aggsender/mocks/mock_l2bridge_syncer.go | 423 ------------------ aggsender/mocks/mock_logger.go | 290 ------------ aggsender/types/block_notifier.go | 15 + aggsender/types/epoch_notifier.go | 25 ++ aggsender/types/generic_subscriber.go | 6 + aggsender/types/types.go | 2 + cmd/run.go | 29 +- config/default.go | 5 +- go.mod | 4 +- go.sum | 7 + scripts/local_config | 2 +- test/Makefile | 9 +- test/bridge-e2e.bats | 22 +- .../kurtosis-cdk-node-config.toml.template | 2 - test/helpers/lxly-bridge-test.bash | 1 + 37 files changed, 3041 insertions(+), 1399 deletions(-) create mode 100644 agglayer/client_test.go create mode 100644 aggsender/block_notifier_polling.go create mode 100644 aggsender/block_notifier_polling_test.go create mode 100644 aggsender/epoch_notifier_per_block.go create mode 100644 aggsender/epoch_notifier_per_block_test.go create mode 100644 aggsender/generic_subscriber_impl.go create mode 100644 aggsender/mocks/agg_sender_storage.go create mode 100644 aggsender/mocks/block_notifier.go create mode 100644 aggsender/mocks/epoch_notifier.go rename aggsender/mocks/{mock_eth_client.go => eth_client.go} (50%) create mode 100644 aggsender/mocks/generic_subscriber.go create mode 100644 aggsender/mocks/l1_info_tree_syncer.go create mode 100644 aggsender/mocks/l2_bridge_syncer.go create mode 100644 aggsender/mocks/logger.go delete mode 100644 aggsender/mocks/mock_aggsender_storage.go delete mode 100644 aggsender/mocks/mock_l1infotree_syncer.go delete mode 100644 aggsender/mocks/mock_l2bridge_syncer.go delete mode 100644 aggsender/mocks/mock_logger.go create mode 100644 aggsender/types/block_notifier.go create mode 100644 aggsender/types/epoch_notifier.go create mode 100644 aggsender/types/generic_subscriber.go diff --git a/agglayer/client.go b/agglayer/client.go index e60c1c7c..8396fc9e 100644 --- a/agglayer/client.go +++ b/agglayer/client.go @@ -15,7 +15,14 @@ import ( const errCodeAgglayerRateLimitExceeded int = -10007 -var ErrAgglayerRateLimitExceeded = fmt.Errorf("agglayer rate limit exceeded") +var ( + ErrAgglayerRateLimitExceeded = fmt.Errorf("agglayer rate limit exceeded") + jSONRPCCall = rpc.JSONRPCCall +) + +type AggLayerClientGetEpochConfiguration interface { + GetEpochConfiguration() (*ClockConfiguration, error) +} // AgglayerClientInterface is the interface that defines the methods that the AggLayerClient will implement type AgglayerClientInterface interface { @@ -23,6 +30,7 @@ type AgglayerClientInterface interface { WaitTxToBeMined(hash common.Hash, ctx context.Context) error SendCertificate(certificate *SignedCertificate) (common.Hash, error) GetCertificateHeader(certificateHash common.Hash) (*CertificateHeader, error) + AggLayerClientGetEpochConfiguration } // AggLayerClient is the client that will be used to interact with the AggLayer @@ -130,3 +138,23 @@ func (c *AggLayerClient) GetCertificateHeader(certificateHash common.Hash) (*Cer return result, nil } + +// GetEpochConfiguration returns the clock configuration of AggLayer +func (c *AggLayerClient) GetEpochConfiguration() (*ClockConfiguration, error) { + response, err := jSONRPCCall(c.url, "interop_getEpochConfiguration") + if err != nil { + return nil, err + } + + if response.Error != nil { + return nil, fmt.Errorf("GetEpochConfiguration code=%d msg=%s", response.Error.Code, response.Error.Message) + } + + var result *ClockConfiguration + err = json.Unmarshal(response.Result, &result) + if err != nil { + return nil, err + } + + return result, nil +} diff --git a/agglayer/client_test.go b/agglayer/client_test.go new file mode 100644 index 00000000..82baea85 --- /dev/null +++ b/agglayer/client_test.go @@ -0,0 +1,76 @@ +package agglayer + +import ( + "fmt" + "testing" + + "github.com/0xPolygon/cdk-rpc/rpc" + "github.com/stretchr/testify/require" +) + +const ( + testURL = "http://localhost:8080" +) + +func TestExploratoryClient(t *testing.T) { + t.Skip("This test is for exploratory purposes only") + sut := NewAggLayerClient("http://127.0.0.1:32853") + config, err := sut.GetEpochConfiguration() + require.NoError(t, err) + require.NotNil(t, config) + fmt.Printf("Config: %s", config.String()) +} + +func TestGetEpochConfigurationResponseWithError(t *testing.T) { + sut := NewAggLayerClient(testURL) + response := rpc.Response{ + Error: &rpc.ErrorObject{}, + } + jSONRPCCall = func(url, method string, params ...interface{}) (rpc.Response, error) { + return response, nil + } + clockConfig, err := sut.GetEpochConfiguration() + require.Nil(t, clockConfig) + require.Error(t, err) +} + +func TestGetEpochConfigurationResponseBadJson(t *testing.T) { + sut := NewAggLayerClient(testURL) + response := rpc.Response{ + Result: []byte(`{`), + } + jSONRPCCall = func(url, method string, params ...interface{}) (rpc.Response, error) { + return response, nil + } + clockConfig, err := sut.GetEpochConfiguration() + require.Nil(t, clockConfig) + require.Error(t, err) +} + +func TestGetEpochConfigurationErrorResponse(t *testing.T) { + sut := NewAggLayerClient(testURL) + + jSONRPCCall = func(url, method string, params ...interface{}) (rpc.Response, error) { + return rpc.Response{}, fmt.Errorf("unittest error") + } + clockConfig, err := sut.GetEpochConfiguration() + require.Nil(t, clockConfig) + require.Error(t, err) +} + +func TestGetEpochConfigurationOkResponse(t *testing.T) { + sut := NewAggLayerClient(testURL) + response := rpc.Response{ + Result: []byte(`{"epoch_duration": 1, "genesis_block": 1}`), + } + jSONRPCCall = func(url, method string, params ...interface{}) (rpc.Response, error) { + return response, nil + } + clockConfig, err := sut.GetEpochConfiguration() + require.NotNil(t, clockConfig) + require.NoError(t, err) + require.Equal(t, ClockConfiguration{ + EpochDuration: 1, + GenesisBlock: 1, + }, *clockConfig) +} diff --git a/agglayer/mock_agglayer_client.go b/agglayer/mock_agglayer_client.go index 43100a2e..1b756713 100644 --- a/agglayer/mock_agglayer_client.go +++ b/agglayer/mock_agglayer_client.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.45.0. DO NOT EDIT. +// Code generated by mockery v2.39.0. DO NOT EDIT. package agglayer @@ -45,6 +45,36 @@ func (_m *AgglayerClientMock) GetCertificateHeader(certificateHash common.Hash) return r0, r1 } +// GetEpochConfiguration provides a mock function with given fields: +func (_m *AgglayerClientMock) GetEpochConfiguration() (*ClockConfiguration, error) { + ret := _m.Called() + + if len(ret) == 0 { + panic("no return value specified for GetEpochConfiguration") + } + + var r0 *ClockConfiguration + var r1 error + if rf, ok := ret.Get(0).(func() (*ClockConfiguration, error)); ok { + return rf() + } + if rf, ok := ret.Get(0).(func() *ClockConfiguration); ok { + r0 = rf() + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*ClockConfiguration) + } + } + + if rf, ok := ret.Get(1).(func() error); ok { + r1 = rf() + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + // SendCertificate provides a mock function with given fields: certificate func (_m *AgglayerClientMock) SendCertificate(certificate *SignedCertificate) (common.Hash, error) { ret := _m.Called(certificate) diff --git a/agglayer/types.go b/agglayer/types.go index 55974763..b6a3198e 100644 --- a/agglayer/types.go +++ b/agglayer/types.go @@ -629,3 +629,14 @@ func (c *CertificateHeader) UnmarshalJSON(data []byte) error { return nil } + +// ClockConfiguration represents the configuration of the epoch clock +// returned by the interop_GetEpochConfiguration RPC call +type ClockConfiguration struct { + EpochDuration uint64 `json:"epoch_duration"` + GenesisBlock uint64 `json:"genesis_block"` +} + +func (c ClockConfiguration) String() string { + return fmt.Sprintf("EpochDuration: %d, GenesisBlock: %d", c.EpochDuration, c.GenesisBlock) +} diff --git a/aggsender/aggsender.go b/aggsender/aggsender.go index e3242bdf..dcbbc268 100644 --- a/aggsender/aggsender.go +++ b/aggsender/aggsender.go @@ -8,11 +8,12 @@ import ( "fmt" "math/big" "os" + "slices" "time" "github.com/0xPolygon/cdk/agglayer" "github.com/0xPolygon/cdk/aggsender/db" - aggsendertypes "github.com/0xPolygon/cdk/aggsender/types" + "github.com/0xPolygon/cdk/aggsender/types" "github.com/0xPolygon/cdk/bridgesync" cdkcommon "github.com/0xPolygon/cdk/common" "github.com/0xPolygon/cdk/l1infotreesync" @@ -33,10 +34,11 @@ var ( // AggSender is a component that will send certificates to the aggLayer type AggSender struct { - log aggsendertypes.Logger + log types.Logger - l2Syncer aggsendertypes.L2BridgeSyncer - l1infoTreeSyncer aggsendertypes.L1InfoTreeSyncer + l2Syncer types.L2BridgeSyncer + l1infoTreeSyncer types.L1InfoTreeSyncer + epochNotifier types.EpochNotifier storage db.AggSenderStorage aggLayerClient agglayer.AgglayerClientInterface @@ -53,7 +55,8 @@ func New( cfg Config, aggLayerClient agglayer.AgglayerClientInterface, l1InfoTreeSyncer *l1infotreesync.L1InfoTreeSync, - l2Syncer *bridgesync.BridgeSync) (*AggSender, error) { + l2Syncer *bridgesync.BridgeSync, + epochNotifier types.EpochNotifier) (*AggSender, error) { storage, err := db.NewAggSenderSQLStorage(logger, cfg.StoragePath) if err != nil { return nil, err @@ -74,24 +77,30 @@ func New( aggLayerClient: aggLayerClient, l1infoTreeSyncer: l1InfoTreeSyncer, sequencerKey: sequencerPrivateKey, + epochNotifier: epochNotifier, }, nil } // Start starts the AggSender func (a *AggSender) Start(ctx context.Context) { - go a.sendCertificates(ctx) - go a.checkIfCertificatesAreSettled(ctx) + a.sendCertificates(ctx) } // sendCertificates sends certificates to the aggLayer func (a *AggSender) sendCertificates(ctx context.Context) { - ticker := time.NewTicker(a.cfg.BlockGetInterval.Duration) - + chEpoch := a.epochNotifier.Subscribe("aggsender") for { select { - case <-ticker.C: - if _, err := a.sendCertificate(ctx); err != nil { - log.Error(err) + case epoch := <-chEpoch: + a.log.Infof("Epoch received: %s", epoch.String()) + thereArePendingCerts, err := a.checkPendingCertificatesStatus(ctx) + if err == nil && !thereArePendingCerts { + if _, err := a.sendCertificate(ctx); err != nil { + log.Error(err) + } + } else { + log.Warnf("Skipping epoch %s because there are pending certificates %v or error: %w", + epoch.String(), thereArePendingCerts, err) } case <-ctx.Done(): a.log.Info("AggSender stopped") @@ -183,7 +192,7 @@ func (a *AggSender) sendCertificate(ctx context.Context) (*agglayer.SignedCertif } createdTime := time.Now().UTC().UnixMilli() - certInfo := aggsendertypes.CertificateInfo{ + certInfo := types.CertificateInfo{ Height: certificate.Height, CertificateID: certificateHash, NewLocalExitRoot: certificate.NewLocalExitRoot, @@ -224,7 +233,7 @@ func (a *AggSender) saveCertificateToFile(signedCertificate *agglayer.SignedCert // getNextHeightAndPreviousLER returns the height and previous LER for the new certificate func (a *AggSender) getNextHeightAndPreviousLER( - lastSentCertificateInfo *aggsendertypes.CertificateInfo) (uint64, common.Hash) { + lastSentCertificateInfo *types.CertificateInfo) (uint64, common.Hash) { height := lastSentCertificateInfo.Height + 1 if lastSentCertificateInfo.Status == agglayer.InError { // previous certificate was in error, so we need to resend it @@ -247,7 +256,7 @@ func (a *AggSender) getNextHeightAndPreviousLER( func (a *AggSender) buildCertificate(ctx context.Context, bridges []bridgesync.Bridge, claims []bridgesync.Claim, - lastSentCertificateInfo aggsendertypes.CertificateInfo, + lastSentCertificateInfo types.CertificateInfo, toBlock uint64) (*agglayer.Certificate, error) { if len(bridges) == 0 && len(claims) == 0 { return nil, errNoBridgesAndClaims @@ -475,34 +484,30 @@ func (a *AggSender) signCertificate(certificate *agglayer.Certificate) (*agglaye }, nil } -// checkIfCertificatesAreSettled checks if certificates are settled -func (a *AggSender) checkIfCertificatesAreSettled(ctx context.Context) { - ticker := time.NewTicker(a.cfg.CheckSettledInterval.Duration) - for { - select { - case <-ticker.C: - a.checkPendingCertificatesStatus(ctx) - case <-ctx.Done(): - return - } - } -} - // checkPendingCertificatesStatus checks the status of pending certificates // and updates in the storage if it changed on agglayer -func (a *AggSender) checkPendingCertificatesStatus(ctx context.Context) { +// It returns: +// bool -> if there are pending certificates +// error -> if there was an error +func (a *AggSender) checkPendingCertificatesStatus(ctx context.Context) (bool, error) { pendingCertificates, err := a.storage.GetCertificatesByStatus(nonSettledStatuses) if err != nil { - a.log.Errorf("error getting pending certificates: %w", err) - return + err = fmt.Errorf("error getting pending certificates: %w", err) + a.log.Error(err) + return true, err } + thereArePendingCertificates := false a.log.Debugf("checkPendingCertificatesStatus num of pendingCertificates: %d", len(pendingCertificates)) for _, certificate := range pendingCertificates { certificateHeader, err := a.aggLayerClient.GetCertificateHeader(certificate.CertificateID) if err != nil { - a.log.Errorf("error getting certificate header of %s from agglayer: %w", - certificate.String(), err) - continue + err = fmt.Errorf("error getting certificate header of %d/%s from agglayer: %w", + certificate.Height, certificate.String(), err) + a.log.Error(err) + return true, err + } + if slices.Contains(nonSettledStatuses, certificateHeader.Status) { + thereArePendingCertificates = true } a.log.Debugf("aggLayerClient.GetCertificateHeader status [%s] of certificate %s ", certificateHeader.Status, @@ -516,11 +521,13 @@ func (a *AggSender) checkPendingCertificatesStatus(ctx context.Context) { certificate.UpdatedAt = time.Now().UTC().UnixMilli() if err := a.storage.UpdateCertificateStatus(ctx, *certificate); err != nil { - a.log.Errorf("error updating certificate %s status in storage: %w", certificateHeader.String(), err) - continue + err = fmt.Errorf("error updating certificate %s status in storage: %w", certificateHeader.String(), err) + a.log.Error(err) + return true, err } } } + return thereArePendingCertificates, nil } // shouldSendCertificate checks if a certificate should be sent at given time diff --git a/aggsender/aggsender_test.go b/aggsender/aggsender_test.go index e55422e0..0d071e76 100644 --- a/aggsender/aggsender_test.go +++ b/aggsender/aggsender_test.go @@ -27,12 +27,19 @@ import ( func TestExploratoryGetCertificateHeader(t *testing.T) { t.Skip("This test is exploratory and should be skipped") - aggLayerClient := agglayer.NewAggLayerClient("http://localhost:32795") + aggLayerClient := agglayer.NewAggLayerClient("http://localhost:32796") certificateID := common.HexToHash("0xf153e75e24591432ac5deafaeaafba3fec0fd851261c86051b9c0d540b38c369") certificateHeader, err := aggLayerClient.GetCertificateHeader(certificateID) require.NoError(t, err) fmt.Print(certificateHeader) } +func TestExploratoryGetEpochConfiguration(t *testing.T) { + t.Skip("This test is exploratory and should be skipped") + aggLayerClient := agglayer.NewAggLayerClient("http://localhost:32796") + clockConfig, err := aggLayerClient.GetEpochConfiguration() + require.NoError(t, err) + fmt.Print(clockConfig) +} func TestConfigString(t *testing.T) { config := Config{ @@ -42,6 +49,8 @@ func TestConfigString(t *testing.T) { CheckSettledInterval: types.Duration{Duration: 20 * time.Second}, AggsenderPrivateKey: types.KeystoreFileConfig{Path: "/path/to/key", Password: "password"}, URLRPCL2: "http://l2.rpc.url", + BlockFinality: "latestBlock", + EpochNotificationPercentage: 50, SaveCertificatesToFilesPath: "/path/to/certificates", } @@ -52,6 +61,8 @@ func TestConfigString(t *testing.T) { "AggsenderPrivateKeyPath: /path/to/key\n" + "AggsenderPrivateKeyPassword: password\n" + "URLRPCL2: http://l2.rpc.url\n" + + "BlockFinality: latestBlock\n" + + "EpochNotificationPercentage: 50\n" + "SaveCertificatesToFilesPath: /path/to/certificates\n" require.Equal(t, expected, config.String()) @@ -274,7 +285,8 @@ func TestGetImportedBridgeExits(t *testing.T) { t.Parallel() mockProof := generateTestProof(t) - mockL1InfoTreeSyncer := mocks.NewL1InfoTreeSyncerMock(t) + + mockL1InfoTreeSyncer := mocks.NewL1InfoTreeSyncer(t) mockL1InfoTreeSyncer.On("GetInfoByGlobalExitRoot", mock.Anything).Return(&l1infotreesync.L1InfoTreeLeaf{ L1InfoTreeIndex: 1, Timestamp: 123456789, @@ -507,8 +519,8 @@ func TestGetImportedBridgeExits(t *testing.T) { } func TestBuildCertificate(t *testing.T) { - mockL2BridgeSyncer := mocks.NewL2BridgeSyncerMock(t) - mockL1InfoTreeSyncer := mocks.NewL1InfoTreeSyncerMock(t) + mockL2BridgeSyncer := mocks.NewL2BridgeSyncer(t) + mockL1InfoTreeSyncer := mocks.NewL1InfoTreeSyncer(t) mockProof := generateTestProof(t) tests := []struct { @@ -738,17 +750,17 @@ func generateTestProof(t *testing.T) treeTypes.Proof { } func TestCheckIfCertificatesAreSettled(t *testing.T) { - t.Parallel() - tests := []struct { - name string - pendingCertificates []*aggsendertypes.CertificateInfo - certificateHeaders map[common.Hash]*agglayer.CertificateHeader - getFromDBError error - clientError error - updateDBError error - expectedErrorLogMessages []string - expectedInfoMessages []string + name string + pendingCertificates []*aggsendertypes.CertificateInfo + certificateHeaders map[common.Hash]*agglayer.CertificateHeader + getFromDBError error + clientError error + updateDBError error + expectedErrorLogMessages []string + expectedInfoMessages []string + expectedThereArePendingCerts bool + expectedError bool }{ { name: "All certificates settled - update successful", @@ -784,6 +796,8 @@ func TestCheckIfCertificatesAreSettled(t *testing.T) { expectedErrorLogMessages: []string{ "error getting pending certificates: %w", }, + expectedThereArePendingCerts: true, + expectedError: true, }, { name: "Error getting certificate header", @@ -797,6 +811,8 @@ func TestCheckIfCertificatesAreSettled(t *testing.T) { expectedErrorLogMessages: []string{ "error getting header of certificate %s with height: %d from agglayer: %w", }, + expectedThereArePendingCerts: true, + expectedError: true, }, { name: "Error updating certificate status", @@ -813,6 +829,8 @@ func TestCheckIfCertificatesAreSettled(t *testing.T) { expectedInfoMessages: []string{ "certificate %s changed status to %s", }, + expectedThereArePendingCerts: true, + expectedError: true, }, } @@ -820,9 +838,7 @@ func TestCheckIfCertificatesAreSettled(t *testing.T) { tt := tt t.Run(tt.name, func(t *testing.T) { - t.Parallel() - - mockStorage := mocks.NewAggSenderStorageMock(t) + mockStorage := mocks.NewAggSenderStorage(t) mockAggLayerClient := agglayer.NewAgglayerClientMock(t) mockLogger := log.WithFields("test", "unittest") @@ -847,14 +863,10 @@ func TestCheckIfCertificatesAreSettled(t *testing.T) { }, } - ctx, cancel := context.WithCancel(context.Background()) - defer cancel() - - go aggSender.checkIfCertificatesAreSettled(ctx) - - time.Sleep(2 * time.Second) - cancel() - + ctx := context.TODO() + thereArePendingCerts, err := aggSender.checkPendingCertificatesStatus(ctx) + require.Equal(t, tt.expectedThereArePendingCerts, thereArePendingCerts) + require.Equal(t, tt.expectedError, err != nil) mockAggLayerClient.AssertExpectations(t) mockStorage.AssertExpectations(t) }) @@ -885,23 +897,23 @@ func TestSendCertificate(t *testing.T) { expectedError string } - setupTest := func(cfg testCfg) (*AggSender, *mocks.AggSenderStorageMock, *mocks.L2BridgeSyncerMock, - *agglayer.AgglayerClientMock, *mocks.L1InfoTreeSyncerMock) { + setupTest := func(cfg testCfg) (*AggSender, *mocks.AggSenderStorage, *mocks.L2BridgeSyncer, + *agglayer.AgglayerClientMock, *mocks.L1InfoTreeSyncer) { var ( aggsender = &AggSender{ log: log.WithFields("aggsender", 1), cfg: Config{}, sequencerKey: cfg.sequencerKey, } - mockStorage *mocks.AggSenderStorageMock - mockL2Syncer *mocks.L2BridgeSyncerMock + mockStorage *mocks.AggSenderStorage + mockL2Syncer *mocks.L2BridgeSyncer mockAggLayerClient *agglayer.AgglayerClientMock - mockL1InfoTreeSyncer *mocks.L1InfoTreeSyncerMock + mockL1InfoTreeSyncer *mocks.L1InfoTreeSyncer ) if cfg.shouldSendCertificate != nil || cfg.getLastSentCertificate != nil || cfg.saveLastSentCertificate != nil { - mockStorage = mocks.NewAggSenderStorageMock(t) + mockStorage = mocks.NewAggSenderStorage(t) mockStorage.On("GetCertificatesByStatus", nonSettledStatuses). Return(cfg.shouldSendCertificate...).Once() @@ -918,7 +930,7 @@ func TestSendCertificate(t *testing.T) { if cfg.lastL2BlockProcessed != nil || cfg.originNetwork != nil || cfg.getBridges != nil || cfg.getClaims != nil || cfg.getInfoByGlobalExitRoot != nil { - mockL2Syncer = mocks.NewL2BridgeSyncerMock(t) + mockL2Syncer = mocks.NewL2BridgeSyncer(t) mockL2Syncer.On("GetLastProcessedBlock", mock.Anything).Return(cfg.lastL2BlockProcessed...).Once() @@ -950,7 +962,7 @@ func TestSendCertificate(t *testing.T) { if cfg.getInfoByGlobalExitRoot != nil || cfg.getL1InfoTreeRootByIndex != nil || cfg.getL1InfoTreeMerkleProofFromIndexToRoot != nil { - mockL1InfoTreeSyncer = mocks.NewL1InfoTreeSyncerMock(t) + mockL1InfoTreeSyncer = mocks.NewL1InfoTreeSyncer(t) mockL1InfoTreeSyncer.On("GetInfoByGlobalExitRoot", mock.Anything).Return(cfg.getInfoByGlobalExitRoot...).Once() if cfg.getL1InfoTreeRootByIndex != nil { @@ -1481,10 +1493,10 @@ func TestSendCertificate_NoClaims(t *testing.T) { require.NoError(t, err) ctx := context.Background() - mockStorage := mocks.NewAggSenderStorageMock(t) - mockL2Syncer := mocks.NewL2BridgeSyncerMock(t) + mockStorage := mocks.NewAggSenderStorage(t) + mockL2Syncer := mocks.NewL2BridgeSyncer(t) mockAggLayerClient := agglayer.NewAgglayerClientMock(t) - mockL1InfoTreeSyncer := mocks.NewL1InfoTreeSyncerMock(t) + mockL1InfoTreeSyncer := mocks.NewL1InfoTreeSyncer(t) aggSender := &AggSender{ log: log.WithFields("aggsender-test", "no claims test"), diff --git a/aggsender/block_notifier_polling.go b/aggsender/block_notifier_polling.go new file mode 100644 index 00000000..17dafefa --- /dev/null +++ b/aggsender/block_notifier_polling.go @@ -0,0 +1,219 @@ +package aggsender + +import ( + "context" + "fmt" + "math/big" + "sync" + "time" + + "github.com/0xPolygon/cdk/aggsender/types" + "github.com/0xPolygon/cdk/etherman" +) + +var ( + timeNowFunc = time.Now +) + +const ( + AutomaticBlockInterval = time.Second * 0 + // minBlockInterval is the minimum interval at which the AggSender will check for new blocks + minBlockInterval = time.Second + // maxBlockInterval is the maximum interval at which the AggSender will check for new blocks + maxBlockInterval = time.Minute +) + +type ConfigBlockNotifierPolling struct { + // BlockFinalityType is the finality of the block to be notified + BlockFinalityType etherman.BlockNumberFinality + // CheckNewBlockInterval is the interval at which the AggSender will check for new blocks + // if is 0 it will be calculated automatically + CheckNewBlockInterval time.Duration +} + +type BlockNotifierPolling struct { + ethClient types.EthClient + blockFinality *big.Int + logger types.Logger + config ConfigBlockNotifierPolling + mu sync.Mutex + lastStatus *blockNotifierPollingInternalStatus + types.GenericSubscriber[types.EventNewBlock] +} + +// NewBlockNotifierPolling creates a new BlockNotifierPolling. +// if param `subscriber` is nil a new GenericSubscriberImpl[types.EventNewBlock] will be created. +// To use this class you need to subscribe and each time that a new block appear the subscriber +// will be notified through the channel. (check unit tests TestExploratoryBlockNotifierPolling +// for more information) +func NewBlockNotifierPolling(ethClient types.EthClient, + config ConfigBlockNotifierPolling, + logger types.Logger, + subscriber types.GenericSubscriber[types.EventNewBlock]) (*BlockNotifierPolling, error) { + if subscriber == nil { + subscriber = NewGenericSubscriberImpl[types.EventNewBlock]() + } + finality, err := config.BlockFinalityType.ToBlockNum() + if err != nil { + return nil, fmt.Errorf("failed to convert block finality type to block number: %w", err) + } + + return &BlockNotifierPolling{ + ethClient: ethClient, + blockFinality: finality, + logger: logger, + config: config, + GenericSubscriber: subscriber, + }, nil +} + +func (b *BlockNotifierPolling) String() string { + status := b.getGlobalStatus() + res := fmt.Sprintf("BlockNotifierPolling: finality=%s", b.config.BlockFinalityType) + if status != nil { + res += fmt.Sprintf(" lastBlockSeen=%d", status.lastBlockSeen) + } else { + res += " lastBlockSeen=none" + } + return res +} + +// Start starts the BlockNotifierPolling blocking the current goroutine +func (b *BlockNotifierPolling) Start(ctx context.Context) { + ticker := time.NewTimer(b.config.CheckNewBlockInterval) + defer ticker.Stop() + + var status *blockNotifierPollingInternalStatus = nil + + for { + select { + case <-ctx.Done(): + return + case <-ticker.C: + delay, newStatus, event := b.step(ctx, status) + status = newStatus + b.setGlobalStatus(status) + if event != nil { + b.Publish(*event) + } + ticker.Reset(delay) + } + } +} + +func (b *BlockNotifierPolling) setGlobalStatus(status *blockNotifierPollingInternalStatus) { + b.mu.Lock() + defer b.mu.Unlock() + b.lastStatus = status +} + +func (b *BlockNotifierPolling) getGlobalStatus() *blockNotifierPollingInternalStatus { + b.mu.Lock() + defer b.mu.Unlock() + if b.lastStatus == nil { + return nil + } + copyStatus := *b.lastStatus + return ©Status +} + +// step is the main function of the BlockNotifierPolling, it checks if there is a new block +// it returns: +// - the delay for the next check +// - the new status +// - the new even to emit or nil +func (b *BlockNotifierPolling) step(ctx context.Context, + previousState *blockNotifierPollingInternalStatus) (time.Duration, + *blockNotifierPollingInternalStatus, *types.EventNewBlock) { + currentBlock, err := b.ethClient.HeaderByNumber(ctx, b.blockFinality) + if err == nil && currentBlock == nil { + err = fmt.Errorf("failed to get block number: return a nil block") + } + if err != nil { + b.logger.Errorf("Failed to get block number: %v", err) + newState := previousState.clear() + return b.nextBlockRequestDelay(nil, err), newState, nil + } + if previousState == nil { + newState := previousState.intialBlock(currentBlock.Number.Uint64()) + return b.nextBlockRequestDelay(previousState, nil), newState, nil + } + if currentBlock.Number.Uint64() == previousState.lastBlockSeen { + // No new block, so no changes on state + return b.nextBlockRequestDelay(previousState, nil), previousState, nil + } + // New blockNumber! + eventToEmit := &types.EventNewBlock{ + BlockNumber: currentBlock.Number.Uint64(), + BlockFinalityType: b.config.BlockFinalityType, + } + + if currentBlock.Number.Uint64()-previousState.lastBlockSeen != 1 { + b.logger.Warnf("Missed block(s) [finality:%s]: %d -> %d", + b.config.BlockFinalityType, previousState.lastBlockSeen, currentBlock.Number.Uint64()) + // It start from scratch because something fails in calculation of block period + newState := previousState.intialBlock(currentBlock.Number.Uint64()) + return b.nextBlockRequestDelay(nil, nil), newState, eventToEmit + } + newState := previousState.incommingNewBlock(currentBlock.Number.Uint64()) + b.logger.Debugf("New block seen [finality:%s]: %d. blockRate:%s", + b.config.BlockFinalityType, currentBlock.Number.Uint64(), newState.previousBlockTime) + + return b.nextBlockRequestDelay(newState, nil), newState, eventToEmit +} + +func (b *BlockNotifierPolling) nextBlockRequestDelay(status *blockNotifierPollingInternalStatus, + err error) time.Duration { + if b.config.CheckNewBlockInterval == AutomaticBlockInterval { + return b.config.CheckNewBlockInterval + } + // Initial stages wait the minimum interval to increas accuracy + if status == nil || status.previousBlockTime == nil { + return minBlockInterval + } + if err != nil { + // If error we wait twice the min interval + return minBlockInterval * 2 //nolint:mnd // 2 times the interval + } + // we have a previous block time so we can calculate the interval + now := timeNowFunc() + expectedTimeNextBlock := status.lastBlockTime.Add(*status.previousBlockTime) + distanceToNextBlock := expectedTimeNextBlock.Sub(now) + interval := distanceToNextBlock * 4 / 5 //nolint:mnd // 80% of for reach the next block + return max(minBlockInterval, min(maxBlockInterval, interval)) +} + +type blockNotifierPollingInternalStatus struct { + lastBlockSeen uint64 + lastBlockTime time.Time // first appear of block lastBlockSeen + previousBlockTime *time.Duration // time of the previous block to appear +} + +func (s *blockNotifierPollingInternalStatus) String() string { + if s == nil { + return "nil" + } + return fmt.Sprintf("lastBlockSeen=%d lastBlockTime=%s previousBlockTime=%s", + s.lastBlockSeen, s.lastBlockTime, s.previousBlockTime) +} + +func (s *blockNotifierPollingInternalStatus) clear() *blockNotifierPollingInternalStatus { + return &blockNotifierPollingInternalStatus{} +} + +func (s *blockNotifierPollingInternalStatus) intialBlock(block uint64) *blockNotifierPollingInternalStatus { + return &blockNotifierPollingInternalStatus{ + lastBlockSeen: block, + lastBlockTime: timeNowFunc(), + } +} + +func (s *blockNotifierPollingInternalStatus) incommingNewBlock(block uint64) *blockNotifierPollingInternalStatus { + now := timeNowFunc() + timePreviousBlock := now.Sub(s.lastBlockTime) + return &blockNotifierPollingInternalStatus{ + lastBlockSeen: block, + lastBlockTime: now, + previousBlockTime: &timePreviousBlock, + } +} diff --git a/aggsender/block_notifier_polling_test.go b/aggsender/block_notifier_polling_test.go new file mode 100644 index 00000000..83b3b643 --- /dev/null +++ b/aggsender/block_notifier_polling_test.go @@ -0,0 +1,211 @@ +package aggsender + +import ( + "context" + "fmt" + "math/big" + "os" + "testing" + "time" + + "github.com/0xPolygon/cdk/aggsender/mocks" + aggsendertypes "github.com/0xPolygon/cdk/aggsender/types" + "github.com/0xPolygon/cdk/etherman" + "github.com/0xPolygon/cdk/log" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/ethclient" + "github.com/stretchr/testify/mock" + "github.com/stretchr/testify/require" +) + +func TestExploratoryBlockNotifierPolling(t *testing.T) { + t.Skip() + urlRPCL1 := os.Getenv("L1URL") + fmt.Println("URL=", urlRPCL1) + ethClient, err := ethclient.Dial(urlRPCL1) + require.NoError(t, err) + + sut, errSut := NewBlockNotifierPolling(ethClient, + ConfigBlockNotifierPolling{ + BlockFinalityType: etherman.LatestBlock, + }, log.WithFields("test", "test"), nil) + require.NoError(t, errSut) + go sut.Start(context.Background()) + ch := sut.Subscribe("test") + for { + select { + case block := <-ch: + fmt.Println(block) + } + } +} + +func TestBlockNotifierPollingStep(t *testing.T) { + time0 := time.Unix(1731322117, 0) + period0 := time.Second * 10 + period0_80percent := time.Second * 8 + time1 := time0.Add(period0) + tests := []struct { + name string + previousStatus *blockNotifierPollingInternalStatus + HeaderByNumberError bool + HeaderByNumberErrorNumber uint64 + forcedTime time.Time + expectedStatus *blockNotifierPollingInternalStatus + expectedDelay time.Duration + expectedEvent *aggsendertypes.EventNewBlock + }{ + { + name: "initial->receive block", + previousStatus: nil, + HeaderByNumberError: false, + HeaderByNumberErrorNumber: 100, + forcedTime: time0, + expectedStatus: &blockNotifierPollingInternalStatus{ + lastBlockSeen: 100, + lastBlockTime: time0, + }, + expectedDelay: minBlockInterval, + expectedEvent: nil, + }, + { + name: "received block->error", + previousStatus: nil, + HeaderByNumberError: true, + forcedTime: time0, + expectedStatus: &blockNotifierPollingInternalStatus{}, + expectedDelay: minBlockInterval, + expectedEvent: nil, + }, + + { + name: "have block period->receive new block", + previousStatus: &blockNotifierPollingInternalStatus{ + lastBlockSeen: 100, + lastBlockTime: time0, + previousBlockTime: &period0, + }, + HeaderByNumberError: false, + HeaderByNumberErrorNumber: 101, + forcedTime: time1, + expectedStatus: &blockNotifierPollingInternalStatus{ + lastBlockSeen: 101, + lastBlockTime: time1, + previousBlockTime: &period0, + }, + expectedDelay: period0_80percent, + expectedEvent: &aggsendertypes.EventNewBlock{ + BlockNumber: 101, + }, + }, + } + for _, tt := range tests { + tt := tt + t.Run(tt.name, func(t *testing.T) { + testData := newBlockNotifierPollingTestData(t, nil) + + timeNowFunc = func() time.Time { + return tt.forcedTime + } + + if tt.HeaderByNumberError == false { + hdr1 := &types.Header{ + Number: big.NewInt(int64(tt.HeaderByNumberErrorNumber)), + } + testData.ethClientMock.EXPECT().HeaderByNumber(mock.Anything, mock.Anything).Return(hdr1, nil).Once() + } else { + testData.ethClientMock.EXPECT().HeaderByNumber(mock.Anything, mock.Anything).Return(nil, fmt.Errorf("error")).Once() + } + delay, newStatus, event := testData.sut.step(context.TODO(), tt.previousStatus) + require.Equal(t, tt.expectedDelay, delay, "delay") + require.Equal(t, tt.expectedStatus, newStatus, "new_status") + if tt.expectedEvent == nil { + require.Nil(t, event, "send_event") + } else { + require.Equal(t, tt.expectedEvent.BlockNumber, event.BlockNumber, "send_event") + } + }) + } +} + +func TestDelayNoPreviousBLock(t *testing.T) { + testData := newBlockNotifierPollingTestData(t, nil) + status := blockNotifierPollingInternalStatus{ + lastBlockSeen: 100, + } + delay := testData.sut.nextBlockRequestDelay(&status, nil) + require.Equal(t, minBlockInterval, delay) +} + +func TestDelayBLock(t *testing.T) { + testData := newBlockNotifierPollingTestData(t, nil) + pt := time.Second * 10 + status := blockNotifierPollingInternalStatus{ + lastBlockSeen: 100, + previousBlockTime: &pt, + } + delay := testData.sut.nextBlockRequestDelay(&status, nil) + require.Equal(t, minBlockInterval, delay) +} + +func TestNewBlockNotifierPolling(t *testing.T) { + testData := newBlockNotifierPollingTestData(t, nil) + require.NotNil(t, testData.sut) + _, err := NewBlockNotifierPolling(testData.ethClientMock, ConfigBlockNotifierPolling{ + BlockFinalityType: etherman.BlockNumberFinality("invalid"), + }, log.WithFields("test", "test"), nil) + require.Error(t, err) +} + +func TestBlockNotifierPollingString(t *testing.T) { + testData := newBlockNotifierPollingTestData(t, nil) + require.NotEmpty(t, testData.sut.String()) + testData.sut.lastStatus = &blockNotifierPollingInternalStatus{ + lastBlockSeen: 100, + } + require.NotEmpty(t, testData.sut.String()) +} + +func TestBlockNotifierPollingStart(t *testing.T) { + testData := newBlockNotifierPollingTestData(t, nil) + ch := testData.sut.Subscribe("test") + hdr1 := &types.Header{ + Number: big.NewInt(100), + } + testData.ethClientMock.EXPECT().HeaderByNumber(mock.Anything, mock.Anything).Return(hdr1, nil).Once() + hdr2 := &types.Header{ + Number: big.NewInt(101), + } + testData.ethClientMock.EXPECT().HeaderByNumber(mock.Anything, mock.Anything).Return(hdr2, nil).Once() + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + go testData.sut.Start(ctx) + block := <-ch + require.NotNil(t, block) + require.Equal(t, uint64(101), block.BlockNumber) +} + +type blockNotifierPollingTestData struct { + sut *BlockNotifierPolling + ethClientMock *mocks.EthClient + ctx context.Context +} + +func newBlockNotifierPollingTestData(t *testing.T, config *ConfigBlockNotifierPolling) blockNotifierPollingTestData { + t.Helper() + if config == nil { + config = &ConfigBlockNotifierPolling{ + BlockFinalityType: etherman.LatestBlock, + CheckNewBlockInterval: time.Second, + } + } + EthClientMock := mocks.NewEthClient(t) + logger := log.WithFields("test", "BlockNotifierPolling") + sut, err := NewBlockNotifierPolling(EthClientMock, *config, logger, nil) + require.NoError(t, err) + return blockNotifierPollingTestData{ + sut: sut, + ethClientMock: EthClientMock, + ctx: context.TODO(), + } +} diff --git a/aggsender/config.go b/aggsender/config.go index 4ff78f96..8ae0b759 100644 --- a/aggsender/config.go +++ b/aggsender/config.go @@ -1,6 +1,8 @@ package aggsender import ( + "fmt" + "github.com/0xPolygon/cdk/config/types" ) @@ -18,6 +20,13 @@ type Config struct { AggsenderPrivateKey types.KeystoreFileConfig `mapstructure:"AggsenderPrivateKey"` // URLRPCL2 is the URL of the L2 RPC node URLRPCL2 string `mapstructure:"URLRPCL2"` + // BlockFinality indicates which finality follows AggLayer + BlockFinality string `jsonschema:"enum=LatestBlock, enum=SafeBlock, enum=PendingBlock, enum=FinalizedBlock, enum=EarliestBlock" mapstructure:"BlockFinality"` //nolint:lll + // EpochNotificationPercentage indicates the percentage of the epoch + // the AggSender should send the certificate + // 0 -> Begin + // 50 -> Middle + EpochNotificationPercentage uint `mapstructure:"EpochNotificationPercentage"` // SaveCertificatesToFilesPath if != "" tells the AggSender to save the certificates to a file in this path SaveCertificatesToFilesPath string `mapstructure:"SaveCertificatesToFilesPath"` } @@ -31,5 +40,7 @@ func (c Config) String() string { "AggsenderPrivateKeyPath: " + c.AggsenderPrivateKey.Path + "\n" + "AggsenderPrivateKeyPassword: " + c.AggsenderPrivateKey.Password + "\n" + "URLRPCL2: " + c.URLRPCL2 + "\n" + + "BlockFinality: " + c.BlockFinality + "\n" + + "EpochNotificationPercentage: " + fmt.Sprintf("%d", c.EpochNotificationPercentage) + "\n" + "SaveCertificatesToFilesPath: " + c.SaveCertificatesToFilesPath + "\n" } diff --git a/aggsender/epoch_notifier_per_block.go b/aggsender/epoch_notifier_per_block.go new file mode 100644 index 00000000..3b560731 --- /dev/null +++ b/aggsender/epoch_notifier_per_block.go @@ -0,0 +1,204 @@ +package aggsender + +import ( + "context" + "fmt" + + "github.com/0xPolygon/cdk/agglayer" + "github.com/0xPolygon/cdk/aggsender/types" +) + +const ( + maxPercent = 100.0 +) + +type ExtraInfoEventEpoch struct { + PendingBlocks int +} + +func (e *ExtraInfoEventEpoch) String() string { + return fmt.Sprintf("ExtraInfoEventEpoch: pendingBlocks=%d", e.PendingBlocks) +} + +type ConfigEpochNotifierPerBlock struct { + StartingEpochBlock uint64 + NumBlockPerEpoch uint + + // EpochNotificationPercentage + // 0 -> begin new Epoch + // 50 -> middle of epoch + // 100 -> end of epoch (same as 0) + EpochNotificationPercentage uint +} + +func NewConfigEpochNotifierPerBlock(aggLayer agglayer.AggLayerClientGetEpochConfiguration, + epochNotificationPercentage uint) (*ConfigEpochNotifierPerBlock, error) { + if aggLayer == nil { + return nil, fmt.Errorf("newConfigEpochNotifierPerBlock: aggLayerClient is required") + } + clockConfig, err := aggLayer.GetEpochConfiguration() + if err != nil { + return nil, fmt.Errorf("newConfigEpochNotifierPerBlock: error getting clock configuration from AggLayer: %w", err) + } + return &ConfigEpochNotifierPerBlock{ + StartingEpochBlock: clockConfig.GenesisBlock, + NumBlockPerEpoch: uint(clockConfig.EpochDuration), + EpochNotificationPercentage: epochNotificationPercentage, + }, nil +} + +func (c *ConfigEpochNotifierPerBlock) Validate() error { + if c.NumBlockPerEpoch == 0 { + return fmt.Errorf("numBlockPerEpoch: num block per epoch is required > 0 ") + } + if c.EpochNotificationPercentage >= maxPercent { + return fmt.Errorf("epochNotificationPercentage: must be between 0 and 99") + } + return nil +} + +type EpochNotifierPerBlock struct { + blockNotifier types.BlockNotifier + logger types.Logger + + lastStartingEpochBlock uint64 + + Config ConfigEpochNotifierPerBlock + types.GenericSubscriber[types.EpochEvent] +} + +func NewEpochNotifierPerBlock(blockNotifier types.BlockNotifier, + logger types.Logger, + config ConfigEpochNotifierPerBlock, + subscriber types.GenericSubscriber[types.EpochEvent]) (*EpochNotifierPerBlock, error) { + if subscriber == nil { + subscriber = NewGenericSubscriberImpl[types.EpochEvent]() + } + + err := config.Validate() + if err != nil { + return nil, fmt.Errorf("invalid config: %w", err) + } + return &EpochNotifierPerBlock{ + blockNotifier: blockNotifier, + logger: logger, + lastStartingEpochBlock: config.StartingEpochBlock, + Config: config, + GenericSubscriber: subscriber, + }, nil +} + +func (e *EpochNotifierPerBlock) String() string { + return fmt.Sprintf("EpochNotifierPerBlock: startingEpochBlock=%d, numBlockPerEpoch=%d,"+ + " EpochNotificationPercentage=%d", + e.Config.StartingEpochBlock, e.Config.NumBlockPerEpoch, e.Config.EpochNotificationPercentage) +} + +// StartAsync starts the notifier in a goroutine +func (e *EpochNotifierPerBlock) StartAsync(ctx context.Context) { + eventNewBlockChannel := e.blockNotifier.Subscribe("EpochNotifierPerBlock") + go e.startInternal(ctx, eventNewBlockChannel) +} + +// Start starts the notifier synchronously +func (e *EpochNotifierPerBlock) Start(ctx context.Context) { + eventNewBlockChannel := e.blockNotifier.Subscribe("EpochNotifierPerBlock") + e.startInternal(ctx, eventNewBlockChannel) +} + +func (e *EpochNotifierPerBlock) startInternal(ctx context.Context, eventNewBlockChannel <-chan types.EventNewBlock) { + status := internalStatus{ + lastBlockSeen: e.Config.StartingEpochBlock, + waitingForEpoch: e.epochNumber(e.Config.StartingEpochBlock), + } + for { + select { + case <-ctx.Done(): + return + case newBlock := <-eventNewBlockChannel: + var event *types.EpochEvent + status, event = e.step(status, newBlock) + if event != nil { + e.logger.Debugf("new Epoch Event: %s", event.String()) + e.GenericSubscriber.Publish(*event) + } + } + } +} + +type internalStatus struct { + lastBlockSeen uint64 + waitingForEpoch uint64 +} + +func (e *EpochNotifierPerBlock) step(status internalStatus, + newBlock types.EventNewBlock) (internalStatus, *types.EpochEvent) { + currentBlock := newBlock.BlockNumber + if currentBlock < e.Config.StartingEpochBlock { + // This is a bit strange, the first epoch is in the future + e.logger.Warnf("Block number %d is before the starting first epoch block %d."+ + " Please check your config", currentBlock, e.Config.StartingEpochBlock) + return status, nil + } + // No new block + if currentBlock <= status.lastBlockSeen { + return status, nil + } + status.lastBlockSeen = currentBlock + + needNotify, closingEpoch := e.isNotificationRequired(currentBlock, status.waitingForEpoch) + if needNotify { + // Notify the epoch has started + info := e.infoEpoch(currentBlock, closingEpoch) + status.waitingForEpoch = closingEpoch + 1 + return status, &types.EpochEvent{ + Epoch: closingEpoch, + ExtraInfo: info, + } + } + return status, nil +} + +func (e *EpochNotifierPerBlock) infoEpoch(currentBlock, newEpochNotified uint64) *ExtraInfoEventEpoch { + nextBlockStartingEpoch := e.endBlockEpoch(newEpochNotified) + return &ExtraInfoEventEpoch{ + PendingBlocks: int(nextBlockStartingEpoch - currentBlock), + } +} +func (e *EpochNotifierPerBlock) percentEpoch(currentBlock uint64) float64 { + epoch := e.epochNumber(currentBlock) + startingBlock := e.startingBlockEpoch(epoch) + elapsedBlocks := currentBlock - startingBlock + return float64(elapsedBlocks) / float64(e.Config.NumBlockPerEpoch) +} +func (e *EpochNotifierPerBlock) isNotificationRequired(currentBlock, lastEpochNotified uint64) (bool, uint64) { + percentEpoch := e.percentEpoch(currentBlock) + thresholdPercent := float64(e.Config.EpochNotificationPercentage) / maxPercent + maxTresholdPercent := float64(e.Config.NumBlockPerEpoch-1) / float64(e.Config.NumBlockPerEpoch) + if thresholdPercent > maxTresholdPercent { + thresholdPercent = maxTresholdPercent + } + if percentEpoch < thresholdPercent { + e.logger.Debugf("Block %d is at %f%% of the epoch no notify", currentBlock, percentEpoch*maxPercent) + return false, e.epochNumber(currentBlock) + } + nextEpoch := e.epochNumber(currentBlock) + 1 + return nextEpoch > lastEpochNotified, e.epochNumber(currentBlock) +} + +func (e *EpochNotifierPerBlock) startingBlockEpoch(epoch uint64) uint64 { + if epoch == 0 { + return e.Config.StartingEpochBlock - 1 + } + return e.Config.StartingEpochBlock + ((epoch - 1) * uint64(e.Config.NumBlockPerEpoch)) +} + +func (e *EpochNotifierPerBlock) endBlockEpoch(epoch uint64) uint64 { + return e.startingBlockEpoch(epoch + 1) +} +func (e *EpochNotifierPerBlock) epochNumber(currentBlock uint64) uint64 { + if currentBlock < e.Config.StartingEpochBlock { + return 0 + } + return 1 + ((currentBlock - e.Config.StartingEpochBlock) / uint64(e.Config.NumBlockPerEpoch)) +} diff --git a/aggsender/epoch_notifier_per_block_test.go b/aggsender/epoch_notifier_per_block_test.go new file mode 100644 index 00000000..203116d0 --- /dev/null +++ b/aggsender/epoch_notifier_per_block_test.go @@ -0,0 +1,219 @@ +package aggsender + +import ( + "context" + "fmt" + "testing" + + "github.com/0xPolygon/cdk/agglayer" + "github.com/0xPolygon/cdk/aggsender/mocks" + "github.com/0xPolygon/cdk/aggsender/types" + "github.com/0xPolygon/cdk/etherman" + "github.com/0xPolygon/cdk/log" + "github.com/stretchr/testify/mock" + "github.com/stretchr/testify/require" +) + +func TestStartingBlockEpoch(t *testing.T) { + testData := newNotifierPerBlockTestData(t, &ConfigEpochNotifierPerBlock{ + StartingEpochBlock: 9, + NumBlockPerEpoch: 10, + EpochNotificationPercentage: 80, + }) + // EPOCH: ---0 ----+----1 -----+----2 ----+----3 ----+----4 ----+----5 ----+---- + // BLOCK: 9 19 29 39 49 + require.Equal(t, uint64(8), testData.sut.startingBlockEpoch(0)) + require.Equal(t, uint64(9), testData.sut.startingBlockEpoch(1)) + require.Equal(t, uint64(19), testData.sut.startingBlockEpoch(2)) +} + +func TestEpochNotifyPercentageEdgeCase0(t *testing.T) { + testData := newNotifierPerBlockTestData(t, nil) + testData.sut.Config.EpochNotificationPercentage = 0 + notify, epoch := testData.sut.isNotificationRequired(9, 0) + require.True(t, notify) + require.Equal(t, uint64(1), epoch) +} + +// if percent is 99 means at end of epoch, so in a config 0, epoch-size=10, +// 99% means last block of epoch +func TestEpochNotifyPercentageEdgeCase99(t *testing.T) { + testData := newNotifierPerBlockTestData(t, nil) + testData.sut.Config.EpochNotificationPercentage = 99 + notify, epoch := testData.sut.isNotificationRequired(9, 0) + require.True(t, notify) + require.Equal(t, uint64(1), epoch) +} + +func TestEpochStep(t *testing.T) { + testData := newNotifierPerBlockTestData(t, &ConfigEpochNotifierPerBlock{ + StartingEpochBlock: 9, + NumBlockPerEpoch: 10, + EpochNotificationPercentage: 50, + }) + // EPOCH: ---0 ----+----1 -----+----2 ----+----3 ----+----4 ----+----5 ----+---- + // BLOCK: 9 19 29 39 49 + // start EPOCH#1 -> 9 + // end EPOCH#1 -> 19 + // start EPOCH#2 -> 19 + + tests := []struct { + name string + initialStatus internalStatus + blockNumber uint64 + expectedEvent bool + expectedEventEpoch uint64 + expectedEventPendingBlocks int + }{ + { + name: "First block of epoch, no notification until close to end", + initialStatus: internalStatus{lastBlockSeen: 8, waitingForEpoch: 0}, + blockNumber: 9, + expectedEvent: false, + expectedEventEpoch: 1, + expectedEventPendingBlocks: 0, + }, + { + name: "epoch#1 close to end, notify it!", + initialStatus: internalStatus{lastBlockSeen: 17, waitingForEpoch: 0}, + blockNumber: 18, + expectedEvent: true, + expectedEventEpoch: 1, // Finishing epoch 0 + expectedEventPendingBlocks: 1, // 19 - 18 + }, + { + name: "epoch#1 close to end, but already notified", + initialStatus: internalStatus{lastBlockSeen: 17, waitingForEpoch: 2}, + blockNumber: 18, + expectedEvent: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + _, event := testData.sut.step(tt.initialStatus, types.EventNewBlock{BlockNumber: tt.blockNumber, BlockFinalityType: etherman.LatestBlock}) + require.Equal(t, tt.expectedEvent, event != nil) + if event != nil { + require.Equal(t, tt.expectedEventEpoch, event.Epoch, "Epoch") + extraInfo, ok := event.ExtraInfo.(*ExtraInfoEventEpoch) + require.True(t, ok, "ExtraInfo") + require.Equal(t, tt.expectedEventPendingBlocks, extraInfo.PendingBlocks, "PendingBlocks") + } + }) + } +} + +func TestNewConfigEpochNotifierPerBlock(t *testing.T) { + _, err := NewConfigEpochNotifierPerBlock(nil, 1) + require.Error(t, err) + aggLayerMock := agglayer.NewAgglayerClientMock(t) + aggLayerMock.On("GetEpochConfiguration").Return(nil, fmt.Errorf("error")).Once() + _, err = NewConfigEpochNotifierPerBlock(aggLayerMock, 1) + require.Error(t, err) + cfgAggLayer := &agglayer.ClockConfiguration{ + GenesisBlock: 123, + EpochDuration: 456, + } + aggLayerMock.On("GetEpochConfiguration").Return(cfgAggLayer, nil).Once() + cfg, err := NewConfigEpochNotifierPerBlock(aggLayerMock, 1) + require.NoError(t, err) + require.Equal(t, uint64(123), cfg.StartingEpochBlock) + require.Equal(t, uint(456), cfg.NumBlockPerEpoch) +} + +func TestNotifyEpoch(t *testing.T) { + testData := newNotifierPerBlockTestData(t, nil) + ch := testData.sut.Subscribe("test") + chBlocks := make(chan types.EventNewBlock) + testData.blockNotifierMock.EXPECT().Subscribe(mock.Anything).Return(chBlocks) + testData.sut.StartAsync(testData.ctx) + chBlocks <- types.EventNewBlock{BlockNumber: 109, BlockFinalityType: etherman.LatestBlock} + epochEvent := <-ch + require.Equal(t, uint64(11), epochEvent.Epoch) + testData.ctx.Done() +} + +func TestStepSameEpoch(t *testing.T) { + testData := newNotifierPerBlockTestData(t, nil) + status := internalStatus{ + lastBlockSeen: 100, + waitingForEpoch: testData.sut.epochNumber(100), + } + newStatus, _ := testData.sut.step(status, types.EventNewBlock{BlockNumber: 103, BlockFinalityType: etherman.LatestBlock}) + require.Equal(t, uint64(103), newStatus.lastBlockSeen) + require.Equal(t, status.waitingForEpoch, newStatus.waitingForEpoch) +} + +func TestStepNotifyEpoch(t *testing.T) { + testData := newNotifierPerBlockTestData(t, nil) + status := internalStatus{ + lastBlockSeen: 100, + waitingForEpoch: testData.sut.epochNumber(100), + } + status, _ = testData.sut.step(status, types.EventNewBlock{BlockNumber: 109, BlockFinalityType: etherman.LatestBlock}) + require.Equal(t, uint64(109), status.lastBlockSeen) + require.Equal(t, uint64(12), status.waitingForEpoch) +} + +func TestBlockEpochNumber(t *testing.T) { + testData := newNotifierPerBlockTestData(t, &ConfigEpochNotifierPerBlock{ + StartingEpochBlock: 105, + NumBlockPerEpoch: 10, + EpochNotificationPercentage: 1, + }) + require.Equal(t, uint64(0), testData.sut.epochNumber(0)) + require.Equal(t, uint64(0), testData.sut.epochNumber(104)) + require.Equal(t, uint64(1), testData.sut.epochNumber(105)) + require.Equal(t, uint64(1), testData.sut.epochNumber(114)) + require.Equal(t, uint64(2), testData.sut.epochNumber(115)) + require.Equal(t, uint64(2), testData.sut.epochNumber(116)) + require.Equal(t, uint64(2), testData.sut.epochNumber(124)) + require.Equal(t, uint64(3), testData.sut.epochNumber(125)) +} + +func TestBlockBeforeEpoch(t *testing.T) { + testData := newNotifierPerBlockTestData(t, &ConfigEpochNotifierPerBlock{ + StartingEpochBlock: 105, + NumBlockPerEpoch: 10, + EpochNotificationPercentage: 1, + }) + status := internalStatus{ + lastBlockSeen: 104, + waitingForEpoch: testData.sut.epochNumber(104), + } + newStatus, _ := testData.sut.step(status, types.EventNewBlock{BlockNumber: 104, BlockFinalityType: etherman.LatestBlock}) + // We are previous block of first epoch, so we should do nothing + require.Equal(t, status, newStatus) + status = newStatus + // First block of first epoch + newStatus, _ = testData.sut.step(status, types.EventNewBlock{BlockNumber: 105, BlockFinalityType: etherman.LatestBlock}) + require.Equal(t, uint64(105), newStatus.lastBlockSeen) + // Near end first epoch + newStatus, _ = testData.sut.step(status, types.EventNewBlock{BlockNumber: 114, BlockFinalityType: etherman.LatestBlock}) + require.Equal(t, uint64(114), newStatus.lastBlockSeen) +} + +type notifierPerBlockTestData struct { + sut *EpochNotifierPerBlock + blockNotifierMock *mocks.BlockNotifier + ctx context.Context +} + +func newNotifierPerBlockTestData(t *testing.T, config *ConfigEpochNotifierPerBlock) notifierPerBlockTestData { + t.Helper() + if config == nil { + config = &ConfigEpochNotifierPerBlock{ + StartingEpochBlock: 0, + NumBlockPerEpoch: 10, + EpochNotificationPercentage: 50, + } + } + blockNotifierMock := mocks.NewBlockNotifier(t) + logger := log.WithFields("test", "EpochNotifierPerBlock") + sut, err := NewEpochNotifierPerBlock(blockNotifierMock, logger, *config, nil) + require.NoError(t, err) + return notifierPerBlockTestData{ + sut: sut, + blockNotifierMock: blockNotifierMock, + ctx: context.TODO(), + } +} diff --git a/aggsender/generic_subscriber_impl.go b/aggsender/generic_subscriber_impl.go new file mode 100644 index 00000000..e4251449 --- /dev/null +++ b/aggsender/generic_subscriber_impl.go @@ -0,0 +1,33 @@ +package aggsender + +import "sync" + +type GenericSubscriberImpl[T any] struct { + // map of subscribers with names + subs map[chan T]string + mu sync.RWMutex +} + +func NewGenericSubscriberImpl[T any]() *GenericSubscriberImpl[T] { + return &GenericSubscriberImpl[T]{ + subs: make(map[chan T]string), + } +} + +func (g *GenericSubscriberImpl[T]) Subscribe(subscriberName string) <-chan T { + ch := make(chan T) + g.mu.Lock() + defer g.mu.Unlock() + g.subs[ch] = subscriberName + return ch +} + +func (g *GenericSubscriberImpl[T]) Publish(data T) { + g.mu.RLock() + defer g.mu.RUnlock() + for ch := range g.subs { + go func(ch chan T) { + ch <- data + }(ch) + } +} diff --git a/aggsender/mocks/agg_sender_storage.go b/aggsender/mocks/agg_sender_storage.go new file mode 100644 index 00000000..1816d4a3 --- /dev/null +++ b/aggsender/mocks/agg_sender_storage.go @@ -0,0 +1,351 @@ +// Code generated by mockery. DO NOT EDIT. + +package mocks + +import ( + agglayer "github.com/0xPolygon/cdk/agglayer" + common "github.com/ethereum/go-ethereum/common" + + context "context" + + mock "github.com/stretchr/testify/mock" + + types "github.com/0xPolygon/cdk/aggsender/types" +) + +// AggSenderStorage is an autogenerated mock type for the AggSenderStorage type +type AggSenderStorage struct { + mock.Mock +} + +type AggSenderStorage_Expecter struct { + mock *mock.Mock +} + +func (_m *AggSenderStorage) EXPECT() *AggSenderStorage_Expecter { + return &AggSenderStorage_Expecter{mock: &_m.Mock} +} + +// DeleteCertificate provides a mock function with given fields: ctx, certificateID +func (_m *AggSenderStorage) DeleteCertificate(ctx context.Context, certificateID common.Hash) error { + ret := _m.Called(ctx, certificateID) + + if len(ret) == 0 { + panic("no return value specified for DeleteCertificate") + } + + var r0 error + if rf, ok := ret.Get(0).(func(context.Context, common.Hash) error); ok { + r0 = rf(ctx, certificateID) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// AggSenderStorage_DeleteCertificate_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'DeleteCertificate' +type AggSenderStorage_DeleteCertificate_Call struct { + *mock.Call +} + +// DeleteCertificate is a helper method to define mock.On call +// - ctx context.Context +// - certificateID common.Hash +func (_e *AggSenderStorage_Expecter) DeleteCertificate(ctx interface{}, certificateID interface{}) *AggSenderStorage_DeleteCertificate_Call { + return &AggSenderStorage_DeleteCertificate_Call{Call: _e.mock.On("DeleteCertificate", ctx, certificateID)} +} + +func (_c *AggSenderStorage_DeleteCertificate_Call) Run(run func(ctx context.Context, certificateID common.Hash)) *AggSenderStorage_DeleteCertificate_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context), args[1].(common.Hash)) + }) + return _c +} + +func (_c *AggSenderStorage_DeleteCertificate_Call) Return(_a0 error) *AggSenderStorage_DeleteCertificate_Call { + _c.Call.Return(_a0) + return _c +} + +func (_c *AggSenderStorage_DeleteCertificate_Call) RunAndReturn(run func(context.Context, common.Hash) error) *AggSenderStorage_DeleteCertificate_Call { + _c.Call.Return(run) + return _c +} + +// GetCertificateByHeight provides a mock function with given fields: height +func (_m *AggSenderStorage) GetCertificateByHeight(height uint64) (types.CertificateInfo, error) { + ret := _m.Called(height) + + if len(ret) == 0 { + panic("no return value specified for GetCertificateByHeight") + } + + var r0 types.CertificateInfo + var r1 error + if rf, ok := ret.Get(0).(func(uint64) (types.CertificateInfo, error)); ok { + return rf(height) + } + if rf, ok := ret.Get(0).(func(uint64) types.CertificateInfo); ok { + r0 = rf(height) + } else { + r0 = ret.Get(0).(types.CertificateInfo) + } + + if rf, ok := ret.Get(1).(func(uint64) error); ok { + r1 = rf(height) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// AggSenderStorage_GetCertificateByHeight_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GetCertificateByHeight' +type AggSenderStorage_GetCertificateByHeight_Call struct { + *mock.Call +} + +// GetCertificateByHeight is a helper method to define mock.On call +// - height uint64 +func (_e *AggSenderStorage_Expecter) GetCertificateByHeight(height interface{}) *AggSenderStorage_GetCertificateByHeight_Call { + return &AggSenderStorage_GetCertificateByHeight_Call{Call: _e.mock.On("GetCertificateByHeight", height)} +} + +func (_c *AggSenderStorage_GetCertificateByHeight_Call) Run(run func(height uint64)) *AggSenderStorage_GetCertificateByHeight_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(uint64)) + }) + return _c +} + +func (_c *AggSenderStorage_GetCertificateByHeight_Call) Return(_a0 types.CertificateInfo, _a1 error) *AggSenderStorage_GetCertificateByHeight_Call { + _c.Call.Return(_a0, _a1) + return _c +} + +func (_c *AggSenderStorage_GetCertificateByHeight_Call) RunAndReturn(run func(uint64) (types.CertificateInfo, error)) *AggSenderStorage_GetCertificateByHeight_Call { + _c.Call.Return(run) + return _c +} + +// GetCertificatesByStatus provides a mock function with given fields: status +func (_m *AggSenderStorage) GetCertificatesByStatus(status []agglayer.CertificateStatus) ([]*types.CertificateInfo, error) { + ret := _m.Called(status) + + if len(ret) == 0 { + panic("no return value specified for GetCertificatesByStatus") + } + + var r0 []*types.CertificateInfo + var r1 error + if rf, ok := ret.Get(0).(func([]agglayer.CertificateStatus) ([]*types.CertificateInfo, error)); ok { + return rf(status) + } + if rf, ok := ret.Get(0).(func([]agglayer.CertificateStatus) []*types.CertificateInfo); ok { + r0 = rf(status) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).([]*types.CertificateInfo) + } + } + + if rf, ok := ret.Get(1).(func([]agglayer.CertificateStatus) error); ok { + r1 = rf(status) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// AggSenderStorage_GetCertificatesByStatus_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GetCertificatesByStatus' +type AggSenderStorage_GetCertificatesByStatus_Call struct { + *mock.Call +} + +// GetCertificatesByStatus is a helper method to define mock.On call +// - status []agglayer.CertificateStatus +func (_e *AggSenderStorage_Expecter) GetCertificatesByStatus(status interface{}) *AggSenderStorage_GetCertificatesByStatus_Call { + return &AggSenderStorage_GetCertificatesByStatus_Call{Call: _e.mock.On("GetCertificatesByStatus", status)} +} + +func (_c *AggSenderStorage_GetCertificatesByStatus_Call) Run(run func(status []agglayer.CertificateStatus)) *AggSenderStorage_GetCertificatesByStatus_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].([]agglayer.CertificateStatus)) + }) + return _c +} + +func (_c *AggSenderStorage_GetCertificatesByStatus_Call) Return(_a0 []*types.CertificateInfo, _a1 error) *AggSenderStorage_GetCertificatesByStatus_Call { + _c.Call.Return(_a0, _a1) + return _c +} + +func (_c *AggSenderStorage_GetCertificatesByStatus_Call) RunAndReturn(run func([]agglayer.CertificateStatus) ([]*types.CertificateInfo, error)) *AggSenderStorage_GetCertificatesByStatus_Call { + _c.Call.Return(run) + return _c +} + +// GetLastSentCertificate provides a mock function with given fields: +func (_m *AggSenderStorage) GetLastSentCertificate() (types.CertificateInfo, error) { + ret := _m.Called() + + if len(ret) == 0 { + panic("no return value specified for GetLastSentCertificate") + } + + var r0 types.CertificateInfo + var r1 error + if rf, ok := ret.Get(0).(func() (types.CertificateInfo, error)); ok { + return rf() + } + if rf, ok := ret.Get(0).(func() types.CertificateInfo); ok { + r0 = rf() + } else { + r0 = ret.Get(0).(types.CertificateInfo) + } + + if rf, ok := ret.Get(1).(func() error); ok { + r1 = rf() + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// AggSenderStorage_GetLastSentCertificate_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GetLastSentCertificate' +type AggSenderStorage_GetLastSentCertificate_Call struct { + *mock.Call +} + +// GetLastSentCertificate is a helper method to define mock.On call +func (_e *AggSenderStorage_Expecter) GetLastSentCertificate() *AggSenderStorage_GetLastSentCertificate_Call { + return &AggSenderStorage_GetLastSentCertificate_Call{Call: _e.mock.On("GetLastSentCertificate")} +} + +func (_c *AggSenderStorage_GetLastSentCertificate_Call) Run(run func()) *AggSenderStorage_GetLastSentCertificate_Call { + _c.Call.Run(func(args mock.Arguments) { + run() + }) + return _c +} + +func (_c *AggSenderStorage_GetLastSentCertificate_Call) Return(_a0 types.CertificateInfo, _a1 error) *AggSenderStorage_GetLastSentCertificate_Call { + _c.Call.Return(_a0, _a1) + return _c +} + +func (_c *AggSenderStorage_GetLastSentCertificate_Call) RunAndReturn(run func() (types.CertificateInfo, error)) *AggSenderStorage_GetLastSentCertificate_Call { + _c.Call.Return(run) + return _c +} + +// SaveLastSentCertificate provides a mock function with given fields: ctx, certificate +func (_m *AggSenderStorage) SaveLastSentCertificate(ctx context.Context, certificate types.CertificateInfo) error { + ret := _m.Called(ctx, certificate) + + if len(ret) == 0 { + panic("no return value specified for SaveLastSentCertificate") + } + + var r0 error + if rf, ok := ret.Get(0).(func(context.Context, types.CertificateInfo) error); ok { + r0 = rf(ctx, certificate) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// AggSenderStorage_SaveLastSentCertificate_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'SaveLastSentCertificate' +type AggSenderStorage_SaveLastSentCertificate_Call struct { + *mock.Call +} + +// SaveLastSentCertificate is a helper method to define mock.On call +// - ctx context.Context +// - certificate types.CertificateInfo +func (_e *AggSenderStorage_Expecter) SaveLastSentCertificate(ctx interface{}, certificate interface{}) *AggSenderStorage_SaveLastSentCertificate_Call { + return &AggSenderStorage_SaveLastSentCertificate_Call{Call: _e.mock.On("SaveLastSentCertificate", ctx, certificate)} +} + +func (_c *AggSenderStorage_SaveLastSentCertificate_Call) Run(run func(ctx context.Context, certificate types.CertificateInfo)) *AggSenderStorage_SaveLastSentCertificate_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context), args[1].(types.CertificateInfo)) + }) + return _c +} + +func (_c *AggSenderStorage_SaveLastSentCertificate_Call) Return(_a0 error) *AggSenderStorage_SaveLastSentCertificate_Call { + _c.Call.Return(_a0) + return _c +} + +func (_c *AggSenderStorage_SaveLastSentCertificate_Call) RunAndReturn(run func(context.Context, types.CertificateInfo) error) *AggSenderStorage_SaveLastSentCertificate_Call { + _c.Call.Return(run) + return _c +} + +// UpdateCertificateStatus provides a mock function with given fields: ctx, certificate +func (_m *AggSenderStorage) UpdateCertificateStatus(ctx context.Context, certificate types.CertificateInfo) error { + ret := _m.Called(ctx, certificate) + + if len(ret) == 0 { + panic("no return value specified for UpdateCertificateStatus") + } + + var r0 error + if rf, ok := ret.Get(0).(func(context.Context, types.CertificateInfo) error); ok { + r0 = rf(ctx, certificate) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// AggSenderStorage_UpdateCertificateStatus_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'UpdateCertificateStatus' +type AggSenderStorage_UpdateCertificateStatus_Call struct { + *mock.Call +} + +// UpdateCertificateStatus is a helper method to define mock.On call +// - ctx context.Context +// - certificate types.CertificateInfo +func (_e *AggSenderStorage_Expecter) UpdateCertificateStatus(ctx interface{}, certificate interface{}) *AggSenderStorage_UpdateCertificateStatus_Call { + return &AggSenderStorage_UpdateCertificateStatus_Call{Call: _e.mock.On("UpdateCertificateStatus", ctx, certificate)} +} + +func (_c *AggSenderStorage_UpdateCertificateStatus_Call) Run(run func(ctx context.Context, certificate types.CertificateInfo)) *AggSenderStorage_UpdateCertificateStatus_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context), args[1].(types.CertificateInfo)) + }) + return _c +} + +func (_c *AggSenderStorage_UpdateCertificateStatus_Call) Return(_a0 error) *AggSenderStorage_UpdateCertificateStatus_Call { + _c.Call.Return(_a0) + return _c +} + +func (_c *AggSenderStorage_UpdateCertificateStatus_Call) RunAndReturn(run func(context.Context, types.CertificateInfo) error) *AggSenderStorage_UpdateCertificateStatus_Call { + _c.Call.Return(run) + return _c +} + +// NewAggSenderStorage creates a new instance of AggSenderStorage. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. +// The first argument is typically a *testing.T value. +func NewAggSenderStorage(t interface { + mock.TestingT + Cleanup(func()) +}) *AggSenderStorage { + mock := &AggSenderStorage{} + mock.Mock.Test(t) + + t.Cleanup(func() { mock.AssertExpectations(t) }) + + return mock +} diff --git a/aggsender/mocks/block_notifier.go b/aggsender/mocks/block_notifier.go new file mode 100644 index 00000000..f8fc556d --- /dev/null +++ b/aggsender/mocks/block_notifier.go @@ -0,0 +1,128 @@ +// Code generated by mockery. DO NOT EDIT. + +package mocks + +import ( + types "github.com/0xPolygon/cdk/aggsender/types" + mock "github.com/stretchr/testify/mock" +) + +// BlockNotifier is an autogenerated mock type for the BlockNotifier type +type BlockNotifier struct { + mock.Mock +} + +type BlockNotifier_Expecter struct { + mock *mock.Mock +} + +func (_m *BlockNotifier) EXPECT() *BlockNotifier_Expecter { + return &BlockNotifier_Expecter{mock: &_m.Mock} +} + +// String provides a mock function with given fields: +func (_m *BlockNotifier) String() string { + ret := _m.Called() + + if len(ret) == 0 { + panic("no return value specified for String") + } + + var r0 string + if rf, ok := ret.Get(0).(func() string); ok { + r0 = rf() + } else { + r0 = ret.Get(0).(string) + } + + return r0 +} + +// BlockNotifier_String_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'String' +type BlockNotifier_String_Call struct { + *mock.Call +} + +// String is a helper method to define mock.On call +func (_e *BlockNotifier_Expecter) String() *BlockNotifier_String_Call { + return &BlockNotifier_String_Call{Call: _e.mock.On("String")} +} + +func (_c *BlockNotifier_String_Call) Run(run func()) *BlockNotifier_String_Call { + _c.Call.Run(func(args mock.Arguments) { + run() + }) + return _c +} + +func (_c *BlockNotifier_String_Call) Return(_a0 string) *BlockNotifier_String_Call { + _c.Call.Return(_a0) + return _c +} + +func (_c *BlockNotifier_String_Call) RunAndReturn(run func() string) *BlockNotifier_String_Call { + _c.Call.Return(run) + return _c +} + +// Subscribe provides a mock function with given fields: id +func (_m *BlockNotifier) Subscribe(id string) <-chan types.EventNewBlock { + ret := _m.Called(id) + + if len(ret) == 0 { + panic("no return value specified for Subscribe") + } + + var r0 <-chan types.EventNewBlock + if rf, ok := ret.Get(0).(func(string) <-chan types.EventNewBlock); ok { + r0 = rf(id) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(<-chan types.EventNewBlock) + } + } + + return r0 +} + +// BlockNotifier_Subscribe_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Subscribe' +type BlockNotifier_Subscribe_Call struct { + *mock.Call +} + +// Subscribe is a helper method to define mock.On call +// - id string +func (_e *BlockNotifier_Expecter) Subscribe(id interface{}) *BlockNotifier_Subscribe_Call { + return &BlockNotifier_Subscribe_Call{Call: _e.mock.On("Subscribe", id)} +} + +func (_c *BlockNotifier_Subscribe_Call) Run(run func(id string)) *BlockNotifier_Subscribe_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(string)) + }) + return _c +} + +func (_c *BlockNotifier_Subscribe_Call) Return(_a0 <-chan types.EventNewBlock) *BlockNotifier_Subscribe_Call { + _c.Call.Return(_a0) + return _c +} + +func (_c *BlockNotifier_Subscribe_Call) RunAndReturn(run func(string) <-chan types.EventNewBlock) *BlockNotifier_Subscribe_Call { + _c.Call.Return(run) + return _c +} + +// NewBlockNotifier creates a new instance of BlockNotifier. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. +// The first argument is typically a *testing.T value. +func NewBlockNotifier(t interface { + mock.TestingT + Cleanup(func()) +}) *BlockNotifier { + mock := &BlockNotifier{} + mock.Mock.Test(t) + + t.Cleanup(func() { mock.AssertExpectations(t) }) + + return mock +} diff --git a/aggsender/mocks/epoch_notifier.go b/aggsender/mocks/epoch_notifier.go new file mode 100644 index 00000000..fb8bf35f --- /dev/null +++ b/aggsender/mocks/epoch_notifier.go @@ -0,0 +1,163 @@ +// Code generated by mockery. DO NOT EDIT. + +package mocks + +import ( + context "context" + + types "github.com/0xPolygon/cdk/aggsender/types" + mock "github.com/stretchr/testify/mock" +) + +// EpochNotifier is an autogenerated mock type for the EpochNotifier type +type EpochNotifier struct { + mock.Mock +} + +type EpochNotifier_Expecter struct { + mock *mock.Mock +} + +func (_m *EpochNotifier) EXPECT() *EpochNotifier_Expecter { + return &EpochNotifier_Expecter{mock: &_m.Mock} +} + +// Start provides a mock function with given fields: ctx +func (_m *EpochNotifier) Start(ctx context.Context) { + _m.Called(ctx) +} + +// EpochNotifier_Start_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Start' +type EpochNotifier_Start_Call struct { + *mock.Call +} + +// Start is a helper method to define mock.On call +// - ctx context.Context +func (_e *EpochNotifier_Expecter) Start(ctx interface{}) *EpochNotifier_Start_Call { + return &EpochNotifier_Start_Call{Call: _e.mock.On("Start", ctx)} +} + +func (_c *EpochNotifier_Start_Call) Run(run func(ctx context.Context)) *EpochNotifier_Start_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context)) + }) + return _c +} + +func (_c *EpochNotifier_Start_Call) Return() *EpochNotifier_Start_Call { + _c.Call.Return() + return _c +} + +func (_c *EpochNotifier_Start_Call) RunAndReturn(run func(context.Context)) *EpochNotifier_Start_Call { + _c.Call.Return(run) + return _c +} + +// String provides a mock function with given fields: +func (_m *EpochNotifier) String() string { + ret := _m.Called() + + if len(ret) == 0 { + panic("no return value specified for String") + } + + var r0 string + if rf, ok := ret.Get(0).(func() string); ok { + r0 = rf() + } else { + r0 = ret.Get(0).(string) + } + + return r0 +} + +// EpochNotifier_String_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'String' +type EpochNotifier_String_Call struct { + *mock.Call +} + +// String is a helper method to define mock.On call +func (_e *EpochNotifier_Expecter) String() *EpochNotifier_String_Call { + return &EpochNotifier_String_Call{Call: _e.mock.On("String")} +} + +func (_c *EpochNotifier_String_Call) Run(run func()) *EpochNotifier_String_Call { + _c.Call.Run(func(args mock.Arguments) { + run() + }) + return _c +} + +func (_c *EpochNotifier_String_Call) Return(_a0 string) *EpochNotifier_String_Call { + _c.Call.Return(_a0) + return _c +} + +func (_c *EpochNotifier_String_Call) RunAndReturn(run func() string) *EpochNotifier_String_Call { + _c.Call.Return(run) + return _c +} + +// Subscribe provides a mock function with given fields: id +func (_m *EpochNotifier) Subscribe(id string) <-chan types.EpochEvent { + ret := _m.Called(id) + + if len(ret) == 0 { + panic("no return value specified for Subscribe") + } + + var r0 <-chan types.EpochEvent + if rf, ok := ret.Get(0).(func(string) <-chan types.EpochEvent); ok { + r0 = rf(id) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(<-chan types.EpochEvent) + } + } + + return r0 +} + +// EpochNotifier_Subscribe_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Subscribe' +type EpochNotifier_Subscribe_Call struct { + *mock.Call +} + +// Subscribe is a helper method to define mock.On call +// - id string +func (_e *EpochNotifier_Expecter) Subscribe(id interface{}) *EpochNotifier_Subscribe_Call { + return &EpochNotifier_Subscribe_Call{Call: _e.mock.On("Subscribe", id)} +} + +func (_c *EpochNotifier_Subscribe_Call) Run(run func(id string)) *EpochNotifier_Subscribe_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(string)) + }) + return _c +} + +func (_c *EpochNotifier_Subscribe_Call) Return(_a0 <-chan types.EpochEvent) *EpochNotifier_Subscribe_Call { + _c.Call.Return(_a0) + return _c +} + +func (_c *EpochNotifier_Subscribe_Call) RunAndReturn(run func(string) <-chan types.EpochEvent) *EpochNotifier_Subscribe_Call { + _c.Call.Return(run) + return _c +} + +// NewEpochNotifier creates a new instance of EpochNotifier. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. +// The first argument is typically a *testing.T value. +func NewEpochNotifier(t interface { + mock.TestingT + Cleanup(func()) +}) *EpochNotifier { + mock := &EpochNotifier{} + mock.Mock.Test(t) + + t.Cleanup(func() { mock.AssertExpectations(t) }) + + return mock +} diff --git a/aggsender/mocks/mock_eth_client.go b/aggsender/mocks/eth_client.go similarity index 50% rename from aggsender/mocks/mock_eth_client.go rename to aggsender/mocks/eth_client.go index ebf618bf..6a68de41 100644 --- a/aggsender/mocks/mock_eth_client.go +++ b/aggsender/mocks/eth_client.go @@ -11,21 +11,21 @@ import ( mock "github.com/stretchr/testify/mock" ) -// EthClientMock is an autogenerated mock type for the EthClient type -type EthClientMock struct { +// EthClient is an autogenerated mock type for the EthClient type +type EthClient struct { mock.Mock } -type EthClientMock_Expecter struct { +type EthClient_Expecter struct { mock *mock.Mock } -func (_m *EthClientMock) EXPECT() *EthClientMock_Expecter { - return &EthClientMock_Expecter{mock: &_m.Mock} +func (_m *EthClient) EXPECT() *EthClient_Expecter { + return &EthClient_Expecter{mock: &_m.Mock} } // BlockNumber provides a mock function with given fields: ctx -func (_m *EthClientMock) BlockNumber(ctx context.Context) (uint64, error) { +func (_m *EthClient) BlockNumber(ctx context.Context) (uint64, error) { ret := _m.Called(ctx) if len(ret) == 0 { @@ -52,36 +52,36 @@ func (_m *EthClientMock) BlockNumber(ctx context.Context) (uint64, error) { return r0, r1 } -// EthClientMock_BlockNumber_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'BlockNumber' -type EthClientMock_BlockNumber_Call struct { +// EthClient_BlockNumber_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'BlockNumber' +type EthClient_BlockNumber_Call struct { *mock.Call } // BlockNumber is a helper method to define mock.On call // - ctx context.Context -func (_e *EthClientMock_Expecter) BlockNumber(ctx interface{}) *EthClientMock_BlockNumber_Call { - return &EthClientMock_BlockNumber_Call{Call: _e.mock.On("BlockNumber", ctx)} +func (_e *EthClient_Expecter) BlockNumber(ctx interface{}) *EthClient_BlockNumber_Call { + return &EthClient_BlockNumber_Call{Call: _e.mock.On("BlockNumber", ctx)} } -func (_c *EthClientMock_BlockNumber_Call) Run(run func(ctx context.Context)) *EthClientMock_BlockNumber_Call { +func (_c *EthClient_BlockNumber_Call) Run(run func(ctx context.Context)) *EthClient_BlockNumber_Call { _c.Call.Run(func(args mock.Arguments) { run(args[0].(context.Context)) }) return _c } -func (_c *EthClientMock_BlockNumber_Call) Return(_a0 uint64, _a1 error) *EthClientMock_BlockNumber_Call { +func (_c *EthClient_BlockNumber_Call) Return(_a0 uint64, _a1 error) *EthClient_BlockNumber_Call { _c.Call.Return(_a0, _a1) return _c } -func (_c *EthClientMock_BlockNumber_Call) RunAndReturn(run func(context.Context) (uint64, error)) *EthClientMock_BlockNumber_Call { +func (_c *EthClient_BlockNumber_Call) RunAndReturn(run func(context.Context) (uint64, error)) *EthClient_BlockNumber_Call { _c.Call.Return(run) return _c } // HeaderByNumber provides a mock function with given fields: ctx, number -func (_m *EthClientMock) HeaderByNumber(ctx context.Context, number *big.Int) (*coretypes.Header, error) { +func (_m *EthClient) HeaderByNumber(ctx context.Context, number *big.Int) (*coretypes.Header, error) { ret := _m.Called(ctx, number) if len(ret) == 0 { @@ -110,42 +110,42 @@ func (_m *EthClientMock) HeaderByNumber(ctx context.Context, number *big.Int) (* return r0, r1 } -// EthClientMock_HeaderByNumber_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'HeaderByNumber' -type EthClientMock_HeaderByNumber_Call struct { +// EthClient_HeaderByNumber_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'HeaderByNumber' +type EthClient_HeaderByNumber_Call struct { *mock.Call } // HeaderByNumber is a helper method to define mock.On call // - ctx context.Context // - number *big.Int -func (_e *EthClientMock_Expecter) HeaderByNumber(ctx interface{}, number interface{}) *EthClientMock_HeaderByNumber_Call { - return &EthClientMock_HeaderByNumber_Call{Call: _e.mock.On("HeaderByNumber", ctx, number)} +func (_e *EthClient_Expecter) HeaderByNumber(ctx interface{}, number interface{}) *EthClient_HeaderByNumber_Call { + return &EthClient_HeaderByNumber_Call{Call: _e.mock.On("HeaderByNumber", ctx, number)} } -func (_c *EthClientMock_HeaderByNumber_Call) Run(run func(ctx context.Context, number *big.Int)) *EthClientMock_HeaderByNumber_Call { +func (_c *EthClient_HeaderByNumber_Call) Run(run func(ctx context.Context, number *big.Int)) *EthClient_HeaderByNumber_Call { _c.Call.Run(func(args mock.Arguments) { run(args[0].(context.Context), args[1].(*big.Int)) }) return _c } -func (_c *EthClientMock_HeaderByNumber_Call) Return(_a0 *coretypes.Header, _a1 error) *EthClientMock_HeaderByNumber_Call { +func (_c *EthClient_HeaderByNumber_Call) Return(_a0 *coretypes.Header, _a1 error) *EthClient_HeaderByNumber_Call { _c.Call.Return(_a0, _a1) return _c } -func (_c *EthClientMock_HeaderByNumber_Call) RunAndReturn(run func(context.Context, *big.Int) (*coretypes.Header, error)) *EthClientMock_HeaderByNumber_Call { +func (_c *EthClient_HeaderByNumber_Call) RunAndReturn(run func(context.Context, *big.Int) (*coretypes.Header, error)) *EthClient_HeaderByNumber_Call { _c.Call.Return(run) return _c } -// NewEthClientMock creates a new instance of EthClientMock. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. +// NewEthClient creates a new instance of EthClient. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. // The first argument is typically a *testing.T value. -func NewEthClientMock(t interface { +func NewEthClient(t interface { mock.TestingT Cleanup(func()) -}) *EthClientMock { - mock := &EthClientMock{} +}) *EthClient { + mock := &EthClient{} mock.Mock.Test(t) t.Cleanup(func() { mock.AssertExpectations(t) }) diff --git a/aggsender/mocks/generic_subscriber.go b/aggsender/mocks/generic_subscriber.go new file mode 100644 index 00000000..b4bee4b4 --- /dev/null +++ b/aggsender/mocks/generic_subscriber.go @@ -0,0 +1,113 @@ +// Code generated by mockery. DO NOT EDIT. + +package mocks + +import mock "github.com/stretchr/testify/mock" + +// GenericSubscriber is an autogenerated mock type for the GenericSubscriber type +type GenericSubscriber[T interface{}] struct { + mock.Mock +} + +type GenericSubscriber_Expecter[T interface{}] struct { + mock *mock.Mock +} + +func (_m *GenericSubscriber[T]) EXPECT() *GenericSubscriber_Expecter[T] { + return &GenericSubscriber_Expecter[T]{mock: &_m.Mock} +} + +// Publish provides a mock function with given fields: data +func (_m *GenericSubscriber[T]) Publish(data T) { + _m.Called(data) +} + +// GenericSubscriber_Publish_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Publish' +type GenericSubscriber_Publish_Call[T interface{}] struct { + *mock.Call +} + +// Publish is a helper method to define mock.On call +// - data T +func (_e *GenericSubscriber_Expecter[T]) Publish(data interface{}) *GenericSubscriber_Publish_Call[T] { + return &GenericSubscriber_Publish_Call[T]{Call: _e.mock.On("Publish", data)} +} + +func (_c *GenericSubscriber_Publish_Call[T]) Run(run func(data T)) *GenericSubscriber_Publish_Call[T] { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(T)) + }) + return _c +} + +func (_c *GenericSubscriber_Publish_Call[T]) Return() *GenericSubscriber_Publish_Call[T] { + _c.Call.Return() + return _c +} + +func (_c *GenericSubscriber_Publish_Call[T]) RunAndReturn(run func(T)) *GenericSubscriber_Publish_Call[T] { + _c.Call.Return(run) + return _c +} + +// Subscribe provides a mock function with given fields: subscriberName +func (_m *GenericSubscriber[T]) Subscribe(subscriberName string) <-chan T { + ret := _m.Called(subscriberName) + + if len(ret) == 0 { + panic("no return value specified for Subscribe") + } + + var r0 <-chan T + if rf, ok := ret.Get(0).(func(string) <-chan T); ok { + r0 = rf(subscriberName) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(<-chan T) + } + } + + return r0 +} + +// GenericSubscriber_Subscribe_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Subscribe' +type GenericSubscriber_Subscribe_Call[T interface{}] struct { + *mock.Call +} + +// Subscribe is a helper method to define mock.On call +// - subscriberName string +func (_e *GenericSubscriber_Expecter[T]) Subscribe(subscriberName interface{}) *GenericSubscriber_Subscribe_Call[T] { + return &GenericSubscriber_Subscribe_Call[T]{Call: _e.mock.On("Subscribe", subscriberName)} +} + +func (_c *GenericSubscriber_Subscribe_Call[T]) Run(run func(subscriberName string)) *GenericSubscriber_Subscribe_Call[T] { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(string)) + }) + return _c +} + +func (_c *GenericSubscriber_Subscribe_Call[T]) Return(_a0 <-chan T) *GenericSubscriber_Subscribe_Call[T] { + _c.Call.Return(_a0) + return _c +} + +func (_c *GenericSubscriber_Subscribe_Call[T]) RunAndReturn(run func(string) <-chan T) *GenericSubscriber_Subscribe_Call[T] { + _c.Call.Return(run) + return _c +} + +// NewGenericSubscriber creates a new instance of GenericSubscriber. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. +// The first argument is typically a *testing.T value. +func NewGenericSubscriber[T interface{}](t interface { + mock.TestingT + Cleanup(func()) +}) *GenericSubscriber[T] { + mock := &GenericSubscriber[T]{} + mock.Mock.Test(t) + + t.Cleanup(func() { mock.AssertExpectations(t) }) + + return mock +} diff --git a/aggsender/mocks/l1_info_tree_syncer.go b/aggsender/mocks/l1_info_tree_syncer.go new file mode 100644 index 00000000..70ac97de --- /dev/null +++ b/aggsender/mocks/l1_info_tree_syncer.go @@ -0,0 +1,217 @@ +// Code generated by mockery. DO NOT EDIT. + +package mocks + +import ( + context "context" + + common "github.com/ethereum/go-ethereum/common" + + l1infotreesync "github.com/0xPolygon/cdk/l1infotreesync" + + mock "github.com/stretchr/testify/mock" + + treetypes "github.com/0xPolygon/cdk/tree/types" +) + +// L1InfoTreeSyncer is an autogenerated mock type for the L1InfoTreeSyncer type +type L1InfoTreeSyncer struct { + mock.Mock +} + +type L1InfoTreeSyncer_Expecter struct { + mock *mock.Mock +} + +func (_m *L1InfoTreeSyncer) EXPECT() *L1InfoTreeSyncer_Expecter { + return &L1InfoTreeSyncer_Expecter{mock: &_m.Mock} +} + +// GetInfoByGlobalExitRoot provides a mock function with given fields: globalExitRoot +func (_m *L1InfoTreeSyncer) GetInfoByGlobalExitRoot(globalExitRoot common.Hash) (*l1infotreesync.L1InfoTreeLeaf, error) { + ret := _m.Called(globalExitRoot) + + if len(ret) == 0 { + panic("no return value specified for GetInfoByGlobalExitRoot") + } + + var r0 *l1infotreesync.L1InfoTreeLeaf + var r1 error + if rf, ok := ret.Get(0).(func(common.Hash) (*l1infotreesync.L1InfoTreeLeaf, error)); ok { + return rf(globalExitRoot) + } + if rf, ok := ret.Get(0).(func(common.Hash) *l1infotreesync.L1InfoTreeLeaf); ok { + r0 = rf(globalExitRoot) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*l1infotreesync.L1InfoTreeLeaf) + } + } + + if rf, ok := ret.Get(1).(func(common.Hash) error); ok { + r1 = rf(globalExitRoot) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// L1InfoTreeSyncer_GetInfoByGlobalExitRoot_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GetInfoByGlobalExitRoot' +type L1InfoTreeSyncer_GetInfoByGlobalExitRoot_Call struct { + *mock.Call +} + +// GetInfoByGlobalExitRoot is a helper method to define mock.On call +// - globalExitRoot common.Hash +func (_e *L1InfoTreeSyncer_Expecter) GetInfoByGlobalExitRoot(globalExitRoot interface{}) *L1InfoTreeSyncer_GetInfoByGlobalExitRoot_Call { + return &L1InfoTreeSyncer_GetInfoByGlobalExitRoot_Call{Call: _e.mock.On("GetInfoByGlobalExitRoot", globalExitRoot)} +} + +func (_c *L1InfoTreeSyncer_GetInfoByGlobalExitRoot_Call) Run(run func(globalExitRoot common.Hash)) *L1InfoTreeSyncer_GetInfoByGlobalExitRoot_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(common.Hash)) + }) + return _c +} + +func (_c *L1InfoTreeSyncer_GetInfoByGlobalExitRoot_Call) Return(_a0 *l1infotreesync.L1InfoTreeLeaf, _a1 error) *L1InfoTreeSyncer_GetInfoByGlobalExitRoot_Call { + _c.Call.Return(_a0, _a1) + return _c +} + +func (_c *L1InfoTreeSyncer_GetInfoByGlobalExitRoot_Call) RunAndReturn(run func(common.Hash) (*l1infotreesync.L1InfoTreeLeaf, error)) *L1InfoTreeSyncer_GetInfoByGlobalExitRoot_Call { + _c.Call.Return(run) + return _c +} + +// GetL1InfoTreeMerkleProofFromIndexToRoot provides a mock function with given fields: ctx, index, root +func (_m *L1InfoTreeSyncer) GetL1InfoTreeMerkleProofFromIndexToRoot(ctx context.Context, index uint32, root common.Hash) (treetypes.Proof, error) { + ret := _m.Called(ctx, index, root) + + if len(ret) == 0 { + panic("no return value specified for GetL1InfoTreeMerkleProofFromIndexToRoot") + } + + var r0 treetypes.Proof + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, uint32, common.Hash) (treetypes.Proof, error)); ok { + return rf(ctx, index, root) + } + if rf, ok := ret.Get(0).(func(context.Context, uint32, common.Hash) treetypes.Proof); ok { + r0 = rf(ctx, index, root) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(treetypes.Proof) + } + } + + if rf, ok := ret.Get(1).(func(context.Context, uint32, common.Hash) error); ok { + r1 = rf(ctx, index, root) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// L1InfoTreeSyncer_GetL1InfoTreeMerkleProofFromIndexToRoot_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GetL1InfoTreeMerkleProofFromIndexToRoot' +type L1InfoTreeSyncer_GetL1InfoTreeMerkleProofFromIndexToRoot_Call struct { + *mock.Call +} + +// GetL1InfoTreeMerkleProofFromIndexToRoot is a helper method to define mock.On call +// - ctx context.Context +// - index uint32 +// - root common.Hash +func (_e *L1InfoTreeSyncer_Expecter) GetL1InfoTreeMerkleProofFromIndexToRoot(ctx interface{}, index interface{}, root interface{}) *L1InfoTreeSyncer_GetL1InfoTreeMerkleProofFromIndexToRoot_Call { + return &L1InfoTreeSyncer_GetL1InfoTreeMerkleProofFromIndexToRoot_Call{Call: _e.mock.On("GetL1InfoTreeMerkleProofFromIndexToRoot", ctx, index, root)} +} + +func (_c *L1InfoTreeSyncer_GetL1InfoTreeMerkleProofFromIndexToRoot_Call) Run(run func(ctx context.Context, index uint32, root common.Hash)) *L1InfoTreeSyncer_GetL1InfoTreeMerkleProofFromIndexToRoot_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context), args[1].(uint32), args[2].(common.Hash)) + }) + return _c +} + +func (_c *L1InfoTreeSyncer_GetL1InfoTreeMerkleProofFromIndexToRoot_Call) Return(_a0 treetypes.Proof, _a1 error) *L1InfoTreeSyncer_GetL1InfoTreeMerkleProofFromIndexToRoot_Call { + _c.Call.Return(_a0, _a1) + return _c +} + +func (_c *L1InfoTreeSyncer_GetL1InfoTreeMerkleProofFromIndexToRoot_Call) RunAndReturn(run func(context.Context, uint32, common.Hash) (treetypes.Proof, error)) *L1InfoTreeSyncer_GetL1InfoTreeMerkleProofFromIndexToRoot_Call { + _c.Call.Return(run) + return _c +} + +// GetL1InfoTreeRootByIndex provides a mock function with given fields: ctx, index +func (_m *L1InfoTreeSyncer) GetL1InfoTreeRootByIndex(ctx context.Context, index uint32) (treetypes.Root, error) { + ret := _m.Called(ctx, index) + + if len(ret) == 0 { + panic("no return value specified for GetL1InfoTreeRootByIndex") + } + + var r0 treetypes.Root + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, uint32) (treetypes.Root, error)); ok { + return rf(ctx, index) + } + if rf, ok := ret.Get(0).(func(context.Context, uint32) treetypes.Root); ok { + r0 = rf(ctx, index) + } else { + r0 = ret.Get(0).(treetypes.Root) + } + + if rf, ok := ret.Get(1).(func(context.Context, uint32) error); ok { + r1 = rf(ctx, index) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// L1InfoTreeSyncer_GetL1InfoTreeRootByIndex_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GetL1InfoTreeRootByIndex' +type L1InfoTreeSyncer_GetL1InfoTreeRootByIndex_Call struct { + *mock.Call +} + +// GetL1InfoTreeRootByIndex is a helper method to define mock.On call +// - ctx context.Context +// - index uint32 +func (_e *L1InfoTreeSyncer_Expecter) GetL1InfoTreeRootByIndex(ctx interface{}, index interface{}) *L1InfoTreeSyncer_GetL1InfoTreeRootByIndex_Call { + return &L1InfoTreeSyncer_GetL1InfoTreeRootByIndex_Call{Call: _e.mock.On("GetL1InfoTreeRootByIndex", ctx, index)} +} + +func (_c *L1InfoTreeSyncer_GetL1InfoTreeRootByIndex_Call) Run(run func(ctx context.Context, index uint32)) *L1InfoTreeSyncer_GetL1InfoTreeRootByIndex_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context), args[1].(uint32)) + }) + return _c +} + +func (_c *L1InfoTreeSyncer_GetL1InfoTreeRootByIndex_Call) Return(_a0 treetypes.Root, _a1 error) *L1InfoTreeSyncer_GetL1InfoTreeRootByIndex_Call { + _c.Call.Return(_a0, _a1) + return _c +} + +func (_c *L1InfoTreeSyncer_GetL1InfoTreeRootByIndex_Call) RunAndReturn(run func(context.Context, uint32) (treetypes.Root, error)) *L1InfoTreeSyncer_GetL1InfoTreeRootByIndex_Call { + _c.Call.Return(run) + return _c +} + +// NewL1InfoTreeSyncer creates a new instance of L1InfoTreeSyncer. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. +// The first argument is typically a *testing.T value. +func NewL1InfoTreeSyncer(t interface { + mock.TestingT + Cleanup(func()) +}) *L1InfoTreeSyncer { + mock := &L1InfoTreeSyncer{} + mock.Mock.Test(t) + + t.Cleanup(func() { mock.AssertExpectations(t) }) + + return mock +} diff --git a/aggsender/mocks/l2_bridge_syncer.go b/aggsender/mocks/l2_bridge_syncer.go new file mode 100644 index 00000000..800007ff --- /dev/null +++ b/aggsender/mocks/l2_bridge_syncer.go @@ -0,0 +1,423 @@ +// Code generated by mockery. DO NOT EDIT. + +package mocks + +import ( + bridgesync "github.com/0xPolygon/cdk/bridgesync" + common "github.com/ethereum/go-ethereum/common" + + context "context" + + etherman "github.com/0xPolygon/cdk/etherman" + + mock "github.com/stretchr/testify/mock" + + treetypes "github.com/0xPolygon/cdk/tree/types" +) + +// L2BridgeSyncer is an autogenerated mock type for the L2BridgeSyncer type +type L2BridgeSyncer struct { + mock.Mock +} + +type L2BridgeSyncer_Expecter struct { + mock *mock.Mock +} + +func (_m *L2BridgeSyncer) EXPECT() *L2BridgeSyncer_Expecter { + return &L2BridgeSyncer_Expecter{mock: &_m.Mock} +} + +// BlockFinality provides a mock function with given fields: +func (_m *L2BridgeSyncer) BlockFinality() etherman.BlockNumberFinality { + ret := _m.Called() + + if len(ret) == 0 { + panic("no return value specified for BlockFinality") + } + + var r0 etherman.BlockNumberFinality + if rf, ok := ret.Get(0).(func() etherman.BlockNumberFinality); ok { + r0 = rf() + } else { + r0 = ret.Get(0).(etherman.BlockNumberFinality) + } + + return r0 +} + +// L2BridgeSyncer_BlockFinality_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'BlockFinality' +type L2BridgeSyncer_BlockFinality_Call struct { + *mock.Call +} + +// BlockFinality is a helper method to define mock.On call +func (_e *L2BridgeSyncer_Expecter) BlockFinality() *L2BridgeSyncer_BlockFinality_Call { + return &L2BridgeSyncer_BlockFinality_Call{Call: _e.mock.On("BlockFinality")} +} + +func (_c *L2BridgeSyncer_BlockFinality_Call) Run(run func()) *L2BridgeSyncer_BlockFinality_Call { + _c.Call.Run(func(args mock.Arguments) { + run() + }) + return _c +} + +func (_c *L2BridgeSyncer_BlockFinality_Call) Return(_a0 etherman.BlockNumberFinality) *L2BridgeSyncer_BlockFinality_Call { + _c.Call.Return(_a0) + return _c +} + +func (_c *L2BridgeSyncer_BlockFinality_Call) RunAndReturn(run func() etherman.BlockNumberFinality) *L2BridgeSyncer_BlockFinality_Call { + _c.Call.Return(run) + return _c +} + +// GetBlockByLER provides a mock function with given fields: ctx, ler +func (_m *L2BridgeSyncer) GetBlockByLER(ctx context.Context, ler common.Hash) (uint64, error) { + ret := _m.Called(ctx, ler) + + if len(ret) == 0 { + panic("no return value specified for GetBlockByLER") + } + + var r0 uint64 + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, common.Hash) (uint64, error)); ok { + return rf(ctx, ler) + } + if rf, ok := ret.Get(0).(func(context.Context, common.Hash) uint64); ok { + r0 = rf(ctx, ler) + } else { + r0 = ret.Get(0).(uint64) + } + + if rf, ok := ret.Get(1).(func(context.Context, common.Hash) error); ok { + r1 = rf(ctx, ler) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// L2BridgeSyncer_GetBlockByLER_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GetBlockByLER' +type L2BridgeSyncer_GetBlockByLER_Call struct { + *mock.Call +} + +// GetBlockByLER is a helper method to define mock.On call +// - ctx context.Context +// - ler common.Hash +func (_e *L2BridgeSyncer_Expecter) GetBlockByLER(ctx interface{}, ler interface{}) *L2BridgeSyncer_GetBlockByLER_Call { + return &L2BridgeSyncer_GetBlockByLER_Call{Call: _e.mock.On("GetBlockByLER", ctx, ler)} +} + +func (_c *L2BridgeSyncer_GetBlockByLER_Call) Run(run func(ctx context.Context, ler common.Hash)) *L2BridgeSyncer_GetBlockByLER_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context), args[1].(common.Hash)) + }) + return _c +} + +func (_c *L2BridgeSyncer_GetBlockByLER_Call) Return(_a0 uint64, _a1 error) *L2BridgeSyncer_GetBlockByLER_Call { + _c.Call.Return(_a0, _a1) + return _c +} + +func (_c *L2BridgeSyncer_GetBlockByLER_Call) RunAndReturn(run func(context.Context, common.Hash) (uint64, error)) *L2BridgeSyncer_GetBlockByLER_Call { + _c.Call.Return(run) + return _c +} + +// GetBridgesPublished provides a mock function with given fields: ctx, fromBlock, toBlock +func (_m *L2BridgeSyncer) GetBridgesPublished(ctx context.Context, fromBlock uint64, toBlock uint64) ([]bridgesync.Bridge, error) { + ret := _m.Called(ctx, fromBlock, toBlock) + + if len(ret) == 0 { + panic("no return value specified for GetBridgesPublished") + } + + var r0 []bridgesync.Bridge + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, uint64, uint64) ([]bridgesync.Bridge, error)); ok { + return rf(ctx, fromBlock, toBlock) + } + if rf, ok := ret.Get(0).(func(context.Context, uint64, uint64) []bridgesync.Bridge); ok { + r0 = rf(ctx, fromBlock, toBlock) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).([]bridgesync.Bridge) + } + } + + if rf, ok := ret.Get(1).(func(context.Context, uint64, uint64) error); ok { + r1 = rf(ctx, fromBlock, toBlock) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// L2BridgeSyncer_GetBridgesPublished_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GetBridgesPublished' +type L2BridgeSyncer_GetBridgesPublished_Call struct { + *mock.Call +} + +// GetBridgesPublished is a helper method to define mock.On call +// - ctx context.Context +// - fromBlock uint64 +// - toBlock uint64 +func (_e *L2BridgeSyncer_Expecter) GetBridgesPublished(ctx interface{}, fromBlock interface{}, toBlock interface{}) *L2BridgeSyncer_GetBridgesPublished_Call { + return &L2BridgeSyncer_GetBridgesPublished_Call{Call: _e.mock.On("GetBridgesPublished", ctx, fromBlock, toBlock)} +} + +func (_c *L2BridgeSyncer_GetBridgesPublished_Call) Run(run func(ctx context.Context, fromBlock uint64, toBlock uint64)) *L2BridgeSyncer_GetBridgesPublished_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context), args[1].(uint64), args[2].(uint64)) + }) + return _c +} + +func (_c *L2BridgeSyncer_GetBridgesPublished_Call) Return(_a0 []bridgesync.Bridge, _a1 error) *L2BridgeSyncer_GetBridgesPublished_Call { + _c.Call.Return(_a0, _a1) + return _c +} + +func (_c *L2BridgeSyncer_GetBridgesPublished_Call) RunAndReturn(run func(context.Context, uint64, uint64) ([]bridgesync.Bridge, error)) *L2BridgeSyncer_GetBridgesPublished_Call { + _c.Call.Return(run) + return _c +} + +// GetClaims provides a mock function with given fields: ctx, fromBlock, toBlock +func (_m *L2BridgeSyncer) GetClaims(ctx context.Context, fromBlock uint64, toBlock uint64) ([]bridgesync.Claim, error) { + ret := _m.Called(ctx, fromBlock, toBlock) + + if len(ret) == 0 { + panic("no return value specified for GetClaims") + } + + var r0 []bridgesync.Claim + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, uint64, uint64) ([]bridgesync.Claim, error)); ok { + return rf(ctx, fromBlock, toBlock) + } + if rf, ok := ret.Get(0).(func(context.Context, uint64, uint64) []bridgesync.Claim); ok { + r0 = rf(ctx, fromBlock, toBlock) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).([]bridgesync.Claim) + } + } + + if rf, ok := ret.Get(1).(func(context.Context, uint64, uint64) error); ok { + r1 = rf(ctx, fromBlock, toBlock) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// L2BridgeSyncer_GetClaims_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GetClaims' +type L2BridgeSyncer_GetClaims_Call struct { + *mock.Call +} + +// GetClaims is a helper method to define mock.On call +// - ctx context.Context +// - fromBlock uint64 +// - toBlock uint64 +func (_e *L2BridgeSyncer_Expecter) GetClaims(ctx interface{}, fromBlock interface{}, toBlock interface{}) *L2BridgeSyncer_GetClaims_Call { + return &L2BridgeSyncer_GetClaims_Call{Call: _e.mock.On("GetClaims", ctx, fromBlock, toBlock)} +} + +func (_c *L2BridgeSyncer_GetClaims_Call) Run(run func(ctx context.Context, fromBlock uint64, toBlock uint64)) *L2BridgeSyncer_GetClaims_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context), args[1].(uint64), args[2].(uint64)) + }) + return _c +} + +func (_c *L2BridgeSyncer_GetClaims_Call) Return(_a0 []bridgesync.Claim, _a1 error) *L2BridgeSyncer_GetClaims_Call { + _c.Call.Return(_a0, _a1) + return _c +} + +func (_c *L2BridgeSyncer_GetClaims_Call) RunAndReturn(run func(context.Context, uint64, uint64) ([]bridgesync.Claim, error)) *L2BridgeSyncer_GetClaims_Call { + _c.Call.Return(run) + return _c +} + +// GetExitRootByIndex provides a mock function with given fields: ctx, index +func (_m *L2BridgeSyncer) GetExitRootByIndex(ctx context.Context, index uint32) (treetypes.Root, error) { + ret := _m.Called(ctx, index) + + if len(ret) == 0 { + panic("no return value specified for GetExitRootByIndex") + } + + var r0 treetypes.Root + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, uint32) (treetypes.Root, error)); ok { + return rf(ctx, index) + } + if rf, ok := ret.Get(0).(func(context.Context, uint32) treetypes.Root); ok { + r0 = rf(ctx, index) + } else { + r0 = ret.Get(0).(treetypes.Root) + } + + if rf, ok := ret.Get(1).(func(context.Context, uint32) error); ok { + r1 = rf(ctx, index) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// L2BridgeSyncer_GetExitRootByIndex_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GetExitRootByIndex' +type L2BridgeSyncer_GetExitRootByIndex_Call struct { + *mock.Call +} + +// GetExitRootByIndex is a helper method to define mock.On call +// - ctx context.Context +// - index uint32 +func (_e *L2BridgeSyncer_Expecter) GetExitRootByIndex(ctx interface{}, index interface{}) *L2BridgeSyncer_GetExitRootByIndex_Call { + return &L2BridgeSyncer_GetExitRootByIndex_Call{Call: _e.mock.On("GetExitRootByIndex", ctx, index)} +} + +func (_c *L2BridgeSyncer_GetExitRootByIndex_Call) Run(run func(ctx context.Context, index uint32)) *L2BridgeSyncer_GetExitRootByIndex_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context), args[1].(uint32)) + }) + return _c +} + +func (_c *L2BridgeSyncer_GetExitRootByIndex_Call) Return(_a0 treetypes.Root, _a1 error) *L2BridgeSyncer_GetExitRootByIndex_Call { + _c.Call.Return(_a0, _a1) + return _c +} + +func (_c *L2BridgeSyncer_GetExitRootByIndex_Call) RunAndReturn(run func(context.Context, uint32) (treetypes.Root, error)) *L2BridgeSyncer_GetExitRootByIndex_Call { + _c.Call.Return(run) + return _c +} + +// GetLastProcessedBlock provides a mock function with given fields: ctx +func (_m *L2BridgeSyncer) GetLastProcessedBlock(ctx context.Context) (uint64, error) { + ret := _m.Called(ctx) + + if len(ret) == 0 { + panic("no return value specified for GetLastProcessedBlock") + } + + var r0 uint64 + var r1 error + if rf, ok := ret.Get(0).(func(context.Context) (uint64, error)); ok { + return rf(ctx) + } + if rf, ok := ret.Get(0).(func(context.Context) uint64); ok { + r0 = rf(ctx) + } else { + r0 = ret.Get(0).(uint64) + } + + if rf, ok := ret.Get(1).(func(context.Context) error); ok { + r1 = rf(ctx) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// L2BridgeSyncer_GetLastProcessedBlock_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GetLastProcessedBlock' +type L2BridgeSyncer_GetLastProcessedBlock_Call struct { + *mock.Call +} + +// GetLastProcessedBlock is a helper method to define mock.On call +// - ctx context.Context +func (_e *L2BridgeSyncer_Expecter) GetLastProcessedBlock(ctx interface{}) *L2BridgeSyncer_GetLastProcessedBlock_Call { + return &L2BridgeSyncer_GetLastProcessedBlock_Call{Call: _e.mock.On("GetLastProcessedBlock", ctx)} +} + +func (_c *L2BridgeSyncer_GetLastProcessedBlock_Call) Run(run func(ctx context.Context)) *L2BridgeSyncer_GetLastProcessedBlock_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context)) + }) + return _c +} + +func (_c *L2BridgeSyncer_GetLastProcessedBlock_Call) Return(_a0 uint64, _a1 error) *L2BridgeSyncer_GetLastProcessedBlock_Call { + _c.Call.Return(_a0, _a1) + return _c +} + +func (_c *L2BridgeSyncer_GetLastProcessedBlock_Call) RunAndReturn(run func(context.Context) (uint64, error)) *L2BridgeSyncer_GetLastProcessedBlock_Call { + _c.Call.Return(run) + return _c +} + +// OriginNetwork provides a mock function with given fields: +func (_m *L2BridgeSyncer) OriginNetwork() uint32 { + ret := _m.Called() + + if len(ret) == 0 { + panic("no return value specified for OriginNetwork") + } + + var r0 uint32 + if rf, ok := ret.Get(0).(func() uint32); ok { + r0 = rf() + } else { + r0 = ret.Get(0).(uint32) + } + + return r0 +} + +// L2BridgeSyncer_OriginNetwork_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'OriginNetwork' +type L2BridgeSyncer_OriginNetwork_Call struct { + *mock.Call +} + +// OriginNetwork is a helper method to define mock.On call +func (_e *L2BridgeSyncer_Expecter) OriginNetwork() *L2BridgeSyncer_OriginNetwork_Call { + return &L2BridgeSyncer_OriginNetwork_Call{Call: _e.mock.On("OriginNetwork")} +} + +func (_c *L2BridgeSyncer_OriginNetwork_Call) Run(run func()) *L2BridgeSyncer_OriginNetwork_Call { + _c.Call.Run(func(args mock.Arguments) { + run() + }) + return _c +} + +func (_c *L2BridgeSyncer_OriginNetwork_Call) Return(_a0 uint32) *L2BridgeSyncer_OriginNetwork_Call { + _c.Call.Return(_a0) + return _c +} + +func (_c *L2BridgeSyncer_OriginNetwork_Call) RunAndReturn(run func() uint32) *L2BridgeSyncer_OriginNetwork_Call { + _c.Call.Return(run) + return _c +} + +// NewL2BridgeSyncer creates a new instance of L2BridgeSyncer. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. +// The first argument is typically a *testing.T value. +func NewL2BridgeSyncer(t interface { + mock.TestingT + Cleanup(func()) +}) *L2BridgeSyncer { + mock := &L2BridgeSyncer{} + mock.Mock.Test(t) + + t.Cleanup(func() { mock.AssertExpectations(t) }) + + return mock +} diff --git a/aggsender/mocks/logger.go b/aggsender/mocks/logger.go new file mode 100644 index 00000000..bb26739e --- /dev/null +++ b/aggsender/mocks/logger.go @@ -0,0 +1,376 @@ +// Code generated by mockery. DO NOT EDIT. + +package mocks + +import mock "github.com/stretchr/testify/mock" + +// Logger is an autogenerated mock type for the Logger type +type Logger struct { + mock.Mock +} + +type Logger_Expecter struct { + mock *mock.Mock +} + +func (_m *Logger) EXPECT() *Logger_Expecter { + return &Logger_Expecter{mock: &_m.Mock} +} + +// Debug provides a mock function with given fields: args +func (_m *Logger) Debug(args ...interface{}) { + var _ca []interface{} + _ca = append(_ca, args...) + _m.Called(_ca...) +} + +// Logger_Debug_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Debug' +type Logger_Debug_Call struct { + *mock.Call +} + +// Debug is a helper method to define mock.On call +// - args ...interface{} +func (_e *Logger_Expecter) Debug(args ...interface{}) *Logger_Debug_Call { + return &Logger_Debug_Call{Call: _e.mock.On("Debug", + append([]interface{}{}, args...)...)} +} + +func (_c *Logger_Debug_Call) Run(run func(args ...interface{})) *Logger_Debug_Call { + _c.Call.Run(func(args mock.Arguments) { + variadicArgs := make([]interface{}, len(args)-0) + for i, a := range args[0:] { + if a != nil { + variadicArgs[i] = a.(interface{}) + } + } + run(variadicArgs...) + }) + return _c +} + +func (_c *Logger_Debug_Call) Return() *Logger_Debug_Call { + _c.Call.Return() + return _c +} + +func (_c *Logger_Debug_Call) RunAndReturn(run func(...interface{})) *Logger_Debug_Call { + _c.Call.Return(run) + return _c +} + +// Debugf provides a mock function with given fields: format, args +func (_m *Logger) Debugf(format string, args ...interface{}) { + var _ca []interface{} + _ca = append(_ca, format) + _ca = append(_ca, args...) + _m.Called(_ca...) +} + +// Logger_Debugf_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Debugf' +type Logger_Debugf_Call struct { + *mock.Call +} + +// Debugf is a helper method to define mock.On call +// - format string +// - args ...interface{} +func (_e *Logger_Expecter) Debugf(format interface{}, args ...interface{}) *Logger_Debugf_Call { + return &Logger_Debugf_Call{Call: _e.mock.On("Debugf", + append([]interface{}{format}, args...)...)} +} + +func (_c *Logger_Debugf_Call) Run(run func(format string, args ...interface{})) *Logger_Debugf_Call { + _c.Call.Run(func(args mock.Arguments) { + variadicArgs := make([]interface{}, len(args)-1) + for i, a := range args[1:] { + if a != nil { + variadicArgs[i] = a.(interface{}) + } + } + run(args[0].(string), variadicArgs...) + }) + return _c +} + +func (_c *Logger_Debugf_Call) Return() *Logger_Debugf_Call { + _c.Call.Return() + return _c +} + +func (_c *Logger_Debugf_Call) RunAndReturn(run func(string, ...interface{})) *Logger_Debugf_Call { + _c.Call.Return(run) + return _c +} + +// Error provides a mock function with given fields: args +func (_m *Logger) Error(args ...interface{}) { + var _ca []interface{} + _ca = append(_ca, args...) + _m.Called(_ca...) +} + +// Logger_Error_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Error' +type Logger_Error_Call struct { + *mock.Call +} + +// Error is a helper method to define mock.On call +// - args ...interface{} +func (_e *Logger_Expecter) Error(args ...interface{}) *Logger_Error_Call { + return &Logger_Error_Call{Call: _e.mock.On("Error", + append([]interface{}{}, args...)...)} +} + +func (_c *Logger_Error_Call) Run(run func(args ...interface{})) *Logger_Error_Call { + _c.Call.Run(func(args mock.Arguments) { + variadicArgs := make([]interface{}, len(args)-0) + for i, a := range args[0:] { + if a != nil { + variadicArgs[i] = a.(interface{}) + } + } + run(variadicArgs...) + }) + return _c +} + +func (_c *Logger_Error_Call) Return() *Logger_Error_Call { + _c.Call.Return() + return _c +} + +func (_c *Logger_Error_Call) RunAndReturn(run func(...interface{})) *Logger_Error_Call { + _c.Call.Return(run) + return _c +} + +// Errorf provides a mock function with given fields: format, args +func (_m *Logger) Errorf(format string, args ...interface{}) { + var _ca []interface{} + _ca = append(_ca, format) + _ca = append(_ca, args...) + _m.Called(_ca...) +} + +// Logger_Errorf_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Errorf' +type Logger_Errorf_Call struct { + *mock.Call +} + +// Errorf is a helper method to define mock.On call +// - format string +// - args ...interface{} +func (_e *Logger_Expecter) Errorf(format interface{}, args ...interface{}) *Logger_Errorf_Call { + return &Logger_Errorf_Call{Call: _e.mock.On("Errorf", + append([]interface{}{format}, args...)...)} +} + +func (_c *Logger_Errorf_Call) Run(run func(format string, args ...interface{})) *Logger_Errorf_Call { + _c.Call.Run(func(args mock.Arguments) { + variadicArgs := make([]interface{}, len(args)-1) + for i, a := range args[1:] { + if a != nil { + variadicArgs[i] = a.(interface{}) + } + } + run(args[0].(string), variadicArgs...) + }) + return _c +} + +func (_c *Logger_Errorf_Call) Return() *Logger_Errorf_Call { + _c.Call.Return() + return _c +} + +func (_c *Logger_Errorf_Call) RunAndReturn(run func(string, ...interface{})) *Logger_Errorf_Call { + _c.Call.Return(run) + return _c +} + +// Info provides a mock function with given fields: args +func (_m *Logger) Info(args ...interface{}) { + var _ca []interface{} + _ca = append(_ca, args...) + _m.Called(_ca...) +} + +// Logger_Info_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Info' +type Logger_Info_Call struct { + *mock.Call +} + +// Info is a helper method to define mock.On call +// - args ...interface{} +func (_e *Logger_Expecter) Info(args ...interface{}) *Logger_Info_Call { + return &Logger_Info_Call{Call: _e.mock.On("Info", + append([]interface{}{}, args...)...)} +} + +func (_c *Logger_Info_Call) Run(run func(args ...interface{})) *Logger_Info_Call { + _c.Call.Run(func(args mock.Arguments) { + variadicArgs := make([]interface{}, len(args)-0) + for i, a := range args[0:] { + if a != nil { + variadicArgs[i] = a.(interface{}) + } + } + run(variadicArgs...) + }) + return _c +} + +func (_c *Logger_Info_Call) Return() *Logger_Info_Call { + _c.Call.Return() + return _c +} + +func (_c *Logger_Info_Call) RunAndReturn(run func(...interface{})) *Logger_Info_Call { + _c.Call.Return(run) + return _c +} + +// Infof provides a mock function with given fields: format, args +func (_m *Logger) Infof(format string, args ...interface{}) { + var _ca []interface{} + _ca = append(_ca, format) + _ca = append(_ca, args...) + _m.Called(_ca...) +} + +// Logger_Infof_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Infof' +type Logger_Infof_Call struct { + *mock.Call +} + +// Infof is a helper method to define mock.On call +// - format string +// - args ...interface{} +func (_e *Logger_Expecter) Infof(format interface{}, args ...interface{}) *Logger_Infof_Call { + return &Logger_Infof_Call{Call: _e.mock.On("Infof", + append([]interface{}{format}, args...)...)} +} + +func (_c *Logger_Infof_Call) Run(run func(format string, args ...interface{})) *Logger_Infof_Call { + _c.Call.Run(func(args mock.Arguments) { + variadicArgs := make([]interface{}, len(args)-1) + for i, a := range args[1:] { + if a != nil { + variadicArgs[i] = a.(interface{}) + } + } + run(args[0].(string), variadicArgs...) + }) + return _c +} + +func (_c *Logger_Infof_Call) Return() *Logger_Infof_Call { + _c.Call.Return() + return _c +} + +func (_c *Logger_Infof_Call) RunAndReturn(run func(string, ...interface{})) *Logger_Infof_Call { + _c.Call.Return(run) + return _c +} + +// Warn provides a mock function with given fields: args +func (_m *Logger) Warn(args ...interface{}) { + var _ca []interface{} + _ca = append(_ca, args...) + _m.Called(_ca...) +} + +// Logger_Warn_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Warn' +type Logger_Warn_Call struct { + *mock.Call +} + +// Warn is a helper method to define mock.On call +// - args ...interface{} +func (_e *Logger_Expecter) Warn(args ...interface{}) *Logger_Warn_Call { + return &Logger_Warn_Call{Call: _e.mock.On("Warn", + append([]interface{}{}, args...)...)} +} + +func (_c *Logger_Warn_Call) Run(run func(args ...interface{})) *Logger_Warn_Call { + _c.Call.Run(func(args mock.Arguments) { + variadicArgs := make([]interface{}, len(args)-0) + for i, a := range args[0:] { + if a != nil { + variadicArgs[i] = a.(interface{}) + } + } + run(variadicArgs...) + }) + return _c +} + +func (_c *Logger_Warn_Call) Return() *Logger_Warn_Call { + _c.Call.Return() + return _c +} + +func (_c *Logger_Warn_Call) RunAndReturn(run func(...interface{})) *Logger_Warn_Call { + _c.Call.Return(run) + return _c +} + +// Warnf provides a mock function with given fields: format, args +func (_m *Logger) Warnf(format string, args ...interface{}) { + var _ca []interface{} + _ca = append(_ca, format) + _ca = append(_ca, args...) + _m.Called(_ca...) +} + +// Logger_Warnf_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Warnf' +type Logger_Warnf_Call struct { + *mock.Call +} + +// Warnf is a helper method to define mock.On call +// - format string +// - args ...interface{} +func (_e *Logger_Expecter) Warnf(format interface{}, args ...interface{}) *Logger_Warnf_Call { + return &Logger_Warnf_Call{Call: _e.mock.On("Warnf", + append([]interface{}{format}, args...)...)} +} + +func (_c *Logger_Warnf_Call) Run(run func(format string, args ...interface{})) *Logger_Warnf_Call { + _c.Call.Run(func(args mock.Arguments) { + variadicArgs := make([]interface{}, len(args)-1) + for i, a := range args[1:] { + if a != nil { + variadicArgs[i] = a.(interface{}) + } + } + run(args[0].(string), variadicArgs...) + }) + return _c +} + +func (_c *Logger_Warnf_Call) Return() *Logger_Warnf_Call { + _c.Call.Return() + return _c +} + +func (_c *Logger_Warnf_Call) RunAndReturn(run func(string, ...interface{})) *Logger_Warnf_Call { + _c.Call.Return(run) + return _c +} + +// NewLogger creates a new instance of Logger. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. +// The first argument is typically a *testing.T value. +func NewLogger(t interface { + mock.TestingT + Cleanup(func()) +}) *Logger { + mock := &Logger{} + mock.Mock.Test(t) + + t.Cleanup(func() { mock.AssertExpectations(t) }) + + return mock +} diff --git a/aggsender/mocks/mock_aggsender_storage.go b/aggsender/mocks/mock_aggsender_storage.go deleted file mode 100644 index 17f8d227..00000000 --- a/aggsender/mocks/mock_aggsender_storage.go +++ /dev/null @@ -1,351 +0,0 @@ -// Code generated by mockery. DO NOT EDIT. - -package mocks - -import ( - agglayer "github.com/0xPolygon/cdk/agglayer" - common "github.com/ethereum/go-ethereum/common" - - context "context" - - mock "github.com/stretchr/testify/mock" - - types "github.com/0xPolygon/cdk/aggsender/types" -) - -// AggSenderStorageMock is an autogenerated mock type for the AggSenderStorage type -type AggSenderStorageMock struct { - mock.Mock -} - -type AggSenderStorageMock_Expecter struct { - mock *mock.Mock -} - -func (_m *AggSenderStorageMock) EXPECT() *AggSenderStorageMock_Expecter { - return &AggSenderStorageMock_Expecter{mock: &_m.Mock} -} - -// DeleteCertificate provides a mock function with given fields: ctx, certificateID -func (_m *AggSenderStorageMock) DeleteCertificate(ctx context.Context, certificateID common.Hash) error { - ret := _m.Called(ctx, certificateID) - - if len(ret) == 0 { - panic("no return value specified for DeleteCertificate") - } - - var r0 error - if rf, ok := ret.Get(0).(func(context.Context, common.Hash) error); ok { - r0 = rf(ctx, certificateID) - } else { - r0 = ret.Error(0) - } - - return r0 -} - -// AggSenderStorageMock_DeleteCertificate_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'DeleteCertificate' -type AggSenderStorageMock_DeleteCertificate_Call struct { - *mock.Call -} - -// DeleteCertificate is a helper method to define mock.On call -// - ctx context.Context -// - certificateID common.Hash -func (_e *AggSenderStorageMock_Expecter) DeleteCertificate(ctx interface{}, certificateID interface{}) *AggSenderStorageMock_DeleteCertificate_Call { - return &AggSenderStorageMock_DeleteCertificate_Call{Call: _e.mock.On("DeleteCertificate", ctx, certificateID)} -} - -func (_c *AggSenderStorageMock_DeleteCertificate_Call) Run(run func(ctx context.Context, certificateID common.Hash)) *AggSenderStorageMock_DeleteCertificate_Call { - _c.Call.Run(func(args mock.Arguments) { - run(args[0].(context.Context), args[1].(common.Hash)) - }) - return _c -} - -func (_c *AggSenderStorageMock_DeleteCertificate_Call) Return(_a0 error) *AggSenderStorageMock_DeleteCertificate_Call { - _c.Call.Return(_a0) - return _c -} - -func (_c *AggSenderStorageMock_DeleteCertificate_Call) RunAndReturn(run func(context.Context, common.Hash) error) *AggSenderStorageMock_DeleteCertificate_Call { - _c.Call.Return(run) - return _c -} - -// GetCertificateByHeight provides a mock function with given fields: height -func (_m *AggSenderStorageMock) GetCertificateByHeight(height uint64) (types.CertificateInfo, error) { - ret := _m.Called(height) - - if len(ret) == 0 { - panic("no return value specified for GetCertificateByHeight") - } - - var r0 types.CertificateInfo - var r1 error - if rf, ok := ret.Get(0).(func(uint64) (types.CertificateInfo, error)); ok { - return rf(height) - } - if rf, ok := ret.Get(0).(func(uint64) types.CertificateInfo); ok { - r0 = rf(height) - } else { - r0 = ret.Get(0).(types.CertificateInfo) - } - - if rf, ok := ret.Get(1).(func(uint64) error); ok { - r1 = rf(height) - } else { - r1 = ret.Error(1) - } - - return r0, r1 -} - -// AggSenderStorageMock_GetCertificateByHeight_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GetCertificateByHeight' -type AggSenderStorageMock_GetCertificateByHeight_Call struct { - *mock.Call -} - -// GetCertificateByHeight is a helper method to define mock.On call -// - height uint64 -func (_e *AggSenderStorageMock_Expecter) GetCertificateByHeight(height interface{}) *AggSenderStorageMock_GetCertificateByHeight_Call { - return &AggSenderStorageMock_GetCertificateByHeight_Call{Call: _e.mock.On("GetCertificateByHeight", height)} -} - -func (_c *AggSenderStorageMock_GetCertificateByHeight_Call) Run(run func(height uint64)) *AggSenderStorageMock_GetCertificateByHeight_Call { - _c.Call.Run(func(args mock.Arguments) { - run(args[0].(uint64)) - }) - return _c -} - -func (_c *AggSenderStorageMock_GetCertificateByHeight_Call) Return(_a0 types.CertificateInfo, _a1 error) *AggSenderStorageMock_GetCertificateByHeight_Call { - _c.Call.Return(_a0, _a1) - return _c -} - -func (_c *AggSenderStorageMock_GetCertificateByHeight_Call) RunAndReturn(run func(uint64) (types.CertificateInfo, error)) *AggSenderStorageMock_GetCertificateByHeight_Call { - _c.Call.Return(run) - return _c -} - -// GetCertificatesByStatus provides a mock function with given fields: status -func (_m *AggSenderStorageMock) GetCertificatesByStatus(status []agglayer.CertificateStatus) ([]*types.CertificateInfo, error) { - ret := _m.Called(status) - - if len(ret) == 0 { - panic("no return value specified for GetCertificatesByStatus") - } - - var r0 []*types.CertificateInfo - var r1 error - if rf, ok := ret.Get(0).(func([]agglayer.CertificateStatus) ([]*types.CertificateInfo, error)); ok { - return rf(status) - } - if rf, ok := ret.Get(0).(func([]agglayer.CertificateStatus) []*types.CertificateInfo); ok { - r0 = rf(status) - } else { - if ret.Get(0) != nil { - r0 = ret.Get(0).([]*types.CertificateInfo) - } - } - - if rf, ok := ret.Get(1).(func([]agglayer.CertificateStatus) error); ok { - r1 = rf(status) - } else { - r1 = ret.Error(1) - } - - return r0, r1 -} - -// AggSenderStorageMock_GetCertificatesByStatus_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GetCertificatesByStatus' -type AggSenderStorageMock_GetCertificatesByStatus_Call struct { - *mock.Call -} - -// GetCertificatesByStatus is a helper method to define mock.On call -// - status []agglayer.CertificateStatus -func (_e *AggSenderStorageMock_Expecter) GetCertificatesByStatus(status interface{}) *AggSenderStorageMock_GetCertificatesByStatus_Call { - return &AggSenderStorageMock_GetCertificatesByStatus_Call{Call: _e.mock.On("GetCertificatesByStatus", status)} -} - -func (_c *AggSenderStorageMock_GetCertificatesByStatus_Call) Run(run func(status []agglayer.CertificateStatus)) *AggSenderStorageMock_GetCertificatesByStatus_Call { - _c.Call.Run(func(args mock.Arguments) { - run(args[0].([]agglayer.CertificateStatus)) - }) - return _c -} - -func (_c *AggSenderStorageMock_GetCertificatesByStatus_Call) Return(_a0 []*types.CertificateInfo, _a1 error) *AggSenderStorageMock_GetCertificatesByStatus_Call { - _c.Call.Return(_a0, _a1) - return _c -} - -func (_c *AggSenderStorageMock_GetCertificatesByStatus_Call) RunAndReturn(run func([]agglayer.CertificateStatus) ([]*types.CertificateInfo, error)) *AggSenderStorageMock_GetCertificatesByStatus_Call { - _c.Call.Return(run) - return _c -} - -// GetLastSentCertificate provides a mock function with given fields: -func (_m *AggSenderStorageMock) GetLastSentCertificate() (types.CertificateInfo, error) { - ret := _m.Called() - - if len(ret) == 0 { - panic("no return value specified for GetLastSentCertificate") - } - - var r0 types.CertificateInfo - var r1 error - if rf, ok := ret.Get(0).(func() (types.CertificateInfo, error)); ok { - return rf() - } - if rf, ok := ret.Get(0).(func() types.CertificateInfo); ok { - r0 = rf() - } else { - r0 = ret.Get(0).(types.CertificateInfo) - } - - if rf, ok := ret.Get(1).(func() error); ok { - r1 = rf() - } else { - r1 = ret.Error(1) - } - - return r0, r1 -} - -// AggSenderStorageMock_GetLastSentCertificate_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GetLastSentCertificate' -type AggSenderStorageMock_GetLastSentCertificate_Call struct { - *mock.Call -} - -// GetLastSentCertificate is a helper method to define mock.On call -func (_e *AggSenderStorageMock_Expecter) GetLastSentCertificate() *AggSenderStorageMock_GetLastSentCertificate_Call { - return &AggSenderStorageMock_GetLastSentCertificate_Call{Call: _e.mock.On("GetLastSentCertificate")} -} - -func (_c *AggSenderStorageMock_GetLastSentCertificate_Call) Run(run func()) *AggSenderStorageMock_GetLastSentCertificate_Call { - _c.Call.Run(func(args mock.Arguments) { - run() - }) - return _c -} - -func (_c *AggSenderStorageMock_GetLastSentCertificate_Call) Return(_a0 types.CertificateInfo, _a1 error) *AggSenderStorageMock_GetLastSentCertificate_Call { - _c.Call.Return(_a0, _a1) - return _c -} - -func (_c *AggSenderStorageMock_GetLastSentCertificate_Call) RunAndReturn(run func() (types.CertificateInfo, error)) *AggSenderStorageMock_GetLastSentCertificate_Call { - _c.Call.Return(run) - return _c -} - -// SaveLastSentCertificate provides a mock function with given fields: ctx, certificate -func (_m *AggSenderStorageMock) SaveLastSentCertificate(ctx context.Context, certificate types.CertificateInfo) error { - ret := _m.Called(ctx, certificate) - - if len(ret) == 0 { - panic("no return value specified for SaveLastSentCertificate") - } - - var r0 error - if rf, ok := ret.Get(0).(func(context.Context, types.CertificateInfo) error); ok { - r0 = rf(ctx, certificate) - } else { - r0 = ret.Error(0) - } - - return r0 -} - -// AggSenderStorageMock_SaveLastSentCertificate_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'SaveLastSentCertificate' -type AggSenderStorageMock_SaveLastSentCertificate_Call struct { - *mock.Call -} - -// SaveLastSentCertificate is a helper method to define mock.On call -// - ctx context.Context -// - certificate types.CertificateInfo -func (_e *AggSenderStorageMock_Expecter) SaveLastSentCertificate(ctx interface{}, certificate interface{}) *AggSenderStorageMock_SaveLastSentCertificate_Call { - return &AggSenderStorageMock_SaveLastSentCertificate_Call{Call: _e.mock.On("SaveLastSentCertificate", ctx, certificate)} -} - -func (_c *AggSenderStorageMock_SaveLastSentCertificate_Call) Run(run func(ctx context.Context, certificate types.CertificateInfo)) *AggSenderStorageMock_SaveLastSentCertificate_Call { - _c.Call.Run(func(args mock.Arguments) { - run(args[0].(context.Context), args[1].(types.CertificateInfo)) - }) - return _c -} - -func (_c *AggSenderStorageMock_SaveLastSentCertificate_Call) Return(_a0 error) *AggSenderStorageMock_SaveLastSentCertificate_Call { - _c.Call.Return(_a0) - return _c -} - -func (_c *AggSenderStorageMock_SaveLastSentCertificate_Call) RunAndReturn(run func(context.Context, types.CertificateInfo) error) *AggSenderStorageMock_SaveLastSentCertificate_Call { - _c.Call.Return(run) - return _c -} - -// UpdateCertificateStatus provides a mock function with given fields: ctx, certificate -func (_m *AggSenderStorageMock) UpdateCertificateStatus(ctx context.Context, certificate types.CertificateInfo) error { - ret := _m.Called(ctx, certificate) - - if len(ret) == 0 { - panic("no return value specified for UpdateCertificateStatus") - } - - var r0 error - if rf, ok := ret.Get(0).(func(context.Context, types.CertificateInfo) error); ok { - r0 = rf(ctx, certificate) - } else { - r0 = ret.Error(0) - } - - return r0 -} - -// AggSenderStorageMock_UpdateCertificateStatus_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'UpdateCertificateStatus' -type AggSenderStorageMock_UpdateCertificateStatus_Call struct { - *mock.Call -} - -// UpdateCertificateStatus is a helper method to define mock.On call -// - ctx context.Context -// - certificate types.CertificateInfo -func (_e *AggSenderStorageMock_Expecter) UpdateCertificateStatus(ctx interface{}, certificate interface{}) *AggSenderStorageMock_UpdateCertificateStatus_Call { - return &AggSenderStorageMock_UpdateCertificateStatus_Call{Call: _e.mock.On("UpdateCertificateStatus", ctx, certificate)} -} - -func (_c *AggSenderStorageMock_UpdateCertificateStatus_Call) Run(run func(ctx context.Context, certificate types.CertificateInfo)) *AggSenderStorageMock_UpdateCertificateStatus_Call { - _c.Call.Run(func(args mock.Arguments) { - run(args[0].(context.Context), args[1].(types.CertificateInfo)) - }) - return _c -} - -func (_c *AggSenderStorageMock_UpdateCertificateStatus_Call) Return(_a0 error) *AggSenderStorageMock_UpdateCertificateStatus_Call { - _c.Call.Return(_a0) - return _c -} - -func (_c *AggSenderStorageMock_UpdateCertificateStatus_Call) RunAndReturn(run func(context.Context, types.CertificateInfo) error) *AggSenderStorageMock_UpdateCertificateStatus_Call { - _c.Call.Return(run) - return _c -} - -// NewAggSenderStorageMock creates a new instance of AggSenderStorageMock. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. -// The first argument is typically a *testing.T value. -func NewAggSenderStorageMock(t interface { - mock.TestingT - Cleanup(func()) -}) *AggSenderStorageMock { - mock := &AggSenderStorageMock{} - mock.Mock.Test(t) - - t.Cleanup(func() { mock.AssertExpectations(t) }) - - return mock -} diff --git a/aggsender/mocks/mock_l1infotree_syncer.go b/aggsender/mocks/mock_l1infotree_syncer.go deleted file mode 100644 index e113d4ed..00000000 --- a/aggsender/mocks/mock_l1infotree_syncer.go +++ /dev/null @@ -1,217 +0,0 @@ -// Code generated by mockery. DO NOT EDIT. - -package mocks - -import ( - context "context" - - common "github.com/ethereum/go-ethereum/common" - - l1infotreesync "github.com/0xPolygon/cdk/l1infotreesync" - - mock "github.com/stretchr/testify/mock" - - treetypes "github.com/0xPolygon/cdk/tree/types" -) - -// L1InfoTreeSyncerMock is an autogenerated mock type for the L1InfoTreeSyncer type -type L1InfoTreeSyncerMock struct { - mock.Mock -} - -type L1InfoTreeSyncerMock_Expecter struct { - mock *mock.Mock -} - -func (_m *L1InfoTreeSyncerMock) EXPECT() *L1InfoTreeSyncerMock_Expecter { - return &L1InfoTreeSyncerMock_Expecter{mock: &_m.Mock} -} - -// GetInfoByGlobalExitRoot provides a mock function with given fields: globalExitRoot -func (_m *L1InfoTreeSyncerMock) GetInfoByGlobalExitRoot(globalExitRoot common.Hash) (*l1infotreesync.L1InfoTreeLeaf, error) { - ret := _m.Called(globalExitRoot) - - if len(ret) == 0 { - panic("no return value specified for GetInfoByGlobalExitRoot") - } - - var r0 *l1infotreesync.L1InfoTreeLeaf - var r1 error - if rf, ok := ret.Get(0).(func(common.Hash) (*l1infotreesync.L1InfoTreeLeaf, error)); ok { - return rf(globalExitRoot) - } - if rf, ok := ret.Get(0).(func(common.Hash) *l1infotreesync.L1InfoTreeLeaf); ok { - r0 = rf(globalExitRoot) - } else { - if ret.Get(0) != nil { - r0 = ret.Get(0).(*l1infotreesync.L1InfoTreeLeaf) - } - } - - if rf, ok := ret.Get(1).(func(common.Hash) error); ok { - r1 = rf(globalExitRoot) - } else { - r1 = ret.Error(1) - } - - return r0, r1 -} - -// L1InfoTreeSyncerMock_GetInfoByGlobalExitRoot_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GetInfoByGlobalExitRoot' -type L1InfoTreeSyncerMock_GetInfoByGlobalExitRoot_Call struct { - *mock.Call -} - -// GetInfoByGlobalExitRoot is a helper method to define mock.On call -// - globalExitRoot common.Hash -func (_e *L1InfoTreeSyncerMock_Expecter) GetInfoByGlobalExitRoot(globalExitRoot interface{}) *L1InfoTreeSyncerMock_GetInfoByGlobalExitRoot_Call { - return &L1InfoTreeSyncerMock_GetInfoByGlobalExitRoot_Call{Call: _e.mock.On("GetInfoByGlobalExitRoot", globalExitRoot)} -} - -func (_c *L1InfoTreeSyncerMock_GetInfoByGlobalExitRoot_Call) Run(run func(globalExitRoot common.Hash)) *L1InfoTreeSyncerMock_GetInfoByGlobalExitRoot_Call { - _c.Call.Run(func(args mock.Arguments) { - run(args[0].(common.Hash)) - }) - return _c -} - -func (_c *L1InfoTreeSyncerMock_GetInfoByGlobalExitRoot_Call) Return(_a0 *l1infotreesync.L1InfoTreeLeaf, _a1 error) *L1InfoTreeSyncerMock_GetInfoByGlobalExitRoot_Call { - _c.Call.Return(_a0, _a1) - return _c -} - -func (_c *L1InfoTreeSyncerMock_GetInfoByGlobalExitRoot_Call) RunAndReturn(run func(common.Hash) (*l1infotreesync.L1InfoTreeLeaf, error)) *L1InfoTreeSyncerMock_GetInfoByGlobalExitRoot_Call { - _c.Call.Return(run) - return _c -} - -// GetL1InfoTreeMerkleProofFromIndexToRoot provides a mock function with given fields: ctx, index, root -func (_m *L1InfoTreeSyncerMock) GetL1InfoTreeMerkleProofFromIndexToRoot(ctx context.Context, index uint32, root common.Hash) (treetypes.Proof, error) { - ret := _m.Called(ctx, index, root) - - if len(ret) == 0 { - panic("no return value specified for GetL1InfoTreeMerkleProofFromIndexToRoot") - } - - var r0 treetypes.Proof - var r1 error - if rf, ok := ret.Get(0).(func(context.Context, uint32, common.Hash) (treetypes.Proof, error)); ok { - return rf(ctx, index, root) - } - if rf, ok := ret.Get(0).(func(context.Context, uint32, common.Hash) treetypes.Proof); ok { - r0 = rf(ctx, index, root) - } else { - if ret.Get(0) != nil { - r0 = ret.Get(0).(treetypes.Proof) - } - } - - if rf, ok := ret.Get(1).(func(context.Context, uint32, common.Hash) error); ok { - r1 = rf(ctx, index, root) - } else { - r1 = ret.Error(1) - } - - return r0, r1 -} - -// L1InfoTreeSyncerMock_GetL1InfoTreeMerkleProofFromIndexToRoot_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GetL1InfoTreeMerkleProofFromIndexToRoot' -type L1InfoTreeSyncerMock_GetL1InfoTreeMerkleProofFromIndexToRoot_Call struct { - *mock.Call -} - -// GetL1InfoTreeMerkleProofFromIndexToRoot is a helper method to define mock.On call -// - ctx context.Context -// - index uint32 -// - root common.Hash -func (_e *L1InfoTreeSyncerMock_Expecter) GetL1InfoTreeMerkleProofFromIndexToRoot(ctx interface{}, index interface{}, root interface{}) *L1InfoTreeSyncerMock_GetL1InfoTreeMerkleProofFromIndexToRoot_Call { - return &L1InfoTreeSyncerMock_GetL1InfoTreeMerkleProofFromIndexToRoot_Call{Call: _e.mock.On("GetL1InfoTreeMerkleProofFromIndexToRoot", ctx, index, root)} -} - -func (_c *L1InfoTreeSyncerMock_GetL1InfoTreeMerkleProofFromIndexToRoot_Call) Run(run func(ctx context.Context, index uint32, root common.Hash)) *L1InfoTreeSyncerMock_GetL1InfoTreeMerkleProofFromIndexToRoot_Call { - _c.Call.Run(func(args mock.Arguments) { - run(args[0].(context.Context), args[1].(uint32), args[2].(common.Hash)) - }) - return _c -} - -func (_c *L1InfoTreeSyncerMock_GetL1InfoTreeMerkleProofFromIndexToRoot_Call) Return(_a0 treetypes.Proof, _a1 error) *L1InfoTreeSyncerMock_GetL1InfoTreeMerkleProofFromIndexToRoot_Call { - _c.Call.Return(_a0, _a1) - return _c -} - -func (_c *L1InfoTreeSyncerMock_GetL1InfoTreeMerkleProofFromIndexToRoot_Call) RunAndReturn(run func(context.Context, uint32, common.Hash) (treetypes.Proof, error)) *L1InfoTreeSyncerMock_GetL1InfoTreeMerkleProofFromIndexToRoot_Call { - _c.Call.Return(run) - return _c -} - -// GetL1InfoTreeRootByIndex provides a mock function with given fields: ctx, index -func (_m *L1InfoTreeSyncerMock) GetL1InfoTreeRootByIndex(ctx context.Context, index uint32) (treetypes.Root, error) { - ret := _m.Called(ctx, index) - - if len(ret) == 0 { - panic("no return value specified for GetL1InfoTreeRootByIndex") - } - - var r0 treetypes.Root - var r1 error - if rf, ok := ret.Get(0).(func(context.Context, uint32) (treetypes.Root, error)); ok { - return rf(ctx, index) - } - if rf, ok := ret.Get(0).(func(context.Context, uint32) treetypes.Root); ok { - r0 = rf(ctx, index) - } else { - r0 = ret.Get(0).(treetypes.Root) - } - - if rf, ok := ret.Get(1).(func(context.Context, uint32) error); ok { - r1 = rf(ctx, index) - } else { - r1 = ret.Error(1) - } - - return r0, r1 -} - -// L1InfoTreeSyncerMock_GetL1InfoTreeRootByIndex_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GetL1InfoTreeRootByIndex' -type L1InfoTreeSyncerMock_GetL1InfoTreeRootByIndex_Call struct { - *mock.Call -} - -// GetL1InfoTreeRootByIndex is a helper method to define mock.On call -// - ctx context.Context -// - index uint32 -func (_e *L1InfoTreeSyncerMock_Expecter) GetL1InfoTreeRootByIndex(ctx interface{}, index interface{}) *L1InfoTreeSyncerMock_GetL1InfoTreeRootByIndex_Call { - return &L1InfoTreeSyncerMock_GetL1InfoTreeRootByIndex_Call{Call: _e.mock.On("GetL1InfoTreeRootByIndex", ctx, index)} -} - -func (_c *L1InfoTreeSyncerMock_GetL1InfoTreeRootByIndex_Call) Run(run func(ctx context.Context, index uint32)) *L1InfoTreeSyncerMock_GetL1InfoTreeRootByIndex_Call { - _c.Call.Run(func(args mock.Arguments) { - run(args[0].(context.Context), args[1].(uint32)) - }) - return _c -} - -func (_c *L1InfoTreeSyncerMock_GetL1InfoTreeRootByIndex_Call) Return(_a0 treetypes.Root, _a1 error) *L1InfoTreeSyncerMock_GetL1InfoTreeRootByIndex_Call { - _c.Call.Return(_a0, _a1) - return _c -} - -func (_c *L1InfoTreeSyncerMock_GetL1InfoTreeRootByIndex_Call) RunAndReturn(run func(context.Context, uint32) (treetypes.Root, error)) *L1InfoTreeSyncerMock_GetL1InfoTreeRootByIndex_Call { - _c.Call.Return(run) - return _c -} - -// NewL1InfoTreeSyncerMock creates a new instance of L1InfoTreeSyncerMock. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. -// The first argument is typically a *testing.T value. -func NewL1InfoTreeSyncerMock(t interface { - mock.TestingT - Cleanup(func()) -}) *L1InfoTreeSyncerMock { - mock := &L1InfoTreeSyncerMock{} - mock.Mock.Test(t) - - t.Cleanup(func() { mock.AssertExpectations(t) }) - - return mock -} diff --git a/aggsender/mocks/mock_l2bridge_syncer.go b/aggsender/mocks/mock_l2bridge_syncer.go deleted file mode 100644 index 725184c3..00000000 --- a/aggsender/mocks/mock_l2bridge_syncer.go +++ /dev/null @@ -1,423 +0,0 @@ -// Code generated by mockery. DO NOT EDIT. - -package mocks - -import ( - bridgesync "github.com/0xPolygon/cdk/bridgesync" - common "github.com/ethereum/go-ethereum/common" - - context "context" - - etherman "github.com/0xPolygon/cdk/etherman" - - mock "github.com/stretchr/testify/mock" - - treetypes "github.com/0xPolygon/cdk/tree/types" -) - -// L2BridgeSyncerMock is an autogenerated mock type for the L2BridgeSyncer type -type L2BridgeSyncerMock struct { - mock.Mock -} - -type L2BridgeSyncerMock_Expecter struct { - mock *mock.Mock -} - -func (_m *L2BridgeSyncerMock) EXPECT() *L2BridgeSyncerMock_Expecter { - return &L2BridgeSyncerMock_Expecter{mock: &_m.Mock} -} - -// BlockFinality provides a mock function with given fields: -func (_m *L2BridgeSyncerMock) BlockFinality() etherman.BlockNumberFinality { - ret := _m.Called() - - if len(ret) == 0 { - panic("no return value specified for BlockFinality") - } - - var r0 etherman.BlockNumberFinality - if rf, ok := ret.Get(0).(func() etherman.BlockNumberFinality); ok { - r0 = rf() - } else { - r0 = ret.Get(0).(etherman.BlockNumberFinality) - } - - return r0 -} - -// L2BridgeSyncerMock_BlockFinality_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'BlockFinality' -type L2BridgeSyncerMock_BlockFinality_Call struct { - *mock.Call -} - -// BlockFinality is a helper method to define mock.On call -func (_e *L2BridgeSyncerMock_Expecter) BlockFinality() *L2BridgeSyncerMock_BlockFinality_Call { - return &L2BridgeSyncerMock_BlockFinality_Call{Call: _e.mock.On("BlockFinality")} -} - -func (_c *L2BridgeSyncerMock_BlockFinality_Call) Run(run func()) *L2BridgeSyncerMock_BlockFinality_Call { - _c.Call.Run(func(args mock.Arguments) { - run() - }) - return _c -} - -func (_c *L2BridgeSyncerMock_BlockFinality_Call) Return(_a0 etherman.BlockNumberFinality) *L2BridgeSyncerMock_BlockFinality_Call { - _c.Call.Return(_a0) - return _c -} - -func (_c *L2BridgeSyncerMock_BlockFinality_Call) RunAndReturn(run func() etherman.BlockNumberFinality) *L2BridgeSyncerMock_BlockFinality_Call { - _c.Call.Return(run) - return _c -} - -// GetBlockByLER provides a mock function with given fields: ctx, ler -func (_m *L2BridgeSyncerMock) GetBlockByLER(ctx context.Context, ler common.Hash) (uint64, error) { - ret := _m.Called(ctx, ler) - - if len(ret) == 0 { - panic("no return value specified for GetBlockByLER") - } - - var r0 uint64 - var r1 error - if rf, ok := ret.Get(0).(func(context.Context, common.Hash) (uint64, error)); ok { - return rf(ctx, ler) - } - if rf, ok := ret.Get(0).(func(context.Context, common.Hash) uint64); ok { - r0 = rf(ctx, ler) - } else { - r0 = ret.Get(0).(uint64) - } - - if rf, ok := ret.Get(1).(func(context.Context, common.Hash) error); ok { - r1 = rf(ctx, ler) - } else { - r1 = ret.Error(1) - } - - return r0, r1 -} - -// L2BridgeSyncerMock_GetBlockByLER_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GetBlockByLER' -type L2BridgeSyncerMock_GetBlockByLER_Call struct { - *mock.Call -} - -// GetBlockByLER is a helper method to define mock.On call -// - ctx context.Context -// - ler common.Hash -func (_e *L2BridgeSyncerMock_Expecter) GetBlockByLER(ctx interface{}, ler interface{}) *L2BridgeSyncerMock_GetBlockByLER_Call { - return &L2BridgeSyncerMock_GetBlockByLER_Call{Call: _e.mock.On("GetBlockByLER", ctx, ler)} -} - -func (_c *L2BridgeSyncerMock_GetBlockByLER_Call) Run(run func(ctx context.Context, ler common.Hash)) *L2BridgeSyncerMock_GetBlockByLER_Call { - _c.Call.Run(func(args mock.Arguments) { - run(args[0].(context.Context), args[1].(common.Hash)) - }) - return _c -} - -func (_c *L2BridgeSyncerMock_GetBlockByLER_Call) Return(_a0 uint64, _a1 error) *L2BridgeSyncerMock_GetBlockByLER_Call { - _c.Call.Return(_a0, _a1) - return _c -} - -func (_c *L2BridgeSyncerMock_GetBlockByLER_Call) RunAndReturn(run func(context.Context, common.Hash) (uint64, error)) *L2BridgeSyncerMock_GetBlockByLER_Call { - _c.Call.Return(run) - return _c -} - -// GetBridgesPublished provides a mock function with given fields: ctx, fromBlock, toBlock -func (_m *L2BridgeSyncerMock) GetBridgesPublished(ctx context.Context, fromBlock uint64, toBlock uint64) ([]bridgesync.Bridge, error) { - ret := _m.Called(ctx, fromBlock, toBlock) - - if len(ret) == 0 { - panic("no return value specified for GetBridgesPublished") - } - - var r0 []bridgesync.Bridge - var r1 error - if rf, ok := ret.Get(0).(func(context.Context, uint64, uint64) ([]bridgesync.Bridge, error)); ok { - return rf(ctx, fromBlock, toBlock) - } - if rf, ok := ret.Get(0).(func(context.Context, uint64, uint64) []bridgesync.Bridge); ok { - r0 = rf(ctx, fromBlock, toBlock) - } else { - if ret.Get(0) != nil { - r0 = ret.Get(0).([]bridgesync.Bridge) - } - } - - if rf, ok := ret.Get(1).(func(context.Context, uint64, uint64) error); ok { - r1 = rf(ctx, fromBlock, toBlock) - } else { - r1 = ret.Error(1) - } - - return r0, r1 -} - -// L2BridgeSyncerMock_GetBridgesPublished_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GetBridgesPublished' -type L2BridgeSyncerMock_GetBridgesPublished_Call struct { - *mock.Call -} - -// GetBridgesPublished is a helper method to define mock.On call -// - ctx context.Context -// - fromBlock uint64 -// - toBlock uint64 -func (_e *L2BridgeSyncerMock_Expecter) GetBridgesPublished(ctx interface{}, fromBlock interface{}, toBlock interface{}) *L2BridgeSyncerMock_GetBridgesPublished_Call { - return &L2BridgeSyncerMock_GetBridgesPublished_Call{Call: _e.mock.On("GetBridgesPublished", ctx, fromBlock, toBlock)} -} - -func (_c *L2BridgeSyncerMock_GetBridgesPublished_Call) Run(run func(ctx context.Context, fromBlock uint64, toBlock uint64)) *L2BridgeSyncerMock_GetBridgesPublished_Call { - _c.Call.Run(func(args mock.Arguments) { - run(args[0].(context.Context), args[1].(uint64), args[2].(uint64)) - }) - return _c -} - -func (_c *L2BridgeSyncerMock_GetBridgesPublished_Call) Return(_a0 []bridgesync.Bridge, _a1 error) *L2BridgeSyncerMock_GetBridgesPublished_Call { - _c.Call.Return(_a0, _a1) - return _c -} - -func (_c *L2BridgeSyncerMock_GetBridgesPublished_Call) RunAndReturn(run func(context.Context, uint64, uint64) ([]bridgesync.Bridge, error)) *L2BridgeSyncerMock_GetBridgesPublished_Call { - _c.Call.Return(run) - return _c -} - -// GetClaims provides a mock function with given fields: ctx, fromBlock, toBlock -func (_m *L2BridgeSyncerMock) GetClaims(ctx context.Context, fromBlock uint64, toBlock uint64) ([]bridgesync.Claim, error) { - ret := _m.Called(ctx, fromBlock, toBlock) - - if len(ret) == 0 { - panic("no return value specified for GetClaims") - } - - var r0 []bridgesync.Claim - var r1 error - if rf, ok := ret.Get(0).(func(context.Context, uint64, uint64) ([]bridgesync.Claim, error)); ok { - return rf(ctx, fromBlock, toBlock) - } - if rf, ok := ret.Get(0).(func(context.Context, uint64, uint64) []bridgesync.Claim); ok { - r0 = rf(ctx, fromBlock, toBlock) - } else { - if ret.Get(0) != nil { - r0 = ret.Get(0).([]bridgesync.Claim) - } - } - - if rf, ok := ret.Get(1).(func(context.Context, uint64, uint64) error); ok { - r1 = rf(ctx, fromBlock, toBlock) - } else { - r1 = ret.Error(1) - } - - return r0, r1 -} - -// L2BridgeSyncerMock_GetClaims_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GetClaims' -type L2BridgeSyncerMock_GetClaims_Call struct { - *mock.Call -} - -// GetClaims is a helper method to define mock.On call -// - ctx context.Context -// - fromBlock uint64 -// - toBlock uint64 -func (_e *L2BridgeSyncerMock_Expecter) GetClaims(ctx interface{}, fromBlock interface{}, toBlock interface{}) *L2BridgeSyncerMock_GetClaims_Call { - return &L2BridgeSyncerMock_GetClaims_Call{Call: _e.mock.On("GetClaims", ctx, fromBlock, toBlock)} -} - -func (_c *L2BridgeSyncerMock_GetClaims_Call) Run(run func(ctx context.Context, fromBlock uint64, toBlock uint64)) *L2BridgeSyncerMock_GetClaims_Call { - _c.Call.Run(func(args mock.Arguments) { - run(args[0].(context.Context), args[1].(uint64), args[2].(uint64)) - }) - return _c -} - -func (_c *L2BridgeSyncerMock_GetClaims_Call) Return(_a0 []bridgesync.Claim, _a1 error) *L2BridgeSyncerMock_GetClaims_Call { - _c.Call.Return(_a0, _a1) - return _c -} - -func (_c *L2BridgeSyncerMock_GetClaims_Call) RunAndReturn(run func(context.Context, uint64, uint64) ([]bridgesync.Claim, error)) *L2BridgeSyncerMock_GetClaims_Call { - _c.Call.Return(run) - return _c -} - -// GetExitRootByIndex provides a mock function with given fields: ctx, index -func (_m *L2BridgeSyncerMock) GetExitRootByIndex(ctx context.Context, index uint32) (treetypes.Root, error) { - ret := _m.Called(ctx, index) - - if len(ret) == 0 { - panic("no return value specified for GetExitRootByIndex") - } - - var r0 treetypes.Root - var r1 error - if rf, ok := ret.Get(0).(func(context.Context, uint32) (treetypes.Root, error)); ok { - return rf(ctx, index) - } - if rf, ok := ret.Get(0).(func(context.Context, uint32) treetypes.Root); ok { - r0 = rf(ctx, index) - } else { - r0 = ret.Get(0).(treetypes.Root) - } - - if rf, ok := ret.Get(1).(func(context.Context, uint32) error); ok { - r1 = rf(ctx, index) - } else { - r1 = ret.Error(1) - } - - return r0, r1 -} - -// L2BridgeSyncerMock_GetExitRootByIndex_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GetExitRootByIndex' -type L2BridgeSyncerMock_GetExitRootByIndex_Call struct { - *mock.Call -} - -// GetExitRootByIndex is a helper method to define mock.On call -// - ctx context.Context -// - index uint32 -func (_e *L2BridgeSyncerMock_Expecter) GetExitRootByIndex(ctx interface{}, index interface{}) *L2BridgeSyncerMock_GetExitRootByIndex_Call { - return &L2BridgeSyncerMock_GetExitRootByIndex_Call{Call: _e.mock.On("GetExitRootByIndex", ctx, index)} -} - -func (_c *L2BridgeSyncerMock_GetExitRootByIndex_Call) Run(run func(ctx context.Context, index uint32)) *L2BridgeSyncerMock_GetExitRootByIndex_Call { - _c.Call.Run(func(args mock.Arguments) { - run(args[0].(context.Context), args[1].(uint32)) - }) - return _c -} - -func (_c *L2BridgeSyncerMock_GetExitRootByIndex_Call) Return(_a0 treetypes.Root, _a1 error) *L2BridgeSyncerMock_GetExitRootByIndex_Call { - _c.Call.Return(_a0, _a1) - return _c -} - -func (_c *L2BridgeSyncerMock_GetExitRootByIndex_Call) RunAndReturn(run func(context.Context, uint32) (treetypes.Root, error)) *L2BridgeSyncerMock_GetExitRootByIndex_Call { - _c.Call.Return(run) - return _c -} - -// GetLastProcessedBlock provides a mock function with given fields: ctx -func (_m *L2BridgeSyncerMock) GetLastProcessedBlock(ctx context.Context) (uint64, error) { - ret := _m.Called(ctx) - - if len(ret) == 0 { - panic("no return value specified for GetLastProcessedBlock") - } - - var r0 uint64 - var r1 error - if rf, ok := ret.Get(0).(func(context.Context) (uint64, error)); ok { - return rf(ctx) - } - if rf, ok := ret.Get(0).(func(context.Context) uint64); ok { - r0 = rf(ctx) - } else { - r0 = ret.Get(0).(uint64) - } - - if rf, ok := ret.Get(1).(func(context.Context) error); ok { - r1 = rf(ctx) - } else { - r1 = ret.Error(1) - } - - return r0, r1 -} - -// L2BridgeSyncerMock_GetLastProcessedBlock_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GetLastProcessedBlock' -type L2BridgeSyncerMock_GetLastProcessedBlock_Call struct { - *mock.Call -} - -// GetLastProcessedBlock is a helper method to define mock.On call -// - ctx context.Context -func (_e *L2BridgeSyncerMock_Expecter) GetLastProcessedBlock(ctx interface{}) *L2BridgeSyncerMock_GetLastProcessedBlock_Call { - return &L2BridgeSyncerMock_GetLastProcessedBlock_Call{Call: _e.mock.On("GetLastProcessedBlock", ctx)} -} - -func (_c *L2BridgeSyncerMock_GetLastProcessedBlock_Call) Run(run func(ctx context.Context)) *L2BridgeSyncerMock_GetLastProcessedBlock_Call { - _c.Call.Run(func(args mock.Arguments) { - run(args[0].(context.Context)) - }) - return _c -} - -func (_c *L2BridgeSyncerMock_GetLastProcessedBlock_Call) Return(_a0 uint64, _a1 error) *L2BridgeSyncerMock_GetLastProcessedBlock_Call { - _c.Call.Return(_a0, _a1) - return _c -} - -func (_c *L2BridgeSyncerMock_GetLastProcessedBlock_Call) RunAndReturn(run func(context.Context) (uint64, error)) *L2BridgeSyncerMock_GetLastProcessedBlock_Call { - _c.Call.Return(run) - return _c -} - -// OriginNetwork provides a mock function with given fields: -func (_m *L2BridgeSyncerMock) OriginNetwork() uint32 { - ret := _m.Called() - - if len(ret) == 0 { - panic("no return value specified for OriginNetwork") - } - - var r0 uint32 - if rf, ok := ret.Get(0).(func() uint32); ok { - r0 = rf() - } else { - r0 = ret.Get(0).(uint32) - } - - return r0 -} - -// L2BridgeSyncerMock_OriginNetwork_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'OriginNetwork' -type L2BridgeSyncerMock_OriginNetwork_Call struct { - *mock.Call -} - -// OriginNetwork is a helper method to define mock.On call -func (_e *L2BridgeSyncerMock_Expecter) OriginNetwork() *L2BridgeSyncerMock_OriginNetwork_Call { - return &L2BridgeSyncerMock_OriginNetwork_Call{Call: _e.mock.On("OriginNetwork")} -} - -func (_c *L2BridgeSyncerMock_OriginNetwork_Call) Run(run func()) *L2BridgeSyncerMock_OriginNetwork_Call { - _c.Call.Run(func(args mock.Arguments) { - run() - }) - return _c -} - -func (_c *L2BridgeSyncerMock_OriginNetwork_Call) Return(_a0 uint32) *L2BridgeSyncerMock_OriginNetwork_Call { - _c.Call.Return(_a0) - return _c -} - -func (_c *L2BridgeSyncerMock_OriginNetwork_Call) RunAndReturn(run func() uint32) *L2BridgeSyncerMock_OriginNetwork_Call { - _c.Call.Return(run) - return _c -} - -// NewL2BridgeSyncerMock creates a new instance of L2BridgeSyncerMock. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. -// The first argument is typically a *testing.T value. -func NewL2BridgeSyncerMock(t interface { - mock.TestingT - Cleanup(func()) -}) *L2BridgeSyncerMock { - mock := &L2BridgeSyncerMock{} - mock.Mock.Test(t) - - t.Cleanup(func() { mock.AssertExpectations(t) }) - - return mock -} diff --git a/aggsender/mocks/mock_logger.go b/aggsender/mocks/mock_logger.go deleted file mode 100644 index 5b0eb4e9..00000000 --- a/aggsender/mocks/mock_logger.go +++ /dev/null @@ -1,290 +0,0 @@ -// Code generated by mockery. DO NOT EDIT. - -package mocks - -import mock "github.com/stretchr/testify/mock" - -// LoggerMock is an autogenerated mock type for the Logger type -type LoggerMock struct { - mock.Mock -} - -type LoggerMock_Expecter struct { - mock *mock.Mock -} - -func (_m *LoggerMock) EXPECT() *LoggerMock_Expecter { - return &LoggerMock_Expecter{mock: &_m.Mock} -} - -// Debug provides a mock function with given fields: args -func (_m *LoggerMock) Debug(args ...interface{}) { - var _ca []interface{} - _ca = append(_ca, args...) - _m.Called(_ca...) -} - -// LoggerMock_Debug_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Debug' -type LoggerMock_Debug_Call struct { - *mock.Call -} - -// Debug is a helper method to define mock.On call -// - args ...interface{} -func (_e *LoggerMock_Expecter) Debug(args ...interface{}) *LoggerMock_Debug_Call { - return &LoggerMock_Debug_Call{Call: _e.mock.On("Debug", - append([]interface{}{}, args...)...)} -} - -func (_c *LoggerMock_Debug_Call) Run(run func(args ...interface{})) *LoggerMock_Debug_Call { - _c.Call.Run(func(args mock.Arguments) { - variadicArgs := make([]interface{}, len(args)-0) - for i, a := range args[0:] { - if a != nil { - variadicArgs[i] = a.(interface{}) - } - } - run(variadicArgs...) - }) - return _c -} - -func (_c *LoggerMock_Debug_Call) Return() *LoggerMock_Debug_Call { - _c.Call.Return() - return _c -} - -func (_c *LoggerMock_Debug_Call) RunAndReturn(run func(...interface{})) *LoggerMock_Debug_Call { - _c.Call.Return(run) - return _c -} - -// Debugf provides a mock function with given fields: format, args -func (_m *LoggerMock) Debugf(format string, args ...interface{}) { - var _ca []interface{} - _ca = append(_ca, format) - _ca = append(_ca, args...) - _m.Called(_ca...) -} - -// LoggerMock_Debugf_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Debugf' -type LoggerMock_Debugf_Call struct { - *mock.Call -} - -// Debugf is a helper method to define mock.On call -// - format string -// - args ...interface{} -func (_e *LoggerMock_Expecter) Debugf(format interface{}, args ...interface{}) *LoggerMock_Debugf_Call { - return &LoggerMock_Debugf_Call{Call: _e.mock.On("Debugf", - append([]interface{}{format}, args...)...)} -} - -func (_c *LoggerMock_Debugf_Call) Run(run func(format string, args ...interface{})) *LoggerMock_Debugf_Call { - _c.Call.Run(func(args mock.Arguments) { - variadicArgs := make([]interface{}, len(args)-1) - for i, a := range args[1:] { - if a != nil { - variadicArgs[i] = a.(interface{}) - } - } - run(args[0].(string), variadicArgs...) - }) - return _c -} - -func (_c *LoggerMock_Debugf_Call) Return() *LoggerMock_Debugf_Call { - _c.Call.Return() - return _c -} - -func (_c *LoggerMock_Debugf_Call) RunAndReturn(run func(string, ...interface{})) *LoggerMock_Debugf_Call { - _c.Call.Return(run) - return _c -} - -// Error provides a mock function with given fields: args -func (_m *LoggerMock) Error(args ...interface{}) { - var _ca []interface{} - _ca = append(_ca, args...) - _m.Called(_ca...) -} - -// LoggerMock_Error_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Error' -type LoggerMock_Error_Call struct { - *mock.Call -} - -// Error is a helper method to define mock.On call -// - args ...interface{} -func (_e *LoggerMock_Expecter) Error(args ...interface{}) *LoggerMock_Error_Call { - return &LoggerMock_Error_Call{Call: _e.mock.On("Error", - append([]interface{}{}, args...)...)} -} - -func (_c *LoggerMock_Error_Call) Run(run func(args ...interface{})) *LoggerMock_Error_Call { - _c.Call.Run(func(args mock.Arguments) { - variadicArgs := make([]interface{}, len(args)-0) - for i, a := range args[0:] { - if a != nil { - variadicArgs[i] = a.(interface{}) - } - } - run(variadicArgs...) - }) - return _c -} - -func (_c *LoggerMock_Error_Call) Return() *LoggerMock_Error_Call { - _c.Call.Return() - return _c -} - -func (_c *LoggerMock_Error_Call) RunAndReturn(run func(...interface{})) *LoggerMock_Error_Call { - _c.Call.Return(run) - return _c -} - -// Errorf provides a mock function with given fields: format, args -func (_m *LoggerMock) Errorf(format string, args ...interface{}) { - var _ca []interface{} - _ca = append(_ca, format) - _ca = append(_ca, args...) - _m.Called(_ca...) -} - -// LoggerMock_Errorf_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Errorf' -type LoggerMock_Errorf_Call struct { - *mock.Call -} - -// Errorf is a helper method to define mock.On call -// - format string -// - args ...interface{} -func (_e *LoggerMock_Expecter) Errorf(format interface{}, args ...interface{}) *LoggerMock_Errorf_Call { - return &LoggerMock_Errorf_Call{Call: _e.mock.On("Errorf", - append([]interface{}{format}, args...)...)} -} - -func (_c *LoggerMock_Errorf_Call) Run(run func(format string, args ...interface{})) *LoggerMock_Errorf_Call { - _c.Call.Run(func(args mock.Arguments) { - variadicArgs := make([]interface{}, len(args)-1) - for i, a := range args[1:] { - if a != nil { - variadicArgs[i] = a.(interface{}) - } - } - run(args[0].(string), variadicArgs...) - }) - return _c -} - -func (_c *LoggerMock_Errorf_Call) Return() *LoggerMock_Errorf_Call { - _c.Call.Return() - return _c -} - -func (_c *LoggerMock_Errorf_Call) RunAndReturn(run func(string, ...interface{})) *LoggerMock_Errorf_Call { - _c.Call.Return(run) - return _c -} - -// Info provides a mock function with given fields: args -func (_m *LoggerMock) Info(args ...interface{}) { - var _ca []interface{} - _ca = append(_ca, args...) - _m.Called(_ca...) -} - -// LoggerMock_Info_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Info' -type LoggerMock_Info_Call struct { - *mock.Call -} - -// Info is a helper method to define mock.On call -// - args ...interface{} -func (_e *LoggerMock_Expecter) Info(args ...interface{}) *LoggerMock_Info_Call { - return &LoggerMock_Info_Call{Call: _e.mock.On("Info", - append([]interface{}{}, args...)...)} -} - -func (_c *LoggerMock_Info_Call) Run(run func(args ...interface{})) *LoggerMock_Info_Call { - _c.Call.Run(func(args mock.Arguments) { - variadicArgs := make([]interface{}, len(args)-0) - for i, a := range args[0:] { - if a != nil { - variadicArgs[i] = a.(interface{}) - } - } - run(variadicArgs...) - }) - return _c -} - -func (_c *LoggerMock_Info_Call) Return() *LoggerMock_Info_Call { - _c.Call.Return() - return _c -} - -func (_c *LoggerMock_Info_Call) RunAndReturn(run func(...interface{})) *LoggerMock_Info_Call { - _c.Call.Return(run) - return _c -} - -// Infof provides a mock function with given fields: format, args -func (_m *LoggerMock) Infof(format string, args ...interface{}) { - var _ca []interface{} - _ca = append(_ca, format) - _ca = append(_ca, args...) - _m.Called(_ca...) -} - -// LoggerMock_Infof_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Infof' -type LoggerMock_Infof_Call struct { - *mock.Call -} - -// Infof is a helper method to define mock.On call -// - format string -// - args ...interface{} -func (_e *LoggerMock_Expecter) Infof(format interface{}, args ...interface{}) *LoggerMock_Infof_Call { - return &LoggerMock_Infof_Call{Call: _e.mock.On("Infof", - append([]interface{}{format}, args...)...)} -} - -func (_c *LoggerMock_Infof_Call) Run(run func(format string, args ...interface{})) *LoggerMock_Infof_Call { - _c.Call.Run(func(args mock.Arguments) { - variadicArgs := make([]interface{}, len(args)-1) - for i, a := range args[1:] { - if a != nil { - variadicArgs[i] = a.(interface{}) - } - } - run(args[0].(string), variadicArgs...) - }) - return _c -} - -func (_c *LoggerMock_Infof_Call) Return() *LoggerMock_Infof_Call { - _c.Call.Return() - return _c -} - -func (_c *LoggerMock_Infof_Call) RunAndReturn(run func(string, ...interface{})) *LoggerMock_Infof_Call { - _c.Call.Return(run) - return _c -} - -// NewLoggerMock creates a new instance of LoggerMock. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. -// The first argument is typically a *testing.T value. -func NewLoggerMock(t interface { - mock.TestingT - Cleanup(func()) -}) *LoggerMock { - mock := &LoggerMock{} - mock.Mock.Test(t) - - t.Cleanup(func() { mock.AssertExpectations(t) }) - - return mock -} diff --git a/aggsender/types/block_notifier.go b/aggsender/types/block_notifier.go new file mode 100644 index 00000000..475abc1b --- /dev/null +++ b/aggsender/types/block_notifier.go @@ -0,0 +1,15 @@ +package types + +import "github.com/0xPolygon/cdk/etherman" + +type EventNewBlock struct { + BlockNumber uint64 + BlockFinalityType etherman.BlockNumberFinality +} + +// BlockNotifier is the interface that wraps the basic methods to notify a new block. +type BlockNotifier interface { + // NotifyEpochStarted notifies the epoch has started. + Subscribe(id string) <-chan EventNewBlock + String() string +} diff --git a/aggsender/types/epoch_notifier.go b/aggsender/types/epoch_notifier.go new file mode 100644 index 00000000..426ad362 --- /dev/null +++ b/aggsender/types/epoch_notifier.go @@ -0,0 +1,25 @@ +package types + +import ( + "context" + "fmt" +) + +// EpochEvent is the event that notifies the neear end epoch +type EpochEvent struct { + Epoch uint64 + // ExtraInfo if a detailed information about the epoch that depends on implementation + ExtraInfo fmt.Stringer +} + +func (e EpochEvent) String() string { + return fmt.Sprintf("EpochEvent: epoch=%d extra=%s", e.Epoch, e.ExtraInfo) +} + +type EpochNotifier interface { + // NotifyEpochStarted notifies the epoch is close to end. + Subscribe(id string) <-chan EpochEvent + // Start starts the notifier synchronously + Start(ctx context.Context) + String() string +} diff --git a/aggsender/types/generic_subscriber.go b/aggsender/types/generic_subscriber.go new file mode 100644 index 00000000..67038c5c --- /dev/null +++ b/aggsender/types/generic_subscriber.go @@ -0,0 +1,6 @@ +package types + +type GenericSubscriber[T any] interface { + Subscribe(subscriberName string) <-chan T + Publish(data T) +} diff --git a/aggsender/types/types.go b/aggsender/types/types.go index 46d31176..d9e0b2e7 100644 --- a/aggsender/types/types.go +++ b/aggsender/types/types.go @@ -47,6 +47,8 @@ type Logger interface { Infof(format string, args ...interface{}) Error(args ...interface{}) Errorf(format string, args ...interface{}) + Warn(args ...interface{}) + Warnf(format string, args ...interface{}) Debug(args ...interface{}) Debugf(format string, args ...interface{}) } diff --git a/cmd/run.go b/cmd/run.go index c30da739..6042e935 100644 --- a/cmd/run.go +++ b/cmd/run.go @@ -125,6 +125,7 @@ func start(cliCtx *cli.Context) error { aggsender, err := createAggSender( cliCtx.Context, c.AggSender, + l1Client, l1InfoTreeSync, l2BridgeSync, ) @@ -144,13 +145,35 @@ func start(cliCtx *cli.Context) error { func createAggSender( ctx context.Context, cfg aggsender.Config, + l1EthClient *ethclient.Client, l1InfoTreeSync *l1infotreesync.L1InfoTreeSync, - l2Syncer *bridgesync.BridgeSync, -) (*aggsender.AggSender, error) { + l2Syncer *bridgesync.BridgeSync) (*aggsender.AggSender, error) { logger := log.WithFields("module", cdkcommon.AGGSENDER) agglayerClient := agglayer.NewAggLayerClient(cfg.AggLayerURL) + blockNotifier, err := aggsender.NewBlockNotifierPolling(l1EthClient, aggsender.ConfigBlockNotifierPolling{ + BlockFinalityType: etherman.BlockNumberFinality(cfg.BlockFinality), + CheckNewBlockInterval: aggsender.AutomaticBlockInterval, + }, logger, nil) + if err != nil { + return nil, err + } - return aggsender.New(ctx, logger, cfg, agglayerClient, l1InfoTreeSync, l2Syncer) + notifierCfg, err := aggsender.NewConfigEpochNotifierPerBlock(agglayerClient, cfg.EpochNotificationPercentage) + if err != nil { + return nil, fmt.Errorf("cant generate config for Epoch Notifier because: %w", err) + } + epochNotifier, err := aggsender.NewEpochNotifierPerBlock( + blockNotifier, + logger, + *notifierCfg, nil) + if err != nil { + return nil, err + } + log.Infof("Starting blockNotifier: %s", blockNotifier.String()) + go blockNotifier.Start(ctx) + log.Infof("Starting epochNotifier: %s", epochNotifier.String()) + go epochNotifier.Start(ctx) + return aggsender.New(ctx, logger, cfg, agglayerClient, l1InfoTreeSync, l2Syncer, epochNotifier) } func createAggregator(ctx context.Context, c config.Config, runMigrations bool) *aggregator.Aggregator { diff --git a/config/default.go b/config/default.go index bbf4d2e0..d7188e43 100644 --- a/config/default.go +++ b/config/default.go @@ -7,6 +7,7 @@ L1URL = "http://localhost:8545" L2URL = "http://localhost:8123" AggLayerURL = "https://agglayer-dev.polygon.technology" + ForkId = 9 ContractVersions = "elderberry" IsValidiumMode = false @@ -215,7 +216,7 @@ DBPath = "{{PathRWData}}/reorgdetectorl2.sqlite" DBPath = "{{PathRWData}}/L1InfoTreeSync.sqlite" GlobalExitRootAddr="{{NetworkConfig.L1.GlobalExitRootManagerAddr}}" RollupManagerAddr = "{{NetworkConfig.L1.RollupManagerAddr}}" -SyncBlockChunkSize=10 +SyncBlockChunkSize=100 BlockFinality="LatestBlock" URLRPCL1="{{L1URL}}" WaitForNewBlocksPeriod="100ms" @@ -340,5 +341,7 @@ AggsenderPrivateKey = {Path = "{{SequencerPrivateKeyPath}}", Password = "{{Seque BlockGetInterval = "2s" URLRPCL2="{{L2URL}}" CheckSettledInterval = "2s" +BlockFinality = "LatestBlock" +EpochNotificationPercentage = 50 SaveCertificatesToFiles = false ` diff --git a/go.mod b/go.mod index 0061c72f..430e8326 100644 --- a/go.mod +++ b/go.mod @@ -33,7 +33,7 @@ require ( go.uber.org/zap v1.27.0 golang.org/x/crypto v0.27.0 golang.org/x/net v0.29.0 - golang.org/x/sync v0.8.0 + golang.org/x/sync v0.9.0 google.golang.org/grpc v1.64.0 google.golang.org/protobuf v1.34.2 modernc.org/sqlite v1.32.0 @@ -151,7 +151,7 @@ require ( github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1 // indirect go.opentelemetry.io/otel/trace v1.24.0 // indirect go.uber.org/multierr v1.10.0 // indirect - golang.org/x/exp v0.0.0-20231110203233-9a3e6036ecaa // indirect + golang.org/x/exp v0.0.0-20241108190413-2d47ceb2692f // indirect golang.org/x/sys v0.25.0 // indirect golang.org/x/text v0.18.0 // indirect golang.org/x/time v0.5.0 // indirect diff --git a/go.sum b/go.sum index ceb905ac..3ad80938 100644 --- a/go.sum +++ b/go.sum @@ -6,6 +6,7 @@ github.com/0xPolygon/cdk-rpc v0.0.0-20241004114257-6c3cb6eebfb6 h1:FXL/rcO7/GtZ3 github.com/0xPolygon/cdk-rpc v0.0.0-20241004114257-6c3cb6eebfb6/go.mod h1:2scWqMMufrQXu7TikDgQ3BsyaKoX8qP26D6E262vSOg= github.com/0xPolygon/zkevm-ethtx-manager v0.2.1 h1:2Yb+KdJFMpVrS9LIkd658XiWuN+MCTs7SgeWaopXScg= github.com/0xPolygon/zkevm-ethtx-manager v0.2.1/go.mod h1:lqQmzSo2OXEZItD0R4Cd+lqKFxphXEWgqHefVcGDZZc= +github.com/0xPolygonHermez/zkevm-data-streamer v0.2.7/go.mod h1:7nM7Ihk+fTG1TQPwdZoGOYd3wprqqyIyjtS514uHzWE= github.com/0xPolygonHermez/zkevm-synchronizer-l1 v1.0.5 h1:YmnhuCl349MoNASN0fMeGKU1o9HqJhiZkfMsA/1cTRA= github.com/0xPolygonHermez/zkevm-synchronizer-l1 v1.0.5/go.mod h1:X4Su/M/+hSISqdl9yomKlRsbTyuZHsRohporyHsP8gg= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= @@ -482,6 +483,8 @@ golang.org/x/crypto v0.27.0 h1:GXm2NjJrPaiv/h1tb2UH8QfgC/hOf/+z0p6PT8o1w7A= golang.org/x/crypto v0.27.0/go.mod h1:1Xngt8kV6Dvbssa53Ziq6Eqn0HqbZi5Z6R0ZpwQzt70= golang.org/x/exp v0.0.0-20231110203233-9a3e6036ecaa h1:FRnLl4eNAQl8hwxVVC17teOw8kdjVDVAiFMtgUdTSRQ= golang.org/x/exp v0.0.0-20231110203233-9a3e6036ecaa/go.mod h1:zk2irFbV9DP96SEBUUAy67IdHUaZuSnrz1n472HUCLE= +golang.org/x/exp v0.0.0-20241108190413-2d47ceb2692f h1:XdNn9LlyWAhLVp6P/i8QYBW+hlyhrhei9uErw2B5GJo= +golang.org/x/exp v0.0.0-20241108190413-2d47ceb2692f/go.mod h1:D5SMRVC3C2/4+F/DB1wZsLRnSNimn2Sp/NPsCrsv8ak= golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= @@ -489,6 +492,7 @@ golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.19.0 h1:fEdghXQSo20giMthA7cd28ZC+jts4amQ3YMXiP5oMQ8= golang.org/x/mod v0.19.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= +golang.org/x/mod v0.22.0 h1:D4nJWe9zXqHOmWqj4VMOJhvzj7bEZg4wEYa759z1pH4= golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= @@ -510,6 +514,8 @@ golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ= golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sync v0.9.0 h1:fEo0HyrW1GIgZdpbhCRO0PkJajUS5H9IFUztCgEo2jQ= +golang.org/x/sync v0.9.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -571,6 +577,7 @@ golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4f golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.23.0 h1:SGsXPZ+2l4JsgaCKkx+FQ9YZ5XEtA1GZYuoDjenLjvg= golang.org/x/tools v0.23.0/go.mod h1:pnu6ufv6vQkll6szChhK3C3L/ruaIv5eBeztNG8wtsI= +golang.org/x/tools v0.27.0 h1:qEKojBykQkQ4EynWy4S8Weg69NumxKdn40Fce3uc/8o= golang.org/x/xerrors v0.0.0-20190410155217-1f06c39b4373/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20190513163551-3ee3066db522/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= diff --git a/scripts/local_config b/scripts/local_config index 09e0167a..5830b6e6 100755 --- a/scripts/local_config +++ b/scripts/local_config @@ -447,4 +447,4 @@ EOF echo " -----------------------------------------------------------" echo " " echo " - rembember to clean previous execution data: " -echo " rm -Rf ${path_rw_data}/*" +echo " rm -Rf ${zkevm_path_rw_data}/*" diff --git a/test/Makefile b/test/Makefile index 51a475ed..2435730c 100644 --- a/test/Makefile +++ b/test/Makefile @@ -3,6 +3,8 @@ generate-mocks: generate-mocks-bridgesync generate-mocks-reorgdetector generate- generate-mocks-da generate-mocks-l1infotreesync generate-mocks-helpers \ generate-mocks-sync generate-mocks-l1infotreesync generate-mocks-aggregator \ generate-mocks-aggsender generate-mocks-agglayer generate-mocks-bridgesync + generate-mocks-sync generate-mocks-l1infotreesync generate-mocks-aggregator \ + generate-mocks-aggsender generate-mocks-agglayer generate-mocks-bridgesync .PHONY: generate-mocks-bridgesync generate-mocks-bridgesync: ## Generates mocks for bridgesync, using mockery tool @@ -61,11 +63,8 @@ generate-mocks-aggregator: ## Generates mocks for aggregator, using mockery tool .PHONY: generate-mocks-aggsender generate-mocks-aggsender: ## Generates mocks for aggsender, using mockery tool - export "GOROOT=$$(go env GOROOT)" && $$(go env GOPATH)/bin/mockery --name=L1InfoTreeSyncer --dir=../aggsender/types --output=../aggsender/mocks --outpkg=mocks --structname=L1InfoTreeSyncerMock --filename=mock_l1infotree_syncer.go ${COMMON_MOCKERY_PARAMS} - export "GOROOT=$$(go env GOROOT)" && $$(go env GOPATH)/bin/mockery --name=L2BridgeSyncer --dir=../aggsender/types --output=../aggsender/mocks --outpkg=mocks --structname=L2BridgeSyncerMock --filename=mock_l2bridge_syncer.go ${COMMON_MOCKERY_PARAMS} - export "GOROOT=$$(go env GOROOT)" && $$(go env GOPATH)/bin/mockery --name=Logger --dir=../aggsender/types --output=../aggsender/mocks --outpkg=mocks --structname=LoggerMock --filename=mock_logger.go ${COMMON_MOCKERY_PARAMS} - export "GOROOT=$$(go env GOROOT)" && $$(go env GOPATH)/bin/mockery --name=AggSenderStorage --dir=../aggsender/db --output=../aggsender/mocks --outpkg=mocks --structname=AggSenderStorageMock --filename=mock_aggsender_storage.go ${COMMON_MOCKERY_PARAMS} - export "GOROOT=$$(go env GOROOT)" && $$(go env GOPATH)/bin/mockery --name=EthClient --dir=../aggsender/types --output=../aggsender/mocks --outpkg=mocks --structname=EthClientMock --filename=mock_eth_client.go ${COMMON_MOCKERY_PARAMS} + rm -Rf ../aggsender/mocks + export "GOROOT=$$(go env GOROOT)" && $$(go env GOPATH)/bin/mockery --all --case snake --dir ../aggsender --output ../aggsender/mocks --outpkg mocks ${COMMON_MOCKERY_PARAMS} .PHONY: generate-mocks-agglayer generate-mocks-agglayer: ## Generates mocks for agglayer, using mockery tool diff --git a/test/bridge-e2e.bats b/test/bridge-e2e.bats index ed599c7d..e754ef70 100644 --- a/test/bridge-e2e.bats +++ b/test/bridge-e2e.bats @@ -48,23 +48,35 @@ setup() { } @test "Native gas token deposit to WETH" { + destination_addr=$sender_addr local initial_receiver_balance=$(cast call --rpc-url "$l2_rpc_url" "$weth_token_addr" "$balance_of_fn_sig" "$destination_addr" | awk '{print $1}') echo "Initial receiver balance of native token on L2 $initial_receiver_balance" >&3 - echo "Running LxLy deposit" >&3 + echo "=== Running LxLy deposit on L1 to network: $l2_rpc_network_id native_token: $native_token_addr" >&3 + + destination_net=$l2_rpc_network_id run bridgeAsset "$native_token_addr" "$l1_rpc_url" assert_success - echo "Running LxLy claim" >&3 + echo "=== Running LxLy claim on L2" >&3 timeout="120" claim_frequency="10" run wait_for_claim "$timeout" "$claim_frequency" "$l2_rpc_url" assert_success run verify_balance "$l2_rpc_url" "$weth_token_addr" "$destination_addr" "$initial_receiver_balance" "$ether_value" - if [ $status -eq 0 ]; then - break - fi + assert_success + + echo "=== bridgeAsset L2 WETH: $weth_token_addr to L1 ETH" >&3 + destination_addr=$sender_addr + destination_net=0 + run bridgeAsset "$weth_token_addr" "$l2_rpc_url" + assert_success + + echo "=== Claim in L1 ETH" >&3 + timeout="400" + claim_frequency="60" + run wait_for_claim "$timeout" "$claim_frequency" "$l1_rpc_url" assert_success } diff --git a/test/config/kurtosis-cdk-node-config.toml.template b/test/config/kurtosis-cdk-node-config.toml.template index 508c1286..4069b350 100644 --- a/test/config/kurtosis-cdk-node-config.toml.template +++ b/test/config/kurtosis-cdk-node-config.toml.template @@ -25,8 +25,6 @@ AggregatorPrivateKeyPassword = "{{.zkevm_l2_keystore_password}}" SenderProofToL1Addr = "{{.zkevm_l2_agglayer_address}}" polygonBridgeAddr = "{{.zkevm_bridge_address}}" - -RPCURL = "http://{{.l2_rpc_name}}{{.deployment_suffix}}:{{.zkevm_rpc_http_port}}" WitnessURL = "http://{{.l2_rpc_name}}{{.deployment_suffix}}:{{.zkevm_rpc_http_port}}" diff --git a/test/helpers/lxly-bridge-test.bash b/test/helpers/lxly-bridge-test.bash index 7b3cb008..ad5ab943 100644 --- a/test/helpers/lxly-bridge-test.bash +++ b/test/helpers/lxly-bridge-test.bash @@ -36,6 +36,7 @@ function claim() { readonly bridge_deposit_file=$(mktemp) readonly claimable_deposit_file=$(mktemp) echo "Getting full list of deposits" >&3 + echo " curl -s \"$bridge_api_url/bridges/$destination_addr?limit=100&offset=0\"" >&3 curl -s "$bridge_api_url/bridges/$destination_addr?limit=100&offset=0" | jq '.' | tee $bridge_deposit_file echo "Looking for claimable deposits" >&3 From 33964476d995606c56dd62b97997026a8525948b Mon Sep 17 00:00:00 2001 From: Rachit Sonthalia <54906134+rachit77@users.noreply.github.com> Date: Tue, 12 Nov 2024 17:42:57 +0530 Subject: [PATCH 03/23] refact: GetSequence method (#169) --- .../datacommittee/datacommittee.go | 33 ++++++------------- 1 file changed, 10 insertions(+), 23 deletions(-) diff --git a/dataavailability/datacommittee/datacommittee.go b/dataavailability/datacommittee/datacommittee.go index 474c5934..369fc0fe 100644 --- a/dataavailability/datacommittee/datacommittee.go +++ b/dataavailability/datacommittee/datacommittee.go @@ -105,53 +105,40 @@ func (d *Backend) Init() error { return nil } -// GetSequence gets backend data one hash at a time. This should be optimized on the DAC side to get them all at once. +// GetSequence retrieves backend data by querying committee members for each hash concurrently. func (d *Backend) GetSequence(_ context.Context, hashes []common.Hash, _ []byte) ([][]byte, error) { - intialMember := d.selectedCommitteeMember + initialMember := d.selectedCommitteeMember - var found bool - for !found && intialMember != -1 { + var batchData [][]byte + for retries := 0; retries < len(d.committeeMembers); retries++ { member := d.committeeMembers[d.selectedCommitteeMember] d.logger.Infof("trying to get data from %s at %s", member.Addr.Hex(), member.URL) c := d.dataCommitteeClientFactory.New(member.URL) - dataMap, err := c.ListOffChainData(d.ctx, hashes) if err != nil { - d.logger.Warnf( - "error getting data from DAC node %s at %s: %s", - member.Addr.Hex(), member.URL, err, - ) + d.logger.Warnf("error getting data from DAC node %s at %s: %s", member.Addr.Hex(), member.URL, err) d.selectedCommitteeMember = (d.selectedCommitteeMember + 1) % len(d.committeeMembers) - if d.selectedCommitteeMember == intialMember { + if d.selectedCommitteeMember == initialMember { break } - continue } - batchData := make([][]byte, 0, len(hashes)) + batchData = make([][]byte, 0, len(hashes)) for _, hash := range hashes { actualTransactionsHash := crypto.Keccak256Hash(dataMap[hash]) if actualTransactionsHash != hash { - unexpectedHash := fmt.Errorf( - unexpectedHashTemplate, hash, actualTransactionsHash, - ) - d.logger.Warnf( - "error getting data from DAC node %s at %s: %s", - member.Addr.Hex(), member.URL, unexpectedHash, - ) + unexpectedHash := fmt.Errorf(unexpectedHashTemplate, hash, actualTransactionsHash) + d.logger.Warnf("error getting data from DAC node %s at %s: %s", member.Addr.Hex(), member.URL, unexpectedHash) d.selectedCommitteeMember = (d.selectedCommitteeMember + 1) % len(d.committeeMembers) - if d.selectedCommitteeMember == intialMember { + if d.selectedCommitteeMember == initialMember { break } - continue } - batchData = append(batchData, dataMap[hash]) } - return batchData, nil } From 05f82902dd8d90b7bcae567d1eb735b55736f2de Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Toni=20Ram=C3=ADrez?= <58293609+ToniRamirezM@users.noreply.github.com> Date: Tue, 12 Nov 2024 13:59:16 +0100 Subject: [PATCH 04/23] feat: remove sanity check (#178) (#179) --- sequencesender/txbuilder/banana_base.go | 20 +++-------- sequencesender/txbuilder/banana_base_test.go | 37 -------------------- 2 files changed, 4 insertions(+), 53 deletions(-) diff --git a/sequencesender/txbuilder/banana_base.go b/sequencesender/txbuilder/banana_base.go index 2868bb4b..ee21228d 100644 --- a/sequencesender/txbuilder/banana_base.go +++ b/sequencesender/txbuilder/banana_base.go @@ -149,21 +149,25 @@ func (t *TxBuilderBananaBase) NewSequence( counterL1InfoRoot, err := t.GetCounterL1InfoRoot(ctx, greatestL1Index) if err != nil { + log.Errorf("error getting CounterL1InfoRoot: %s", err) return nil, err } sequence.CounterL1InfoRoot = counterL1InfoRoot l1InfoRoot, err := t.getL1InfoRoot(sequence.CounterL1InfoRoot) if err != nil { + log.Errorf("error getting L1InfoRootMap: %s", err) return nil, err } err = t.CheckL1InfoTreeLeafCounterVsInitL1InfoMap(ctx, sequence.CounterL1InfoRoot) if err != nil { + log.Errorf("error checking L1InfoTreeLeafCounterVsInitL1InfoMap: %s", err) return nil, err } sequence.L1InfoRoot = l1InfoRoot accInputHash, err := t.rollupContract.LastAccInputHash(&bind.CallOpts{Pending: false}) if err != nil { + log.Errorf("error getting LastAccInputHash: %s", err) return nil, err } @@ -187,26 +191,10 @@ func (t *TxBuilderBananaBase) NewSequence( sequence.OldAccInputHash = oldAccInputHash sequence.AccInputHash = accInputHash - - err = SequenceSanityCheck(sequence) - if err != nil { - return nil, fmt.Errorf("sequenceSanityCheck fails. Err: %w", err) - } res := NewBananaSequence(*sequence) return res, nil } -func SequenceSanityCheck(seq *etherman.SequenceBanana) error { - maxL1InfoIndex, err := calculateMaxL1InfoTreeIndexInsideSequence(seq) - if err != nil { - return err - } - if seq.CounterL1InfoRoot < maxL1InfoIndex+1 { - return fmt.Errorf("wrong CounterL1InfoRoot(%d): BatchL2Data (max=%d) ", seq.CounterL1InfoRoot, maxL1InfoIndex) - } - return nil -} - func (t *TxBuilderBananaBase) getL1InfoRoot(counterL1InfoRoot uint32) (common.Hash, error) { return t.globalExitRootContract.L1InfoRootMap(&bind.CallOpts{Pending: false}, counterL1InfoRoot) } diff --git a/sequencesender/txbuilder/banana_base_test.go b/sequencesender/txbuilder/banana_base_test.go index 44d7a7b1..e5911500 100644 --- a/sequencesender/txbuilder/banana_base_test.go +++ b/sequencesender/txbuilder/banana_base_test.go @@ -6,13 +6,11 @@ import ( "math/big" "testing" - "github.com/0xPolygon/cdk/etherman" "github.com/0xPolygon/cdk/l1infotreesync" "github.com/0xPolygon/cdk/log" "github.com/0xPolygon/cdk/sequencesender/seqsendertypes" "github.com/0xPolygon/cdk/sequencesender/txbuilder" "github.com/0xPolygon/cdk/sequencesender/txbuilder/mocks_txbuilder" - "github.com/0xPolygon/cdk/state" "github.com/0xPolygon/cdk/state/datastream" "github.com/ethereum/go-ethereum/accounts/abi/bind" "github.com/ethereum/go-ethereum/common" @@ -92,41 +90,6 @@ func TestBananaBaseNewSequenceBatch(t *testing.T) { // TODO: check that the seq have the right values } -func TestBananaSanityCheck(t *testing.T) { - batch := state.BatchRawV2{ - Blocks: []state.L2BlockRaw{ - { - BlockNumber: 1, - ChangeL2BlockHeader: state.ChangeL2BlockHeader{ - DeltaTimestamp: 1, - IndexL1InfoTree: 1, - }, - }, - }, - } - data, err := state.EncodeBatchV2(&batch) - require.NoError(t, err) - require.NotNil(t, data) - seq := etherman.SequenceBanana{ - CounterL1InfoRoot: 2, - Batches: []etherman.Batch{ - { - L2Data: data, - }, - }, - } - err = txbuilder.SequenceSanityCheck(&seq) - require.NoError(t, err, "inside batchl2data max is 1 and counter is 2 (2>=1+1)") - seq.CounterL1InfoRoot = 1 - err = txbuilder.SequenceSanityCheck(&seq) - require.Error(t, err, "inside batchl2data max is 1 and counter is 1. The batchl2data is not included in counter") -} - -func TestBananaSanityCheckNilSeq(t *testing.T) { - err := txbuilder.SequenceSanityCheck(nil) - require.Error(t, err, "nil sequence") -} - func TestBananaEmptyL1InfoTree(t *testing.T) { testData := newBananaBaseTestData(t) From c1b7054541141cda16706518f566f4e20d5644f1 Mon Sep 17 00:00:00 2001 From: Joan Esteban <129153821+joanestebanr@users.noreply.github.com> Date: Thu, 14 Nov 2024 12:10:08 +0100 Subject: [PATCH 05/23] feat: improve aggsender logs (#186) (#187) --- agglayer/mock_agglayer_client.go | 150 +++++++++++++++++++++++++++++- agglayer/types.go | 2 +- aggsender/aggsender.go | 43 +++++---- aggsender/aggsender_test.go | 97 +++++++++++++++---- aggsender/types/epoch_notifier.go | 3 + config/default.go | 2 +- test/Makefile | 2 +- 7 files changed, 256 insertions(+), 43 deletions(-) diff --git a/agglayer/mock_agglayer_client.go b/agglayer/mock_agglayer_client.go index 1b756713..b7f70ee8 100644 --- a/agglayer/mock_agglayer_client.go +++ b/agglayer/mock_agglayer_client.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.39.0. DO NOT EDIT. +// Code generated by mockery. DO NOT EDIT. package agglayer @@ -15,6 +15,14 @@ type AgglayerClientMock struct { mock.Mock } +type AgglayerClientMock_Expecter struct { + mock *mock.Mock +} + +func (_m *AgglayerClientMock) EXPECT() *AgglayerClientMock_Expecter { + return &AgglayerClientMock_Expecter{mock: &_m.Mock} +} + // GetCertificateHeader provides a mock function with given fields: certificateHash func (_m *AgglayerClientMock) GetCertificateHeader(certificateHash common.Hash) (*CertificateHeader, error) { ret := _m.Called(certificateHash) @@ -45,6 +53,34 @@ func (_m *AgglayerClientMock) GetCertificateHeader(certificateHash common.Hash) return r0, r1 } +// AgglayerClientMock_GetCertificateHeader_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GetCertificateHeader' +type AgglayerClientMock_GetCertificateHeader_Call struct { + *mock.Call +} + +// GetCertificateHeader is a helper method to define mock.On call +// - certificateHash common.Hash +func (_e *AgglayerClientMock_Expecter) GetCertificateHeader(certificateHash interface{}) *AgglayerClientMock_GetCertificateHeader_Call { + return &AgglayerClientMock_GetCertificateHeader_Call{Call: _e.mock.On("GetCertificateHeader", certificateHash)} +} + +func (_c *AgglayerClientMock_GetCertificateHeader_Call) Run(run func(certificateHash common.Hash)) *AgglayerClientMock_GetCertificateHeader_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(common.Hash)) + }) + return _c +} + +func (_c *AgglayerClientMock_GetCertificateHeader_Call) Return(_a0 *CertificateHeader, _a1 error) *AgglayerClientMock_GetCertificateHeader_Call { + _c.Call.Return(_a0, _a1) + return _c +} + +func (_c *AgglayerClientMock_GetCertificateHeader_Call) RunAndReturn(run func(common.Hash) (*CertificateHeader, error)) *AgglayerClientMock_GetCertificateHeader_Call { + _c.Call.Return(run) + return _c +} + // GetEpochConfiguration provides a mock function with given fields: func (_m *AgglayerClientMock) GetEpochConfiguration() (*ClockConfiguration, error) { ret := _m.Called() @@ -75,6 +111,33 @@ func (_m *AgglayerClientMock) GetEpochConfiguration() (*ClockConfiguration, erro return r0, r1 } +// AgglayerClientMock_GetEpochConfiguration_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GetEpochConfiguration' +type AgglayerClientMock_GetEpochConfiguration_Call struct { + *mock.Call +} + +// GetEpochConfiguration is a helper method to define mock.On call +func (_e *AgglayerClientMock_Expecter) GetEpochConfiguration() *AgglayerClientMock_GetEpochConfiguration_Call { + return &AgglayerClientMock_GetEpochConfiguration_Call{Call: _e.mock.On("GetEpochConfiguration")} +} + +func (_c *AgglayerClientMock_GetEpochConfiguration_Call) Run(run func()) *AgglayerClientMock_GetEpochConfiguration_Call { + _c.Call.Run(func(args mock.Arguments) { + run() + }) + return _c +} + +func (_c *AgglayerClientMock_GetEpochConfiguration_Call) Return(_a0 *ClockConfiguration, _a1 error) *AgglayerClientMock_GetEpochConfiguration_Call { + _c.Call.Return(_a0, _a1) + return _c +} + +func (_c *AgglayerClientMock_GetEpochConfiguration_Call) RunAndReturn(run func() (*ClockConfiguration, error)) *AgglayerClientMock_GetEpochConfiguration_Call { + _c.Call.Return(run) + return _c +} + // SendCertificate provides a mock function with given fields: certificate func (_m *AgglayerClientMock) SendCertificate(certificate *SignedCertificate) (common.Hash, error) { ret := _m.Called(certificate) @@ -105,6 +168,34 @@ func (_m *AgglayerClientMock) SendCertificate(certificate *SignedCertificate) (c return r0, r1 } +// AgglayerClientMock_SendCertificate_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'SendCertificate' +type AgglayerClientMock_SendCertificate_Call struct { + *mock.Call +} + +// SendCertificate is a helper method to define mock.On call +// - certificate *SignedCertificate +func (_e *AgglayerClientMock_Expecter) SendCertificate(certificate interface{}) *AgglayerClientMock_SendCertificate_Call { + return &AgglayerClientMock_SendCertificate_Call{Call: _e.mock.On("SendCertificate", certificate)} +} + +func (_c *AgglayerClientMock_SendCertificate_Call) Run(run func(certificate *SignedCertificate)) *AgglayerClientMock_SendCertificate_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(*SignedCertificate)) + }) + return _c +} + +func (_c *AgglayerClientMock_SendCertificate_Call) Return(_a0 common.Hash, _a1 error) *AgglayerClientMock_SendCertificate_Call { + _c.Call.Return(_a0, _a1) + return _c +} + +func (_c *AgglayerClientMock_SendCertificate_Call) RunAndReturn(run func(*SignedCertificate) (common.Hash, error)) *AgglayerClientMock_SendCertificate_Call { + _c.Call.Return(run) + return _c +} + // SendTx provides a mock function with given fields: signedTx func (_m *AgglayerClientMock) SendTx(signedTx SignedTx) (common.Hash, error) { ret := _m.Called(signedTx) @@ -135,6 +226,34 @@ func (_m *AgglayerClientMock) SendTx(signedTx SignedTx) (common.Hash, error) { return r0, r1 } +// AgglayerClientMock_SendTx_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'SendTx' +type AgglayerClientMock_SendTx_Call struct { + *mock.Call +} + +// SendTx is a helper method to define mock.On call +// - signedTx SignedTx +func (_e *AgglayerClientMock_Expecter) SendTx(signedTx interface{}) *AgglayerClientMock_SendTx_Call { + return &AgglayerClientMock_SendTx_Call{Call: _e.mock.On("SendTx", signedTx)} +} + +func (_c *AgglayerClientMock_SendTx_Call) Run(run func(signedTx SignedTx)) *AgglayerClientMock_SendTx_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(SignedTx)) + }) + return _c +} + +func (_c *AgglayerClientMock_SendTx_Call) Return(_a0 common.Hash, _a1 error) *AgglayerClientMock_SendTx_Call { + _c.Call.Return(_a0, _a1) + return _c +} + +func (_c *AgglayerClientMock_SendTx_Call) RunAndReturn(run func(SignedTx) (common.Hash, error)) *AgglayerClientMock_SendTx_Call { + _c.Call.Return(run) + return _c +} + // WaitTxToBeMined provides a mock function with given fields: hash, ctx func (_m *AgglayerClientMock) WaitTxToBeMined(hash common.Hash, ctx context.Context) error { ret := _m.Called(hash, ctx) @@ -153,6 +272,35 @@ func (_m *AgglayerClientMock) WaitTxToBeMined(hash common.Hash, ctx context.Cont return r0 } +// AgglayerClientMock_WaitTxToBeMined_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'WaitTxToBeMined' +type AgglayerClientMock_WaitTxToBeMined_Call struct { + *mock.Call +} + +// WaitTxToBeMined is a helper method to define mock.On call +// - hash common.Hash +// - ctx context.Context +func (_e *AgglayerClientMock_Expecter) WaitTxToBeMined(hash interface{}, ctx interface{}) *AgglayerClientMock_WaitTxToBeMined_Call { + return &AgglayerClientMock_WaitTxToBeMined_Call{Call: _e.mock.On("WaitTxToBeMined", hash, ctx)} +} + +func (_c *AgglayerClientMock_WaitTxToBeMined_Call) Run(run func(hash common.Hash, ctx context.Context)) *AgglayerClientMock_WaitTxToBeMined_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(common.Hash), args[1].(context.Context)) + }) + return _c +} + +func (_c *AgglayerClientMock_WaitTxToBeMined_Call) Return(_a0 error) *AgglayerClientMock_WaitTxToBeMined_Call { + _c.Call.Return(_a0) + return _c +} + +func (_c *AgglayerClientMock_WaitTxToBeMined_Call) RunAndReturn(run func(common.Hash, context.Context) error) *AgglayerClientMock_WaitTxToBeMined_Call { + _c.Call.Return(run) + return _c +} + // NewAgglayerClientMock creates a new instance of AgglayerClientMock. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. // The first argument is typically a *testing.T value. func NewAgglayerClientMock(t interface { diff --git a/agglayer/types.go b/agglayer/types.go index b6a3198e..aece93f0 100644 --- a/agglayer/types.go +++ b/agglayer/types.go @@ -556,7 +556,7 @@ func (c CertificateHeader) String() string { errors = c.Error.String() } - return fmt.Sprintf("Height: %d, CertificateID: %s, NewLocalExitRoot: %s. Status: %s. Errors: %s", + 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) } diff --git a/aggsender/aggsender.go b/aggsender/aggsender.go index dcbbc268..08730572 100644 --- a/aggsender/aggsender.go +++ b/aggsender/aggsender.go @@ -55,7 +55,7 @@ func New( cfg Config, aggLayerClient agglayer.AgglayerClientInterface, l1InfoTreeSyncer *l1infotreesync.L1InfoTreeSync, - l2Syncer *bridgesync.BridgeSync, + l2Syncer types.L2BridgeSyncer, epochNotifier types.EpochNotifier) (*AggSender, error) { storage, err := db.NewAggSenderSQLStorage(logger, cfg.StoragePath) if err != nil { @@ -93,14 +93,14 @@ func (a *AggSender) sendCertificates(ctx context.Context) { select { case epoch := <-chEpoch: a.log.Infof("Epoch received: %s", epoch.String()) - thereArePendingCerts, err := a.checkPendingCertificatesStatus(ctx) - if err == nil && !thereArePendingCerts { + thereArePendingCerts := a.checkPendingCertificatesStatus(ctx) + if !thereArePendingCerts { if _, err := a.sendCertificate(ctx); err != nil { log.Error(err) } } else { - log.Warnf("Skipping epoch %s because there are pending certificates %v or error: %w", - epoch.String(), thereArePendingCerts, err) + log.Infof("Skipping epoch %s because there are pending certificates", + epoch.String()) } case <-ctx.Done(): a.log.Info("AggSender stopped") @@ -177,7 +177,7 @@ func (a *AggSender) sendCertificate(ctx context.Context) (*agglayer.SignedCertif } a.saveCertificateToFile(signedCertificate) - a.log.Debugf("certificate ready to be send to AggLayer: %s", signedCertificate.String()) + a.log.Infof("certificate ready to be send to AggLayer: %s", signedCertificate.String()) certificateHash, err := a.aggLayerClient.SendCertificate(signedCertificate) if err != nil { @@ -488,15 +488,14 @@ func (a *AggSender) signCertificate(certificate *agglayer.Certificate) (*agglaye // and updates in the storage if it changed on agglayer // It returns: // bool -> if there are pending certificates -// error -> if there was an error -func (a *AggSender) checkPendingCertificatesStatus(ctx context.Context) (bool, error) { +func (a *AggSender) checkPendingCertificatesStatus(ctx context.Context) bool { pendingCertificates, err := a.storage.GetCertificatesByStatus(nonSettledStatuses) if err != nil { err = fmt.Errorf("error getting pending certificates: %w", err) a.log.Error(err) - return true, err + return true } - thereArePendingCertificates := false + thereArePendingCerts := false a.log.Debugf("checkPendingCertificatesStatus num of pendingCertificates: %d", len(pendingCertificates)) for _, certificate := range pendingCertificates { certificateHeader, err := a.aggLayerClient.GetCertificateHeader(certificate.CertificateID) @@ -504,18 +503,17 @@ func (a *AggSender) checkPendingCertificatesStatus(ctx context.Context) (bool, e err = fmt.Errorf("error getting certificate header of %d/%s from agglayer: %w", certificate.Height, certificate.String(), err) a.log.Error(err) - return true, err + return true } - if slices.Contains(nonSettledStatuses, certificateHeader.Status) { - thereArePendingCertificates = true - } - a.log.Debugf("aggLayerClient.GetCertificateHeader status [%s] of certificate %s ", + elapsedTime := time.Now().UTC().Sub(time.UnixMilli(certificate.CreatedAt)) + a.log.Debugf("aggLayerClient.GetCertificateHeader status [%s] of certificate %s elapsed time:%s", certificateHeader.Status, - certificateHeader.String()) + certificateHeader.String(), + elapsedTime) if certificateHeader.Status != certificate.Status { - a.log.Infof("certificate %s changed status from [%s] to [%s]", - certificateHeader.String(), certificate.Status, certificateHeader.Status) + a.log.Infof("certificate %s changed status from [%s] to [%s] elapsed time: %s", + certificateHeader.String(), certificate.Status, certificateHeader.Status, elapsedTime) certificate.Status = certificateHeader.Status certificate.UpdatedAt = time.Now().UTC().UnixMilli() @@ -523,11 +521,16 @@ func (a *AggSender) checkPendingCertificatesStatus(ctx context.Context) (bool, e if err := a.storage.UpdateCertificateStatus(ctx, *certificate); err != nil { err = fmt.Errorf("error updating certificate %s status in storage: %w", certificateHeader.String(), err) a.log.Error(err) - return true, err + return true } } + if slices.Contains(nonSettledStatuses, certificateHeader.Status) { + a.log.Infof("certificate %s is still pending, elapsed time:%s ", + certificateHeader.String(), elapsedTime) + thereArePendingCerts = true + } } - return thereArePendingCertificates, nil + return thereArePendingCerts } // shouldSendCertificate checks if a certificate should be sent at given time diff --git a/aggsender/aggsender_test.go b/aggsender/aggsender_test.go index 0d071e76..b9242bdf 100644 --- a/aggsender/aggsender_test.go +++ b/aggsender/aggsender_test.go @@ -280,6 +280,70 @@ func TestGetBridgeExits(t *testing.T) { } } +func TestAggSenderStart(t *testing.T) { + AggLayerMock := agglayer.NewAgglayerClientMock(t) + epochNotifierMock := mocks.NewEpochNotifier(t) + bridgeL2SyncerMock := mocks.NewL2BridgeSyncer(t) + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + aggSender, err := New( + ctx, + log.WithFields("test", "unittest"), + Config{ + StoragePath: "file::memory:?cache=shared", + }, + AggLayerMock, + nil, + bridgeL2SyncerMock, + epochNotifierMock) + require.NoError(t, err) + require.NotNil(t, aggSender) + ch := make(chan aggsendertypes.EpochEvent) + epochNotifierMock.EXPECT().Subscribe("aggsender").Return(ch) + bridgeL2SyncerMock.EXPECT().GetLastProcessedBlock(mock.Anything).Return(uint64(0), nil) + + go aggSender.Start(ctx) + ch <- aggsendertypes.EpochEvent{ + Epoch: 1, + } + time.Sleep(200 * time.Millisecond) +} + +func TestAggSenderSendCertificates(t *testing.T) { + AggLayerMock := agglayer.NewAgglayerClientMock(t) + epochNotifierMock := mocks.NewEpochNotifier(t) + bridgeL2SyncerMock := mocks.NewL2BridgeSyncer(t) + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + aggSender, err := New( + ctx, + log.WithFields("test", "unittest"), + Config{ + StoragePath: "file::memory:?cache=shared", + }, + AggLayerMock, + nil, + bridgeL2SyncerMock, + epochNotifierMock) + require.NoError(t, err) + require.NotNil(t, aggSender) + ch := make(chan aggsendertypes.EpochEvent, 2) + epochNotifierMock.EXPECT().Subscribe("aggsender").Return(ch) + err = aggSender.storage.SaveLastSentCertificate(ctx, aggsendertypes.CertificateInfo{ + Height: 1, + Status: agglayer.Pending, + }) + AggLayerMock.EXPECT().GetCertificateHeader(mock.Anything).Return(&agglayer.CertificateHeader{ + Status: agglayer.Pending, + }, nil) + require.NoError(t, err) + ch <- aggsendertypes.EpochEvent{ + Epoch: 1, + } + go aggSender.sendCertificates(ctx) + time.Sleep(200 * time.Millisecond) +} + //nolint:dupl func TestGetImportedBridgeExits(t *testing.T) { t.Parallel() @@ -751,16 +815,15 @@ func generateTestProof(t *testing.T) treeTypes.Proof { func TestCheckIfCertificatesAreSettled(t *testing.T) { tests := []struct { - name string - pendingCertificates []*aggsendertypes.CertificateInfo - certificateHeaders map[common.Hash]*agglayer.CertificateHeader - getFromDBError error - clientError error - updateDBError error - expectedErrorLogMessages []string - expectedInfoMessages []string - expectedThereArePendingCerts bool - expectedError bool + name string + pendingCertificates []*aggsendertypes.CertificateInfo + certificateHeaders map[common.Hash]*agglayer.CertificateHeader + getFromDBError error + clientError error + updateDBError error + expectedErrorLogMessages []string + expectedInfoMessages []string + expectedError bool }{ { name: "All certificates settled - update successful", @@ -796,8 +859,7 @@ func TestCheckIfCertificatesAreSettled(t *testing.T) { expectedErrorLogMessages: []string{ "error getting pending certificates: %w", }, - expectedThereArePendingCerts: true, - expectedError: true, + expectedError: true, }, { name: "Error getting certificate header", @@ -811,8 +873,7 @@ func TestCheckIfCertificatesAreSettled(t *testing.T) { expectedErrorLogMessages: []string{ "error getting header of certificate %s with height: %d from agglayer: %w", }, - expectedThereArePendingCerts: true, - expectedError: true, + expectedError: true, }, { name: "Error updating certificate status", @@ -829,8 +890,7 @@ func TestCheckIfCertificatesAreSettled(t *testing.T) { expectedInfoMessages: []string{ "certificate %s changed status to %s", }, - expectedThereArePendingCerts: true, - expectedError: true, + expectedError: true, }, } @@ -864,9 +924,8 @@ func TestCheckIfCertificatesAreSettled(t *testing.T) { } ctx := context.TODO() - thereArePendingCerts, err := aggSender.checkPendingCertificatesStatus(ctx) - require.Equal(t, tt.expectedThereArePendingCerts, thereArePendingCerts) - require.Equal(t, tt.expectedError, err != nil) + thereArePendingCerts := aggSender.checkPendingCertificatesStatus(ctx) + require.Equal(t, tt.expectedError, thereArePendingCerts) mockAggLayerClient.AssertExpectations(t) mockStorage.AssertExpectations(t) }) diff --git a/aggsender/types/epoch_notifier.go b/aggsender/types/epoch_notifier.go index 426ad362..045ba7ff 100644 --- a/aggsender/types/epoch_notifier.go +++ b/aggsender/types/epoch_notifier.go @@ -23,3 +23,6 @@ type EpochNotifier interface { Start(ctx context.Context) String() string } + +type BridgeL2Syncer interface { +} diff --git a/config/default.go b/config/default.go index d7188e43..61b099c8 100644 --- a/config/default.go +++ b/config/default.go @@ -343,5 +343,5 @@ URLRPCL2="{{L2URL}}" CheckSettledInterval = "2s" BlockFinality = "LatestBlock" EpochNotificationPercentage = 50 -SaveCertificatesToFiles = false +SaveCertificatesToFilesPath = "" ` diff --git a/test/Makefile b/test/Makefile index 2435730c..12f406fd 100644 --- a/test/Makefile +++ b/test/Makefile @@ -68,7 +68,7 @@ generate-mocks-aggsender: ## Generates mocks for aggsender, using mockery tool .PHONY: generate-mocks-agglayer generate-mocks-agglayer: ## Generates mocks for agglayer, using mockery tool - export "GOROOT=$$(go env GOROOT)" && $$(go env GOPATH)/bin/mockery --name=AgglayerClientInterface --dir=../agglayer --output=../agglayer --outpkg=agglayer --inpackage --structname=AgglayerClientMock --filename=mock_agglayer_client.go + export "GOROOT=$$(go env GOROOT)" && $$(go env GOPATH)/bin/mockery --name=AgglayerClientInterface --dir=../agglayer --output=../agglayer --outpkg=agglayer --inpackage --structname=AgglayerClientMock --filename=mock_agglayer_client.go ${COMMON_MOCKERY_PARAMS} .PHONY: generate-mocks-bridgesync generate-mocks-bridgesync: ## Generates mocks for bridgesync, using mockery tool From f87359a0bc8b75e170fdb4ba35972be155d85c71 Mon Sep 17 00:00:00 2001 From: Goran Rojovic <100121253+goran-ethernal@users.noreply.github.com> Date: Thu, 14 Nov 2024 14:12:26 +0100 Subject: [PATCH 06/23] fix: `L1InfoRootIncorrect` error from `agglayer` (#185) * fix: add new error * fix: ut * fix: comment --- .../l1_info_root_incorrect_error.json | 11 ++++ agglayer/type_conversion_error.go | 51 +++++++++++++++++++ 2 files changed, 62 insertions(+) create mode 100644 agglayer/testdata/type_conversion_errors/l1_info_root_incorrect_error.json diff --git a/agglayer/testdata/type_conversion_errors/l1_info_root_incorrect_error.json b/agglayer/testdata/type_conversion_errors/l1_info_root_incorrect_error.json new file mode 100644 index 00000000..fe3c420a --- /dev/null +++ b/agglayer/testdata/type_conversion_errors/l1_info_root_incorrect_error.json @@ -0,0 +1,11 @@ +[ + { + "test_name": "L1InfoRootIncorrect", + "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\":{\"L1InfoRootIncorrect\":{\"leaf_count\":11,\"declared\":\"0x566244fbf813b6926f6895142979f61ed6706184909cb8c819cd0216202a8aa9\",\"retrieved\":\"0xa80cd4abb016bbb3c3058f923a88be1ad49d68277366c55554d6f13d62428a1f\"}}}}}}" + }, + { + "test_name": "L1InfoRootIncorrect - unmarshal error", + "expected_error": "value of key leaf_count is not of type uint32", + "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\":{\"L1InfoRootIncorrect\":{\"leaf_count\":\"invalid\",\"declared\":\"0x566244fbf813b6926f6895142979f61ed6706184909cb8c819cd0216202a8aa9\",\"retrieved\":\"0xa80cd4abb016bbb3c3058f923a88be1ad49d68277366c55554d6f13d62428a1f\"}}}}}}" + } +] diff --git a/agglayer/type_conversion_error.go b/agglayer/type_conversion_error.go index 89129253..3d75658f 100644 --- a/agglayer/type_conversion_error.go +++ b/agglayer/type_conversion_error.go @@ -3,6 +3,8 @@ package agglayer import ( "errors" "fmt" + + "github.com/ethereum/go-ethereum/common" ) const ( @@ -12,6 +14,7 @@ const ( BalanceUnderflowErrorType = "BalanceUnderflow" BalanceProofGenerationFailedErrorType = "BalanceProofGenerationFailed" NullifierPathGenerationFailedErrorType = "NullifierPathGenerationFailed" + L1InfoRootIncorrectErrorType = "L1InfoRootIncorrect" ) // TypeConversionError is an error that is returned when verifying a certficate @@ -57,6 +60,12 @@ func (p *TypeConversionError) Unmarshal(data interface{}) error { return nil, err } return nullifierPathGenerationFailed, nil + case L1InfoRootIncorrectErrorType: + l1InfoRootIncorrect := &L1InfoRootIncorrect{} + if err := l1InfoRootIncorrect.Unmarshal(value); err != nil { + return nil, err + } + return l1InfoRootIncorrect, nil default: return nil, fmt.Errorf("unknown type conversion error type: %v", key) } @@ -253,3 +262,45 @@ func (e *NullifierPathGenerationFailed) UnmarshalFromMap(data interface{}) error e.GlobalIndex = &GlobalIndex{} return e.GlobalIndex.UnmarshalFromMap(globalIndexMap) } + +// L1InfoRootIncorrect is an error that is returned when the L1 Info Root is invalid or unsettled +type L1InfoRootIncorrect struct { + Declared common.Hash `json:"declared"` + Retrieved common.Hash `json:"retrieved"` + LeafCount uint32 `json:"leaf_count"` +} + +// String is the implementation of the Error interface +func (e *L1InfoRootIncorrect) String() string { + return fmt.Sprintf("%s: The L1 Info Root is incorrect. Declared: %s, Retrieved: %s, LeafCount: %d", + L1InfoRootIncorrectErrorType, e.Declared.String(), e.Retrieved.String(), e.LeafCount) +} + +// Unmarshal unmarshals the data from a map into a L1InfoRootIncorrect struct. +func (e *L1InfoRootIncorrect) Unmarshal(data interface{}) error { + dataMap, ok := data.(map[string]interface{}) + if !ok { + return errNotMap + } + + declared, err := convertMapValue[string](dataMap, "declared") + if err != nil { + return err + } + + retrieved, err := convertMapValue[string](dataMap, "retrieved") + if err != nil { + return err + } + + leafCount, err := convertMapValue[uint32](dataMap, "leaf_count") + if err != nil { + return err + } + + e.Declared = common.HexToHash(declared) + e.Retrieved = common.HexToHash(retrieved) + e.LeafCount = leafCount + + return nil +} From 8a1307b30dae7e93c17f8ef65e764b2c31a6cab2 Mon Sep 17 00:00:00 2001 From: Arnau Bennassar Date: Thu, 14 Nov 2024 08:05:12 -0600 Subject: [PATCH 07/23] fix: l1infotree flaky test (#182) * change to run gha * better wait for processot * handle context cancel * handle context cancel * add mutex to halt * add mutex to halt * better wait * better mutex for halted * lint * fix wait * fix wait --- bridgesync/e2e_test.go | 19 +--------------- crates/cdk/versions.json | 6 ++--- go.mod | 1 - go.sum | 11 ++------- l1infotreesync/e2e_test.go | 38 +++++++++----------------------- l1infotreesync/l1infotreesync.go | 38 ++++++++++++++++---------------- l1infotreesync/processor.go | 14 +++++++++++- lastgersync/e2e_test.go | 20 ++++------------- reorgdetector/types.go | 2 +- sync/evmdownloader.go | 10 ++++++--- test/helpers/wait.go | 32 +++++++++++++++++++++++++++ 11 files changed, 92 insertions(+), 99 deletions(-) create mode 100644 test/helpers/wait.go diff --git a/bridgesync/e2e_test.go b/bridgesync/e2e_test.go index 6f1e10c4..0b350006 100644 --- a/bridgesync/e2e_test.go +++ b/bridgesync/e2e_test.go @@ -2,7 +2,6 @@ package bridgesync_test import ( "context" - "fmt" "math/big" "path" "testing" @@ -63,25 +62,9 @@ func TestBridgeEventE2E(t *testing.T) { } // Wait for syncer to catch up - syncerUpToDate := false - - var errMsg string lb, err := client.Client().BlockNumber(ctx) require.NoError(t, err) - - for i := 0; i < 10; i++ { - lpb, err := syncer.GetLastProcessedBlock(ctx) - require.NoError(t, err) - if lpb == lb { - syncerUpToDate = true - - break - } - - time.Sleep(time.Millisecond * 100) - errMsg = fmt.Sprintf("last block from client: %d, last block from syncer: %d", lb, lpb) - } - require.True(t, syncerUpToDate, errMsg) + helpers.RequireProcessorUpdated(t, syncer, lb) // Get bridges lastBlock, err := client.Client().BlockNumber(ctx) diff --git a/crates/cdk/versions.json b/crates/cdk/versions.json index bafbd00b..39bfb4dc 100644 --- a/crates/cdk/versions.json +++ b/crates/cdk/versions.json @@ -1,15 +1,15 @@ { "agglayer_image": "ghcr.io/agglayer/agglayer:0.2.0-rc.5", "cdk_erigon_node_image": "hermeznetwork/cdk-erigon:v2.1.2", - "cdk_node_image": "ghcr.io/0xpolygon/cdk:0.4.0-beta5", + "cdk_node_image": "ghcr.io/0xpolygon/cdk:0.4.0-beta8", "cdk_validium_node_image": "0xpolygon/cdk-validium-node:0.7.0-cdk", "zkevm_bridge_proxy_image": "haproxy:3.0-bookworm", "zkevm_bridge_service_image": "hermeznetwork/zkevm-bridge-service:v0.6.0-RC1", - "zkevm_bridge_ui_image": "leovct/zkevm-bridge-ui:multi-network-2", + "zkevm_bridge_ui_image": "leovct/zkevm-bridge-ui:multi-network", "zkevm_contracts_image": "leovct/zkevm-contracts:v8.0.0-rc.4-fork.12", "zkevm_da_image": "0xpolygon/cdk-data-availability:0.0.10", "zkevm_node_image": "hermeznetwork/zkevm-node:v0.7.3", - "zkevm_pool_manager_image": "hermeznetwork/zkevm-pool-manager:v0.1.1", + "zkevm_pool_manager_image": "hermeznetwork/zkevm-pool-manager:v0.1.2", "zkevm_prover_image": "hermeznetwork/zkevm-prover:v8.0.0-RC14-fork.12", "zkevm_sequence_sender_image": "hermeznetwork/zkevm-sequence-sender:v0.2.4" } diff --git a/go.mod b/go.mod index 430e8326..c42bb806 100644 --- a/go.mod +++ b/go.mod @@ -40,7 +40,6 @@ require ( ) require ( - github.com/0xPolygonHermez/zkevm-data-streamer v0.2.7 // indirect github.com/DataDog/zstd v1.5.6 // indirect github.com/Microsoft/go-winio v0.6.2 // indirect github.com/StackExchange/wmi v1.2.1 // indirect diff --git a/go.sum b/go.sum index 3ad80938..d6cbdb2b 100644 --- a/go.sum +++ b/go.sum @@ -6,7 +6,6 @@ github.com/0xPolygon/cdk-rpc v0.0.0-20241004114257-6c3cb6eebfb6 h1:FXL/rcO7/GtZ3 github.com/0xPolygon/cdk-rpc v0.0.0-20241004114257-6c3cb6eebfb6/go.mod h1:2scWqMMufrQXu7TikDgQ3BsyaKoX8qP26D6E262vSOg= github.com/0xPolygon/zkevm-ethtx-manager v0.2.1 h1:2Yb+KdJFMpVrS9LIkd658XiWuN+MCTs7SgeWaopXScg= github.com/0xPolygon/zkevm-ethtx-manager v0.2.1/go.mod h1:lqQmzSo2OXEZItD0R4Cd+lqKFxphXEWgqHefVcGDZZc= -github.com/0xPolygonHermez/zkevm-data-streamer v0.2.7/go.mod h1:7nM7Ihk+fTG1TQPwdZoGOYd3wprqqyIyjtS514uHzWE= github.com/0xPolygonHermez/zkevm-synchronizer-l1 v1.0.5 h1:YmnhuCl349MoNASN0fMeGKU1o9HqJhiZkfMsA/1cTRA= github.com/0xPolygonHermez/zkevm-synchronizer-l1 v1.0.5/go.mod h1:X4Su/M/+hSISqdl9yomKlRsbTyuZHsRohporyHsP8gg= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= @@ -481,8 +480,6 @@ golang.org/x/crypto v0.0.0-20210616213533-5ff15b29337e/go.mod h1:GvvjBRRGRdwPK5y golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.27.0 h1:GXm2NjJrPaiv/h1tb2UH8QfgC/hOf/+z0p6PT8o1w7A= golang.org/x/crypto v0.27.0/go.mod h1:1Xngt8kV6Dvbssa53Ziq6Eqn0HqbZi5Z6R0ZpwQzt70= -golang.org/x/exp v0.0.0-20231110203233-9a3e6036ecaa h1:FRnLl4eNAQl8hwxVVC17teOw8kdjVDVAiFMtgUdTSRQ= -golang.org/x/exp v0.0.0-20231110203233-9a3e6036ecaa/go.mod h1:zk2irFbV9DP96SEBUUAy67IdHUaZuSnrz1n472HUCLE= golang.org/x/exp v0.0.0-20241108190413-2d47ceb2692f h1:XdNn9LlyWAhLVp6P/i8QYBW+hlyhrhei9uErw2B5GJo= golang.org/x/exp v0.0.0-20241108190413-2d47ceb2692f/go.mod h1:D5SMRVC3C2/4+F/DB1wZsLRnSNimn2Sp/NPsCrsv8ak= golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= @@ -490,9 +487,8 @@ golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKG golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/mod v0.19.0 h1:fEdghXQSo20giMthA7cd28ZC+jts4amQ3YMXiP5oMQ8= -golang.org/x/mod v0.19.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= golang.org/x/mod v0.22.0 h1:D4nJWe9zXqHOmWqj4VMOJhvzj7bEZg4wEYa759z1pH4= +golang.org/x/mod v0.22.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY= golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= @@ -512,8 +508,6 @@ golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ= -golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sync v0.9.0 h1:fEo0HyrW1GIgZdpbhCRO0PkJajUS5H9IFUztCgEo2jQ= golang.org/x/sync v0.9.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -575,9 +569,8 @@ golang.org/x/tools v0.0.0-20200103221440-774c71fcf114/go.mod h1:TB2adYChydJhpapK golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= -golang.org/x/tools v0.23.0 h1:SGsXPZ+2l4JsgaCKkx+FQ9YZ5XEtA1GZYuoDjenLjvg= -golang.org/x/tools v0.23.0/go.mod h1:pnu6ufv6vQkll6szChhK3C3L/ruaIv5eBeztNG8wtsI= golang.org/x/tools v0.27.0 h1:qEKojBykQkQ4EynWy4S8Weg69NumxKdn40Fce3uc/8o= +golang.org/x/tools v0.27.0/go.mod h1:sUi0ZgbwW9ZPAq26Ekut+weQPR5eIM6GQLQ1Yjm1H0Q= golang.org/x/xerrors v0.0.0-20190410155217-1f06c39b4373/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20190513163551-3ee3066db522/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= diff --git a/l1infotreesync/e2e_test.go b/l1infotreesync/e2e_test.go index 132f563f..ffb00e35 100644 --- a/l1infotreesync/e2e_test.go +++ b/l1infotreesync/e2e_test.go @@ -36,7 +36,6 @@ func newSimulatedClient(t *testing.T) ( *verifybatchesmock.Verifybatchesmock, ) { t.Helper() - ctx := context.Background() client, setup := helpers.SimulatedBackend(t, nil, 0) @@ -79,11 +78,11 @@ func TestE2E(t *testing.T) { client.Commit() g, err := gerSc.L1InfoRootMap(nil, uint32(i+1)) require.NoError(t, err) - // Let the processor catch up - time.Sleep(time.Millisecond * 100) receipt, err := client.Client().TransactionReceipt(ctx, tx.Hash()) require.NoError(t, err) require.Equal(t, receipt.Status, types.ReceiptStatusSuccessful) + // Let the processor catch up + helpers.RequireProcessorUpdated(t, syncer, receipt.BlockNumber.Uint64()) expectedGER, err := gerSc.GetLastGlobalExitRoot(&bind.CallOpts{Pending: false}) require.NoError(t, err) @@ -118,17 +117,7 @@ func TestE2E(t *testing.T) { require.True(t, len(receipt.Logs) == 1+i%2+i%2) // Let the processor catch - processorUpdated := false - for i := 0; i < 30; i++ { - lpb, err := syncer.GetLastProcessedBlock(ctx) - require.NoError(t, err) - if receipt.BlockNumber.Uint64() == lpb { - processorUpdated = true - break - } - time.Sleep(time.Millisecond * 10) - } - require.True(t, processorUpdated) + helpers.RequireProcessorUpdated(t, syncer, receipt.BlockNumber.Uint64()) // Assert rollup exit root expectedRollupExitRoot, err := verifySC.GetRollupExitRoot(&bind.CallOpts{Pending: false}) @@ -351,24 +340,17 @@ func TestStressAndReorgs(t *testing.T) { func waitForSyncerToCatchUp(ctx context.Context, t *testing.T, syncer *l1infotreesync.L1InfoTreeSync, client *simulated.Backend) { t.Helper() - - syncerUpToDate := false - var errMsg string - - for i := 0; i < 200; i++ { - lpb, err := syncer.GetLastProcessedBlock(ctx) + for { + lastBlockNum, err := client.Client().BlockNumber(ctx) require.NoError(t, err) - lb, err := client.Client().BlockNumber(ctx) + helpers.RequireProcessorUpdated(t, syncer, lastBlockNum) + time.Sleep(time.Second / 2) + lastBlockNum2, err := client.Client().BlockNumber(ctx) require.NoError(t, err) - if lpb == lb { - syncerUpToDate = true - break + if lastBlockNum == lastBlockNum2 { + return } - time.Sleep(time.Second / 2) - errMsg = fmt.Sprintf("last block from client: %d, last block from syncer: %d", lb, lpb) } - - require.True(t, syncerUpToDate, errMsg) } // commitBlocks commits the specified number of blocks with the given client and waits for the specified duration after each block diff --git a/l1infotreesync/l1infotreesync.go b/l1infotreesync/l1infotreesync.go index 9719fcd7..e6262ffb 100644 --- a/l1infotreesync/l1infotreesync.go +++ b/l1infotreesync/l1infotreesync.go @@ -106,7 +106,7 @@ func (s *L1InfoTreeSync) Start(ctx context.Context) { // GetL1InfoTreeMerkleProof creates a merkle proof for the L1 Info tree func (s *L1InfoTreeSync) GetL1InfoTreeMerkleProof(ctx context.Context, index uint32) (types.Proof, types.Root, error) { - if s.processor.halted { + if s.processor.isHalted() { return types.Proof{}, types.Root{}, sync.ErrInconsistentState } return s.processor.GetL1InfoTreeMerkleProof(ctx, index) @@ -118,7 +118,7 @@ func (s *L1InfoTreeSync) GetRollupExitTreeMerkleProof( networkID uint32, root common.Hash, ) (types.Proof, error) { - if s.processor.halted { + if s.processor.isHalted() { return types.Proof{}, sync.ErrInconsistentState } if networkID == 0 { @@ -141,7 +141,7 @@ func translateError(err error) error { // - ErrBlockNotProcessed, // - ErrNotFound func (s *L1InfoTreeSync) GetLatestInfoUntilBlock(ctx context.Context, blockNum uint64) (*L1InfoTreeLeaf, error) { - if s.processor.halted { + if s.processor.isHalted() { return nil, sync.ErrInconsistentState } leaf, err := s.processor.GetLatestInfoUntilBlock(ctx, blockNum) @@ -150,7 +150,7 @@ func (s *L1InfoTreeSync) GetLatestInfoUntilBlock(ctx context.Context, blockNum u // GetInfoByIndex returns the value of a leaf (not the hash) of the L1 info tree func (s *L1InfoTreeSync) GetInfoByIndex(ctx context.Context, index uint32) (*L1InfoTreeLeaf, error) { - if s.processor.halted { + if s.processor.isHalted() { return nil, sync.ErrInconsistentState } return s.processor.GetInfoByIndex(ctx, index) @@ -158,7 +158,7 @@ func (s *L1InfoTreeSync) GetInfoByIndex(ctx context.Context, index uint32) (*L1I // GetL1InfoTreeRootByIndex returns the root of the L1 info tree at the moment the leaf with the given index was added func (s *L1InfoTreeSync) GetL1InfoTreeRootByIndex(ctx context.Context, index uint32) (types.Root, error) { - if s.processor.halted { + if s.processor.isHalted() { return types.Root{}, sync.ErrInconsistentState } return s.processor.l1InfoTree.GetRootByIndex(ctx, index) @@ -166,7 +166,7 @@ func (s *L1InfoTreeSync) GetL1InfoTreeRootByIndex(ctx context.Context, index uin // GetLastRollupExitRoot return the last rollup exit root processed func (s *L1InfoTreeSync) GetLastRollupExitRoot(ctx context.Context) (types.Root, error) { - if s.processor.halted { + if s.processor.isHalted() { return types.Root{}, sync.ErrInconsistentState } return s.processor.rollupExitTree.GetLastRoot(nil) @@ -174,7 +174,7 @@ func (s *L1InfoTreeSync) GetLastRollupExitRoot(ctx context.Context) (types.Root, // GetLastL1InfoTreeRoot return the last root and index processed from the L1 Info tree func (s *L1InfoTreeSync) GetLastL1InfoTreeRoot(ctx context.Context) (types.Root, error) { - if s.processor.halted { + if s.processor.isHalted() { return types.Root{}, sync.ErrInconsistentState } return s.processor.l1InfoTree.GetLastRoot(nil) @@ -182,7 +182,7 @@ func (s *L1InfoTreeSync) GetLastL1InfoTreeRoot(ctx context.Context) (types.Root, // GetLastProcessedBlock return the last processed block func (s *L1InfoTreeSync) GetLastProcessedBlock(ctx context.Context) (uint64, error) { - if s.processor.halted { + if s.processor.isHalted() { return 0, sync.ErrInconsistentState } return s.processor.GetLastProcessedBlock(ctx) @@ -191,7 +191,7 @@ func (s *L1InfoTreeSync) GetLastProcessedBlock(ctx context.Context) (uint64, err func (s *L1InfoTreeSync) GetLocalExitRoot( ctx context.Context, networkID uint32, rollupExitRoot common.Hash, ) (common.Hash, error) { - if s.processor.halted { + if s.processor.isHalted() { return common.Hash{}, sync.ErrInconsistentState } if networkID == 0 { @@ -202,56 +202,56 @@ func (s *L1InfoTreeSync) GetLocalExitRoot( } func (s *L1InfoTreeSync) GetLastVerifiedBatches(rollupID uint32) (*VerifyBatches, error) { - if s.processor.halted { + if s.processor.isHalted() { return nil, sync.ErrInconsistentState } return s.processor.GetLastVerifiedBatches(rollupID) } func (s *L1InfoTreeSync) GetFirstVerifiedBatches(rollupID uint32) (*VerifyBatches, error) { - if s.processor.halted { + if s.processor.isHalted() { return nil, sync.ErrInconsistentState } return s.processor.GetFirstVerifiedBatches(rollupID) } func (s *L1InfoTreeSync) GetFirstVerifiedBatchesAfterBlock(rollupID uint32, blockNum uint64) (*VerifyBatches, error) { - if s.processor.halted { + if s.processor.isHalted() { return nil, sync.ErrInconsistentState } return s.processor.GetFirstVerifiedBatchesAfterBlock(rollupID, blockNum) } func (s *L1InfoTreeSync) GetFirstL1InfoWithRollupExitRoot(rollupExitRoot common.Hash) (*L1InfoTreeLeaf, error) { - if s.processor.halted { + if s.processor.isHalted() { return nil, sync.ErrInconsistentState } return s.processor.GetFirstL1InfoWithRollupExitRoot(rollupExitRoot) } func (s *L1InfoTreeSync) GetLastInfo() (*L1InfoTreeLeaf, error) { - if s.processor.halted { + if s.processor.isHalted() { return nil, sync.ErrInconsistentState } return s.processor.GetLastInfo() } func (s *L1InfoTreeSync) GetFirstInfo() (*L1InfoTreeLeaf, error) { - if s.processor.halted { + if s.processor.isHalted() { return nil, sync.ErrInconsistentState } return s.processor.GetFirstInfo() } func (s *L1InfoTreeSync) GetFirstInfoAfterBlock(blockNum uint64) (*L1InfoTreeLeaf, error) { - if s.processor.halted { + if s.processor.isHalted() { return nil, sync.ErrInconsistentState } return s.processor.GetFirstInfoAfterBlock(blockNum) } func (s *L1InfoTreeSync) GetInfoByGlobalExitRoot(ger common.Hash) (*L1InfoTreeLeaf, error) { - if s.processor.halted { + if s.processor.isHalted() { return nil, sync.ErrInconsistentState } return s.processor.GetInfoByGlobalExitRoot(ger) @@ -261,7 +261,7 @@ func (s *L1InfoTreeSync) GetInfoByGlobalExitRoot(ger common.Hash) (*L1InfoTreeLe func (s *L1InfoTreeSync) GetL1InfoTreeMerkleProofFromIndexToRoot( ctx context.Context, index uint32, root common.Hash, ) (types.Proof, error) { - if s.processor.halted { + if s.processor.isHalted() { return types.Proof{}, sync.ErrInconsistentState } return s.processor.l1InfoTree.GetProof(ctx, index, root) @@ -269,7 +269,7 @@ func (s *L1InfoTreeSync) GetL1InfoTreeMerkleProofFromIndexToRoot( // GetInitL1InfoRootMap returns the initial L1 info root map, nil if no root map has been set func (s *L1InfoTreeSync) GetInitL1InfoRootMap(ctx context.Context) (*L1InfoTreeInitial, error) { - if s.processor.halted { + if s.processor.isHalted() { return nil, sync.ErrInconsistentState } return s.processor.GetInitL1InfoRootMap(nil) diff --git a/l1infotreesync/processor.go b/l1infotreesync/processor.go index ee94e829..ac58fb5c 100644 --- a/l1infotreesync/processor.go +++ b/l1infotreesync/processor.go @@ -6,6 +6,7 @@ import ( "encoding/binary" "errors" "fmt" + mutex "sync" "github.com/0xPolygon/cdk/db" "github.com/0xPolygon/cdk/l1infotreesync/migrations" @@ -28,6 +29,7 @@ type processor struct { db *sql.DB l1InfoTree *tree.AppendOnlyTree rollupExitTree *tree.UpdatableTree + mu mutex.RWMutex halted bool haltedReason string } @@ -267,6 +269,8 @@ func (p *processor) Reorg(ctx context.Context, firstReorgedBlock uint64) error { return err } if rowsAffected > 0 { + p.mu.Lock() + defer p.mu.Unlock() p.halted = false p.haltedReason = "" } @@ -277,7 +281,7 @@ func (p *processor) Reorg(ctx context.Context, firstReorgedBlock uint64) error { // ProcessBlock process the events of the block to build the rollup exit tree and the l1 info tree // and updates the last processed block (can be called without events for that purpose) func (p *processor) ProcessBlock(ctx context.Context, block sync.Block) error { - if p.halted { + if p.isHalted() { log.Errorf("processor is halted due to: %s", p.haltedReason) return sync.ErrInconsistentState } @@ -361,8 +365,10 @@ func (p *processor) ProcessBlock(ctx context.Context, block sync.Block) error { block.Num, ) log.Error(errStr) + p.mu.Lock() p.haltedReason = errStr p.halted = true + p.mu.Unlock() return sync.ErrInconsistentState } } @@ -464,3 +470,9 @@ func (p *processor) getDBQuerier(tx db.Txer) db.Querier { } return p.db } + +func (p *processor) isHalted() bool { + p.mu.RLock() + defer p.mu.RUnlock() + return p.halted +} diff --git a/lastgersync/e2e_test.go b/lastgersync/e2e_test.go index 9b9a6f36..6f62f12d 100644 --- a/lastgersync/e2e_test.go +++ b/lastgersync/e2e_test.go @@ -11,6 +11,7 @@ import ( "github.com/0xPolygon/cdk/etherman" "github.com/0xPolygon/cdk/lastgersync" "github.com/0xPolygon/cdk/test/aggoraclehelpers" + "github.com/0xPolygon/cdk/test/helpers" "github.com/ethereum/go-ethereum/accounts/abi/bind" "github.com/ethereum/go-ethereum/common" "github.com/stretchr/testify/require" @@ -49,22 +50,9 @@ func TestE2E(t *testing.T) { require.True(t, isInjected, fmt.Sprintf("iteration %d, GER: %s", i, common.Bytes2Hex(expectedGER[:]))) // Wait for syncer to catch up - syncerUpToDate := false - var errMsg string - for i := 0; i < 10; i++ { - lpb, err := syncer.GetLastProcessedBlock(ctx) - require.NoError(t, err) - lb, err := env.L2Client.Client().BlockNumber(ctx) - require.NoError(t, err) - if lpb == lb { - syncerUpToDate = true - - break - } - time.Sleep(time.Millisecond * 100) - errMsg = fmt.Sprintf("last block from client: %d, last block from syncer: %d", lb, lpb) - } - require.True(t, syncerUpToDate, errMsg) + lb, err := env.L2Client.Client().BlockNumber(ctx) + require.NoError(t, err) + helpers.RequireProcessorUpdated(t, syncer, lb) e, err := syncer.GetFirstGERAfterL1InfoTreeIndex(ctx, uint32(i)) require.NoError(t, err, fmt.Sprint("iteration: ", i)) diff --git a/reorgdetector/types.go b/reorgdetector/types.go index 20d4562c..2c860277 100644 --- a/reorgdetector/types.go +++ b/reorgdetector/types.go @@ -93,9 +93,9 @@ func (hl *headersList) get(num uint64) *header { // getSorted returns headers in sorted order func (hl *headersList) getSorted() []header { + hl.RLock() sortedBlocks := make([]header, 0, len(hl.headers)) - hl.RLock() for _, b := range hl.headers { sortedBlocks = append(sortedBlocks, b) } diff --git a/sync/evmdownloader.go b/sync/evmdownloader.go index 13539f2f..74b8ec7c 100644 --- a/sync/evmdownloader.go +++ b/sync/evmdownloader.go @@ -163,9 +163,13 @@ func (d *EVMDownloaderImplementation) WaitForNewBlocks( case <-ticker.C: header, err := d.ethClient.HeaderByNumber(ctx, d.blockFinality) if err != nil { - attempts++ - d.log.Error("error getting last block num from eth client: ", err) - d.rh.Handle("waitForNewBlocks", attempts) + if ctx.Err() == nil { + attempts++ + d.log.Error("error getting last block num from eth client: ", err) + d.rh.Handle("waitForNewBlocks", attempts) + } else { + d.log.Warn("context has been canceled while trying to get header by number") + } continue } if header.Number.Uint64() > lastBlockSeen { diff --git a/test/helpers/wait.go b/test/helpers/wait.go new file mode 100644 index 00000000..86a6f9fb --- /dev/null +++ b/test/helpers/wait.go @@ -0,0 +1,32 @@ +package helpers + +import ( + "context" + "errors" + "testing" + "time" + + "github.com/stretchr/testify/require" +) + +type Processorer interface { + GetLastProcessedBlock(ctx context.Context) (uint64, error) +} + +func RequireProcessorUpdated(t *testing.T, processor Processorer, targetBlock uint64) { + t.Helper() + const ( + maxIterations = 100 + sleepTimePerIteration = time.Millisecond * 10 + ) + ctx := context.Background() + for i := 0; i < maxIterations; i++ { + lpb, err := processor.GetLastProcessedBlock(ctx) + require.NoError(t, err) + if targetBlock <= lpb { + return + } + time.Sleep(sleepTimePerIteration) + } + require.NoError(t, errors.New("processor not updated")) +} From 06f8aa6b8556f1a79bde5f1d6969d736b42d725e Mon Sep 17 00:00:00 2001 From: Joan Esteban <129153821+joanestebanr@users.noreply.github.com> Date: Fri, 15 Nov 2024 10:14:04 +0100 Subject: [PATCH 08/23] feat: l1infotreesync can be run as individual component (#188) --- cmd/run.go | 6 ++++-- common/components.go | 2 ++ 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/cmd/run.go b/cmd/run.go index 6042e935..727533e8 100644 --- a/cmd/run.go +++ b/cmd/run.go @@ -529,7 +529,7 @@ func runL1InfoTreeSyncerIfNeeded( reorgDetector *reorgdetector.ReorgDetector, ) *l1infotreesync.L1InfoTreeSync { if !isNeeded([]string{cdkcommon.AGGORACLE, cdkcommon.RPC, - cdkcommon.SEQUENCE_SENDER, cdkcommon.AGGSENDER}, components) { + cdkcommon.SEQUENCE_SENDER, cdkcommon.AGGSENDER, cdkcommon.L1INFOTREESYNC}, components) { return nil } l1InfoTreeSync, err := l1infotreesync.New( @@ -560,6 +560,7 @@ func runL1ClientIfNeeded(components []string, urlRPCL1 string) *ethclient.Client cdkcommon.SEQUENCE_SENDER, cdkcommon.AGGREGATOR, cdkcommon.AGGORACLE, cdkcommon.RPC, cdkcommon.AGGSENDER, + cdkcommon.L1INFOTREESYNC, }, components) { return nil } @@ -594,7 +595,8 @@ func runReorgDetectorL1IfNeeded( ) (*reorgdetector.ReorgDetector, chan error) { if !isNeeded([]string{ cdkcommon.SEQUENCE_SENDER, cdkcommon.AGGREGATOR, - cdkcommon.AGGORACLE, cdkcommon.RPC, cdkcommon.AGGSENDER}, + cdkcommon.AGGORACLE, cdkcommon.RPC, cdkcommon.AGGSENDER, + cdkcommon.L1INFOTREESYNC}, components) { return nil, nil } diff --git a/common/components.go b/common/components.go index 7ef9d285..2c8ab188 100644 --- a/common/components.go +++ b/common/components.go @@ -15,4 +15,6 @@ const ( PROVER = "prover" // AGGSENDER name to identify the aggsender component AGGSENDER = "aggsender" + // L1INFOTREESYNC name to identify the l1infotreesync component + L1INFOTREESYNC = "l1infotreesync" ) From 6e4577fe73863675a2fd3f968e21b1a85e62c203 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Toni=20Ram=C3=ADrez?= <58293609+ToniRamirezM@users.noreply.github.com> Date: Tue, 19 Nov 2024 10:34:44 +0100 Subject: [PATCH 09/23] fix: aggregating proofs (#191) (#193) * fix: aggregating proofs (#191) * ensure oldAccInputHash is ready * feat: updata sync lib * feat: acc input hash sanity check * feat: check acc input hash -1 * feat: refactor * feat: refactor * fix: batch1 acc input hash * fix: timestamp in input prover * fix: timestamp in input prover * fix: timestamp * feat: remove test * fix: test * fix: test * fix: comments * fix: comments * fix: test --- aggregator/aggregator.go | 88 ++++-- aggregator/aggregator_test.go | 340 ++++++++++-------------- aggregator/interfaces.go | 2 +- aggregator/mocks/mock_prover.go | 21 +- aggregator/prover/mocks/mock_channel.go | 176 ++++++++++++ aggregator/prover/prover.go | 31 ++- aggregator/prover/prover_test.go | 58 +++- go.mod | 2 +- go.sum | 4 +- test/Makefile | 3 +- 10 files changed, 480 insertions(+), 245 deletions(-) create mode 100644 aggregator/prover/mocks/mock_channel.go diff --git a/aggregator/aggregator.go b/aggregator/aggregator.go index 72c316be..0659180f 100644 --- a/aggregator/aggregator.go +++ b/aggregator/aggregator.go @@ -970,7 +970,7 @@ func (a *Aggregator) tryAggregateProofs(ctx context.Context, prover ProverInterf tmpLogger.Infof("Proof ID for aggregated proof: %v", *proof.ProofID) tmpLogger = tmpLogger.WithFields("proofId", *proof.ProofID) - recursiveProof, _, err := prover.WaitRecursiveProof(ctx, *proof.ProofID) + recursiveProof, _, _, err := prover.WaitRecursiveProof(ctx, *proof.ProofID) if err != nil { err = fmt.Errorf("failed to get aggregated proof from prover, %w", err) tmpLogger.Error(FirstToUpper(err.Error())) @@ -1121,7 +1121,7 @@ func (a *Aggregator) getAndLockBatchToProve( // Not found, so it it not possible to verify the batch yet if sequence == nil || errors.Is(err, entities.ErrNotFound) { tmpLogger.Infof("Sequencing event for batch %d has not been synced yet, "+ - "so it is not possible to verify it yet. Waiting...", batchNumberToVerify) + "so it is not possible to verify it yet. Waiting ...", batchNumberToVerify) return nil, nil, nil, state.ErrNotFound } @@ -1138,7 +1138,7 @@ func (a *Aggregator) getAndLockBatchToProve( return nil, nil, nil, err } else if errors.Is(err, entities.ErrNotFound) { a.logger.Infof("Virtual batch %d has not been synced yet, "+ - "so it is not possible to verify it yet. Waiting...", batchNumberToVerify) + "so it is not possible to verify it yet. Waiting ...", batchNumberToVerify) return nil, nil, nil, state.ErrNotFound } @@ -1163,21 +1163,43 @@ func (a *Aggregator) getAndLockBatchToProve( virtualBatch.L1InfoRoot = &l1InfoRoot } + // Ensure the old acc input hash is in memory + oldAccInputHash := a.getAccInputHash(batchNumberToVerify - 1) + if oldAccInputHash == (common.Hash{}) && batchNumberToVerify > 1 { + tmpLogger.Warnf("AccInputHash for previous batch (%d) is not in memory. Waiting ...", batchNumberToVerify-1) + return nil, nil, nil, state.ErrNotFound + } + + forcedBlockHashL1 := rpcBatch.ForcedBlockHashL1() + l1InfoRoot = *virtualBatch.L1InfoRoot + + if batchNumberToVerify == 1 { + l1Block, err := a.l1Syncr.GetL1BlockByNumber(ctx, virtualBatch.BlockNumber) + if err != nil { + a.logger.Errorf("Error getting l1 block: %v", err) + return nil, nil, nil, err + } + + forcedBlockHashL1 = l1Block.ParentHash + l1InfoRoot = rpcBatch.GlobalExitRoot() + } + // Calculate acc input hash as the RPC is not returning the correct one at the moment accInputHash := cdkcommon.CalculateAccInputHash( a.logger, - a.getAccInputHash(batchNumberToVerify-1), + oldAccInputHash, virtualBatch.BatchL2Data, - *virtualBatch.L1InfoRoot, + l1InfoRoot, uint64(sequence.Timestamp.Unix()), rpcBatch.LastCoinbase(), - rpcBatch.ForcedBlockHashL1(), + forcedBlockHashL1, ) // Store the acc input hash a.setAccInputHash(batchNumberToVerify, accInputHash) // Log params to calculate acc input hash a.logger.Debugf("Calculated acc input hash for batch %d: %v", batchNumberToVerify, accInputHash) + a.logger.Debugf("OldAccInputHash: %v", oldAccInputHash) a.logger.Debugf("L1InfoRoot: %v", virtualBatch.L1InfoRoot) // a.logger.Debugf("LastL2BLockTimestamp: %v", rpcBatch.LastL2BLockTimestamp()) a.logger.Debugf("TimestampLimit: %v", uint64(sequence.Timestamp.Unix())) @@ -1196,7 +1218,7 @@ func (a *Aggregator) getAndLockBatchToProve( AccInputHash: accInputHash, L1InfoTreeIndex: rpcBatch.L1InfoTreeIndex(), L1InfoRoot: *virtualBatch.L1InfoRoot, - Timestamp: time.Unix(int64(rpcBatch.LastL2BLockTimestamp()), 0), + Timestamp: sequence.Timestamp, GlobalExitRoot: rpcBatch.GlobalExitRoot(), ChainID: a.cfg.ChainID, ForkID: a.cfg.ForkId, @@ -1325,7 +1347,7 @@ func (a *Aggregator) tryGenerateBatchProof(ctx context.Context, prover ProverInt tmpLogger = tmpLogger.WithFields("proofId", *proof.ProofID) - resGetProof, stateRoot, err := prover.WaitRecursiveProof(ctx, *proof.ProofID) + resGetProof, stateRoot, accInputHash, err := prover.WaitRecursiveProof(ctx, *proof.ProofID) if err != nil { err = fmt.Errorf("failed to get proof from prover, %w", err) tmpLogger.Error(FirstToUpper(err.Error())) @@ -1335,17 +1357,9 @@ func (a *Aggregator) tryGenerateBatchProof(ctx context.Context, prover ProverInt tmpLogger.Info("Batch proof generated") // Sanity Check: state root from the proof must match the one from the batch - if a.cfg.BatchProofSanityCheckEnabled && (stateRoot != common.Hash{}) && (stateRoot != batchToProve.StateRoot) { - for { - tmpLogger.Errorf("State root from the proof does not match the expected for batch %d: Proof = [%s] Expected = [%s]", - batchToProve.BatchNumber, stateRoot.String(), batchToProve.StateRoot.String(), - ) - time.Sleep(a.cfg.RetryTime.Duration) - } - } else { - tmpLogger.Infof("State root sanity check for batch %d passed", batchToProve.BatchNumber) + if a.cfg.BatchProofSanityCheckEnabled { + a.performSanityChecks(tmpLogger, stateRoot, accInputHash, batchToProve) } - proof.Proof = resGetProof // NOTE(pg): the defer func is useless from now on, use a different variable @@ -1374,6 +1388,35 @@ func (a *Aggregator) tryGenerateBatchProof(ctx context.Context, prover ProverInt return true, nil } +func (a *Aggregator) performSanityChecks(tmpLogger *log.Logger, stateRoot, accInputHash common.Hash, + batchToProve *state.Batch) { + // Sanity Check: state root from the proof must match the one from the batch + if (stateRoot != common.Hash{}) && (stateRoot != batchToProve.StateRoot) { + for { + tmpLogger.Errorf("HALTING: "+ + "State root from the proof does not match the expected for batch %d: Proof = [%s] Expected = [%s]", + batchToProve.BatchNumber, stateRoot.String(), batchToProve.StateRoot.String(), + ) + time.Sleep(a.cfg.RetryTime.Duration) + } + } else { + tmpLogger.Infof("State root sanity check for batch %d passed", batchToProve.BatchNumber) + } + + // Sanity Check: acc input hash from the proof must match the one from the batch + if (accInputHash != common.Hash{}) && (accInputHash != batchToProve.AccInputHash) { + for { + tmpLogger.Errorf("HALTING: Acc input hash from the proof does not match the expected for "+ + "batch %d: Proof = [%s] Expected = [%s]", + batchToProve.BatchNumber, accInputHash.String(), batchToProve.AccInputHash.String(), + ) + time.Sleep(a.cfg.RetryTime.Duration) + } + } else { + tmpLogger.Infof("Acc input hash sanity check for batch %d passed", batchToProve.BatchNumber) + } +} + // canVerifyProof returns true if we have reached the timeout to verify a proof // and no other prover is verifying a proof (verifyingProof = false). func (a *Aggregator) canVerifyProof() bool { @@ -1505,10 +1548,17 @@ func (a *Aggregator) buildInputProver( } } + // Ensure the old acc input hash is in memory + oldAccInputHash := a.getAccInputHash(batchToVerify.BatchNumber - 1) + if oldAccInputHash == (common.Hash{}) && batchToVerify.BatchNumber > 1 { + a.logger.Warnf("AccInputHash for previous batch (%d) is not in memory. Waiting ...", batchToVerify.BatchNumber-1) + return nil, fmt.Errorf("acc input hash for previous batch (%d) is not in memory", batchToVerify.BatchNumber-1) + } + inputProver := &prover.StatelessInputProver{ PublicInputs: &prover.StatelessPublicInputs{ Witness: witness, - OldAccInputHash: a.getAccInputHash(batchToVerify.BatchNumber - 1).Bytes(), + OldAccInputHash: oldAccInputHash.Bytes(), OldBatchNum: batchToVerify.BatchNumber - 1, ChainId: batchToVerify.ChainID, ForkId: batchToVerify.ForkID, diff --git a/aggregator/aggregator_test.go b/aggregator/aggregator_test.go index 506ce16c..8d5b5392 100644 --- a/aggregator/aggregator_test.go +++ b/aggregator/aggregator_test.go @@ -1114,7 +1114,7 @@ func Test_tryAggregateProofs(t *testing.T) { Return(nil). Once() m.proverMock.On("AggregatedProof", proof1.Proof, proof2.Proof).Return(&proofID, nil).Once() - m.proverMock.On("WaitRecursiveProof", mock.MatchedBy(matchProverCtxFn), proofID).Return("", common.Hash{}, errTest).Once() + m.proverMock.On("WaitRecursiveProof", mock.MatchedBy(matchProverCtxFn), proofID).Return("", common.Hash{}, common.Hash{}, errTest).Once() m.stateMock.On("BeginStateTransaction", mock.MatchedBy(matchAggregatorCtxFn)).Return(dbTx, nil).Once().NotBefore(lockProofsTxBegin) m.stateMock. On("UpdateGeneratedProof", mock.MatchedBy(matchAggregatorCtxFn), &proof1, dbTx). @@ -1172,7 +1172,7 @@ func Test_tryAggregateProofs(t *testing.T) { Return(nil). Once() m.proverMock.On("AggregatedProof", proof1.Proof, proof2.Proof).Return(&proofID, nil).Once() - m.proverMock.On("WaitRecursiveProof", mock.MatchedBy(matchProverCtxFn), proofID).Return("", common.Hash{}, errTest).Once() + m.proverMock.On("WaitRecursiveProof", mock.MatchedBy(matchProverCtxFn), proofID).Return("", common.Hash{}, common.Hash{}, errTest).Once() m.stateMock.On("BeginStateTransaction", mock.MatchedBy(matchAggregatorCtxFn)).Return(dbTx, nil).Once().NotBefore(lockProofsTxBegin) m.stateMock. On("UpdateGeneratedProof", mock.MatchedBy(matchAggregatorCtxFn), &proof1, dbTx). @@ -1220,7 +1220,7 @@ func Test_tryAggregateProofs(t *testing.T) { Return(nil). Once() m.proverMock.On("AggregatedProof", proof1.Proof, proof2.Proof).Return(&proofID, nil).Once() - m.proverMock.On("WaitRecursiveProof", mock.MatchedBy(matchProverCtxFn), proofID).Return(recursiveProof, common.Hash{}, nil).Once() + m.proverMock.On("WaitRecursiveProof", mock.MatchedBy(matchProverCtxFn), proofID).Return(recursiveProof, common.Hash{}, common.Hash{}, nil).Once() m.stateMock.On("DeleteGeneratedProofs", mock.MatchedBy(matchProverCtxFn), proof1.BatchNumber, proof2.BatchNumberFinal, dbTx).Return(errTest).Once() dbTx.On("Rollback", mock.MatchedBy(matchProverCtxFn)).Return(nil).Once() m.stateMock.On("BeginStateTransaction", mock.MatchedBy(matchAggregatorCtxFn)).Return(dbTx, nil).Once().NotBefore(lockProofsTxBegin) @@ -1280,7 +1280,7 @@ func Test_tryAggregateProofs(t *testing.T) { Return(nil). Once() m.proverMock.On("AggregatedProof", proof1.Proof, proof2.Proof).Return(&proofID, nil).Once() - m.proverMock.On("WaitRecursiveProof", mock.MatchedBy(matchProverCtxFn), proofID).Return(recursiveProof, common.Hash{}, nil).Once() + m.proverMock.On("WaitRecursiveProof", mock.MatchedBy(matchProverCtxFn), proofID).Return(recursiveProof, common.Hash{}, common.Hash{}, nil).Once() m.stateMock.On("DeleteGeneratedProofs", mock.MatchedBy(matchProverCtxFn), proof1.BatchNumber, proof2.BatchNumberFinal, dbTx).Return(nil).Once() m.stateMock.On("AddGeneratedProof", mock.MatchedBy(matchProverCtxFn), mock.Anything, dbTx).Return(errTest).Once() dbTx.On("Rollback", mock.MatchedBy(matchProverCtxFn)).Return(nil).Once() @@ -1315,6 +1315,7 @@ func Test_tryAggregateProofs(t *testing.T) { { name: "time to send final, state error", setup: func(m mox, a *Aggregator) { + a.accInputHashes = make(map[uint64]common.Hash) a.cfg.VerifyProofInterval = types.Duration{Duration: time.Nanosecond} m.proverMock.On("Name").Return(proverName).Times(3) m.proverMock.On("ID").Return(proverID).Times(3) @@ -1343,7 +1344,7 @@ func Test_tryAggregateProofs(t *testing.T) { Once() m.proverMock.On("AggregatedProof", proof1.Proof, proof2.Proof).Return(&proofID, nil).Once() - m.proverMock.On("WaitRecursiveProof", mock.MatchedBy(matchProverCtxFn), proofID).Return(recursiveProof, common.Hash{}, nil).Once() + m.proverMock.On("WaitRecursiveProof", mock.MatchedBy(matchProverCtxFn), proofID).Return(recursiveProof, common.Hash{}, common.Hash{}, nil).Once() m.stateMock.On("DeleteGeneratedProofs", mock.MatchedBy(matchProverCtxFn), proof1.BatchNumber, proof2.BatchNumberFinal, dbTx).Return(nil).Once() expectedInputProver := map[string]interface{}{ "recursive_proof_1": proof1.Proof, @@ -1443,6 +1444,7 @@ func Test_tryGenerateBatchProof(t *testing.T) { IntervalAfterWhichBatchConsolidateAnyway: types.Duration{Duration: time.Second * 1}, ChainID: uint64(1), ForkId: uint64(12), + BatchProofSanityCheckEnabled: true, } lastVerifiedBatchNum := uint64(22) @@ -1456,8 +1458,8 @@ func Test_tryGenerateBatchProof(t *testing.T) { proverName := "proverName" proverID := "proverID" - recursiveProof := "recursiveProof" errTest := errors.New("test error") + errAIH := fmt.Errorf("failed to build input prover, acc input hash for previous batch (22) is not in memory") proverCtx := context.WithValue(context.Background(), "owner", ownerProver) //nolint:staticcheck matchProverCtxFn := func(ctx context.Context) bool { return ctx.Value("owner") == ownerProver } matchAggregatorCtxFn := func(ctx context.Context) bool { return ctx.Value("owner") == ownerAggregator } @@ -1491,6 +1493,49 @@ func Test_tryGenerateBatchProof(t *testing.T) { setup func(mox, *Aggregator) asserts func(bool, *Aggregator, error) }{ + { + name: "getAndLockBatchToProve returns AIH error", + setup: func(m mox, a *Aggregator) { + sequence := synchronizer.SequencedBatches{ + FromBatchNumber: uint64(1), + ToBatchNumber: uint64(2), + } + l1InfoRoot := common.HexToHash("0x057e9950fbd39b002e323f37c2330d0c096e66919e24cc96fb4b2dfa8f4af782") + + virtualBatch := synchronizer.VirtualBatch{ + BatchNumber: 1, + BatchL2Data: []byte{ + 0xb, 0x0, 0x0, 0x0, 0x7b, 0x0, 0x0, 0x1, 0xc8, 0xb, 0x0, 0x0, 0x3, 0x15, 0x0, 0x1, 0x8a, 0xf8, + }, + L1InfoRoot: &l1InfoRoot, + } + rpcBatch := rpctypes.NewRPCBatch(lastVerifiedBatchNum+1, common.Hash{}, []string{}, []byte("batchL2Data"), common.Hash{}, common.BytesToHash([]byte("mock LocalExitRoot")), common.BytesToHash([]byte("mock StateRoot")), common.Address{}, false) + + m.proverMock.On("Name").Return(proverName) + m.proverMock.On("ID").Return(proverID) + m.proverMock.On("Addr").Return("addr") + m.etherman.On("GetLatestVerifiedBatchNum").Return(uint64(0), nil) + m.stateMock.On("CheckProofExistsForBatch", mock.Anything, uint64(1), nil).Return(false, nil) + m.synchronizerMock.On("GetSequenceByBatchNumber", mock.Anything, mock.Anything).Return(&sequence, nil) + m.synchronizerMock.On("GetVirtualBatchByBatchNumber", mock.Anything, mock.Anything).Return(&virtualBatch, nil) + m.synchronizerMock.On("GetL1BlockByNumber", mock.Anything, mock.Anything).Return(&synchronizer.L1Block{ParentHash: common.Hash{}}, nil) + m.rpcMock.On("GetBatch", mock.Anything).Return(rpcBatch, nil) + m.rpcMock.On("GetWitness", mock.Anything, false).Return([]byte("witness"), nil) + m.stateMock.On("AddGeneratedProof", mock.Anything, mock.Anything, nil).Return(nil) + m.stateMock.On("AddSequence", mock.Anything, mock.Anything, nil).Return(nil) + m.stateMock.On("DeleteGeneratedProofs", mock.Anything, uint64(1), uint64(1), nil).Return(nil) + m.synchronizerMock.On("GetLeafsByL1InfoRoot", mock.Anything, l1InfoRoot).Return(l1InfoTreeLeaf, nil) + m.synchronizerMock.On("GetL1InfoTreeLeaves", mock.Anything, mock.Anything).Return(map[uint32]synchronizer.L1InfoTreeLeaf{ + 1: { + BlockNumber: uint64(1), + }, + }, nil) + }, + asserts: func(result bool, a *Aggregator, err error) { + assert.False(result) + assert.ErrorContains(err, errAIH.Error()) + }, + }, { name: "getAndLockBatchToProve returns generic error", setup: func(m mox, a *Aggregator) { @@ -1534,20 +1579,20 @@ func Test_tryGenerateBatchProof(t *testing.T) { L1InfoRoot: &l1InfoRoot, } - m.synchronizerMock.On("GetVirtualBatchByBatchNumber", mock.Anything, lastVerifiedBatchNum).Return(&virtualBatch, nil).Once() + m.synchronizerMock.On("GetVirtualBatchByBatchNumber", mock.Anything, mock.Anything).Return(&virtualBatch, nil).Once() m.etherman.On("GetLatestVerifiedBatchNum").Return(lastVerifiedBatchNum, nil).Once() - m.stateMock.On("CheckProofExistsForBatch", mock.MatchedBy(matchProverCtxFn), lastVerifiedBatchNum+1, nil).Return(true, nil).Once() + m.stateMock.On("CheckProofExistsForBatch", mock.MatchedBy(matchProverCtxFn), mock.Anything, nil).Return(true, nil) m.stateMock.On("CleanupGeneratedProofs", mock.Anything, mock.Anything, mock.Anything).Return(nil).Once() sequence := synchronizer.SequencedBatches{ FromBatchNumber: uint64(10), ToBatchNumber: uint64(20), } - m.synchronizerMock.On("GetSequenceByBatchNumber", mock.MatchedBy(matchProverCtxFn), lastVerifiedBatchNum).Return(&sequence, nil).Once() + m.synchronizerMock.On("GetSequenceByBatchNumber", mock.Anything, mock.Anything).Return(&sequence, nil) rpcBatch := rpctypes.NewRPCBatch(lastVerifiedBatchNum+1, common.Hash{}, []string{}, batchL2Data, common.Hash{}, common.BytesToHash([]byte("mock LocalExitRoot")), common.BytesToHash([]byte("mock StateRoot")), common.Address{}, false) rpcBatch.SetLastL2BLockTimestamp(uint64(time.Now().Unix())) - m.rpcMock.On("GetWitness", lastVerifiedBatchNum, false).Return([]byte("witness"), nil) - m.rpcMock.On("GetBatch", lastVerifiedBatchNum).Return(rpcBatch, nil) + m.rpcMock.On("GetWitness", mock.Anything, false).Return([]byte("witness"), nil) + m.rpcMock.On("GetBatch", mock.Anything).Return(rpcBatch, nil) m.stateMock.On("AddSequence", mock.MatchedBy(matchProverCtxFn), mock.Anything, nil).Return(nil).Once() m.stateMock.On("AddGeneratedProof", mock.MatchedBy(matchProverCtxFn), mock.Anything, nil).Run( func(args mock.Arguments) { @@ -1587,15 +1632,6 @@ func Test_tryGenerateBatchProof(t *testing.T) { batchL2Data, err := hex.DecodeString(codedL2Block1) require.NoError(err) l1InfoRoot := common.HexToHash("0x057e9950fbd39b002e323f37c2330d0c096e66919e24cc96fb4b2dfa8f4af782") - batch := state.Batch{ - BatchNumber: lastVerifiedBatchNum + 1, - BatchL2Data: batchL2Data, - L1InfoRoot: l1InfoRoot, - Timestamp: time.Now(), - Coinbase: common.Address{}, - ChainID: uint64(1), - ForkID: uint64(12), - } virtualBatch := synchronizer.VirtualBatch{ BatchNumber: lastVerifiedBatchNum + 1, @@ -1630,20 +1666,17 @@ func Test_tryGenerateBatchProof(t *testing.T) { }, ).Return(nil).Once() - m.synchronizerMock.On("GetLeafsByL1InfoRoot", mock.Anything, l1InfoRoot).Return(l1InfoTreeLeaf, nil).Twice() + m.synchronizerMock.On("GetLeafsByL1InfoRoot", mock.Anything, l1InfoRoot).Return(l1InfoTreeLeaf, nil) m.synchronizerMock.On("GetL1InfoTreeLeaves", mock.Anything, mock.Anything).Return(map[uint32]synchronizer.L1InfoTreeLeaf{ 1: { BlockNumber: uint64(35), }, - }, nil).Twice() + }, nil) m.rpcMock.On("GetBatch", lastVerifiedBatchNum+1).Return(rpcBatch, nil) - expectedInputProver, err := a.buildInputProver(context.Background(), &batch, []byte("witness")) - require.NoError(err) - - m.proverMock.On("BatchProof", expectedInputProver).Return(&proofID, nil).Once() - m.proverMock.On("WaitRecursiveProof", mock.MatchedBy(matchProverCtxFn), proofID).Return("", common.Hash{}, errTest).Once() - m.stateMock.On("DeleteGeneratedProofs", mock.MatchedBy(matchAggregatorCtxFn), batchToProve.BatchNumber, batchToProve.BatchNumber, nil).Return(nil).Once() + m.proverMock.On("BatchProof", mock.Anything).Return(&proofID, nil).Once() + m.proverMock.On("WaitRecursiveProof", mock.MatchedBy(matchProverCtxFn), proofID).Return("", common.Hash{}, common.Hash{}, errTest).Once() + m.stateMock.On("DeleteGeneratedProofs", mock.MatchedBy(matchAggregatorCtxFn), batchToProve.BatchNumber, batchToProve.BatchNumber, nil).Return(nil) }, asserts: func(result bool, a *Aggregator, err error) { assert.False(result) @@ -1651,36 +1684,35 @@ func Test_tryGenerateBatchProof(t *testing.T) { }, }, { - name: "DeleteBatchProofs error after WaitRecursiveProof prover error", + name: "WaitRecursiveProof no error", setup: func(m mox, a *Aggregator) { - m.proverMock.On("Name").Return(proverName).Twice() - m.proverMock.On("ID").Return(proverID).Twice() - m.proverMock.On("Addr").Return("addr").Twice() + m.proverMock.On("Name").Return(proverName) + m.proverMock.On("ID").Return(proverID) + m.proverMock.On("Addr").Return("addr") batchL2Data, err := hex.DecodeString(codedL2Block1) require.NoError(err) l1InfoRoot := common.HexToHash("0x057e9950fbd39b002e323f37c2330d0c096e66919e24cc96fb4b2dfa8f4af782") - batch := state.Batch{ + + virtualBatch := synchronizer.VirtualBatch{ BatchNumber: lastVerifiedBatchNum + 1, BatchL2Data: batchL2Data, - L1InfoRoot: l1InfoRoot, - Timestamp: time.Now(), - Coinbase: common.Address{}, - ChainID: uint64(1), - ForkID: uint64(12), + L1InfoRoot: &l1InfoRoot, } - m.etherman.On("GetLatestVerifiedBatchNum").Return(lastVerifiedBatchNum, nil).Once() - m.stateMock.On("CheckProofExistsForBatch", mock.MatchedBy(matchProverCtxFn), mock.AnythingOfType("uint64"), nil).Return(false, nil).Once() + m.synchronizerMock.On("GetVirtualBatchByBatchNumber", mock.Anything, lastVerifiedBatchNum+1).Return(&virtualBatch, nil) + + m.etherman.On("GetLatestVerifiedBatchNum").Return(lastVerifiedBatchNum, nil) + m.stateMock.On("CheckProofExistsForBatch", mock.MatchedBy(matchProverCtxFn), mock.AnythingOfType("uint64"), nil).Return(false, nil) sequence := synchronizer.SequencedBatches{ FromBatchNumber: uint64(10), ToBatchNumber: uint64(20), } - m.synchronizerMock.On("GetSequenceByBatchNumber", mock.MatchedBy(matchProverCtxFn), lastVerifiedBatchNum+1).Return(&sequence, nil).Once() + m.synchronizerMock.On("GetSequenceByBatchNumber", mock.MatchedBy(matchProverCtxFn), lastVerifiedBatchNum+1).Return(&sequence, nil) rpcBatch := rpctypes.NewRPCBatch(lastVerifiedBatchNum+1, common.Hash{}, []string{}, batchL2Data, common.Hash{}, common.BytesToHash([]byte("mock LocalExitRoot")), common.BytesToHash([]byte("mock StateRoot")), common.Address{}, false) rpcBatch.SetLastL2BLockTimestamp(uint64(time.Now().Unix())) - m.rpcMock.On("GetBatch", lastVerifiedBatchNum+1).Return(rpcBatch, nil) - m.stateMock.On("AddSequence", mock.MatchedBy(matchProverCtxFn), mock.Anything, nil).Return(nil).Once() + m.rpcMock.On("GetWitness", lastVerifiedBatchNum+1, false).Return([]byte("witness"), nil) + m.stateMock.On("AddSequence", mock.MatchedBy(matchProverCtxFn), mock.Anything, nil).Return(nil) m.stateMock.On("AddGeneratedProof", mock.MatchedBy(matchProverCtxFn), mock.Anything, nil).Run( func(args mock.Arguments) { proof, ok := args[1].(*state.Proof) @@ -1693,57 +1725,35 @@ func Test_tryGenerateBatchProof(t *testing.T) { assert.Equal(&proverID, proof.ProverID) assert.InDelta(time.Now().Unix(), proof.GeneratingSince.Unix(), float64(time.Second)) }, - ).Return(nil).Once() + ).Return(nil) - m.synchronizerMock.On("GetLeafsByL1InfoRoot", mock.Anything, l1InfoRoot).Return(l1InfoTreeLeaf, nil).Twice() + m.synchronizerMock.On("GetLeafsByL1InfoRoot", mock.Anything, l1InfoRoot).Return(l1InfoTreeLeaf, nil) m.synchronizerMock.On("GetL1InfoTreeLeaves", mock.Anything, mock.Anything).Return(map[uint32]synchronizer.L1InfoTreeLeaf{ 1: { BlockNumber: uint64(35), }, - }, nil).Twice() - - expectedInputProver, err := a.buildInputProver(context.Background(), &batch, []byte("witness")) - require.NoError(err) - - m.rpcMock.On("GetWitness", lastVerifiedBatchNum+1, false).Return([]byte("witness"), nil) - - virtualBatch := synchronizer.VirtualBatch{ - BatchNumber: lastVerifiedBatchNum + 1, - BatchL2Data: batchL2Data, - L1InfoRoot: &l1InfoRoot, - } - - m.synchronizerMock.On("GetVirtualBatchByBatchNumber", mock.Anything, lastVerifiedBatchNum+1).Return(&virtualBatch, nil).Once() + }, nil) - m.proverMock.On("BatchProof", expectedInputProver).Return(&proofID, nil).Once() - m.proverMock.On("WaitRecursiveProof", mock.MatchedBy(matchProverCtxFn), proofID).Return("", common.Hash{}, errTest).Once() - m.stateMock.On("DeleteGeneratedProofs", mock.MatchedBy(matchAggregatorCtxFn), batchToProve.BatchNumber, batchToProve.BatchNumber, nil).Return(errTest).Once() + m.rpcMock.On("GetBatch", lastVerifiedBatchNum+1).Return(rpcBatch, nil) + m.proverMock.On("BatchProof", mock.Anything).Return(&proofID, nil) + m.proverMock.On("WaitRecursiveProof", mock.MatchedBy(matchProverCtxFn), proofID).Return("", common.Hash{}, common.Hash{}, nil) + m.stateMock.On("UpdateGeneratedProof", mock.Anything, mock.Anything, nil).Return(nil) }, asserts: func(result bool, a *Aggregator, err error) { - assert.False(result) - assert.ErrorIs(err, errTest) + assert.True(result) + assert.NoError(err) }, }, { - name: "not time to send final ok", + name: "DeleteBatchProofs error after WaitRecursiveProof prover error", setup: func(m mox, a *Aggregator) { - a.cfg.BatchProofSanityCheckEnabled = false - m.proverMock.On("Name").Return(proverName).Times(3) - m.proverMock.On("ID").Return(proverID).Times(3) - m.proverMock.On("Addr").Return("addr").Times(3) + m.proverMock.On("Name").Return(proverName).Twice() + m.proverMock.On("ID").Return(proverID).Twice() + m.proverMock.On("Addr").Return("addr").Twice() batchL2Data, err := hex.DecodeString(codedL2Block1) require.NoError(err) l1InfoRoot := common.HexToHash("0x057e9950fbd39b002e323f37c2330d0c096e66919e24cc96fb4b2dfa8f4af782") - batch := state.Batch{ - BatchNumber: lastVerifiedBatchNum + 1, - BatchL2Data: batchL2Data, - L1InfoRoot: l1InfoRoot, - Timestamp: time.Now(), - Coinbase: common.Address{}, - ChainID: uint64(1), - ForkID: uint64(12), - } m.etherman.On("GetLatestVerifiedBatchNum").Return(lastVerifiedBatchNum, nil).Once() m.stateMock.On("CheckProofExistsForBatch", mock.MatchedBy(matchProverCtxFn), mock.AnythingOfType("uint64"), nil).Return(false, nil).Once() @@ -1752,7 +1762,9 @@ func Test_tryGenerateBatchProof(t *testing.T) { ToBatchNumber: uint64(20), } m.synchronizerMock.On("GetSequenceByBatchNumber", mock.MatchedBy(matchProverCtxFn), lastVerifiedBatchNum+1).Return(&sequence, nil).Once() - + rpcBatch := rpctypes.NewRPCBatch(lastVerifiedBatchNum+1, common.Hash{}, []string{}, batchL2Data, common.Hash{}, common.BytesToHash([]byte("mock LocalExitRoot")), common.BytesToHash([]byte("mock StateRoot")), common.Address{}, false) + rpcBatch.SetLastL2BLockTimestamp(uint64(time.Now().Unix())) + m.rpcMock.On("GetBatch", lastVerifiedBatchNum+1).Return(rpcBatch, nil) m.stateMock.On("AddSequence", mock.MatchedBy(matchProverCtxFn), mock.Anything, nil).Return(nil).Once() m.stateMock.On("AddGeneratedProof", mock.MatchedBy(matchProverCtxFn), mock.Anything, nil).Run( func(args mock.Arguments) { @@ -1768,81 +1780,12 @@ func Test_tryGenerateBatchProof(t *testing.T) { }, ).Return(nil).Once() - m.synchronizerMock.On("GetLeafsByL1InfoRoot", mock.Anything, l1InfoRoot).Return(l1InfoTreeLeaf, nil).Twice() + m.synchronizerMock.On("GetLeafsByL1InfoRoot", mock.Anything, l1InfoRoot).Return(l1InfoTreeLeaf, nil) m.synchronizerMock.On("GetL1InfoTreeLeaves", mock.Anything, mock.Anything).Return(map[uint32]synchronizer.L1InfoTreeLeaf{ 1: { BlockNumber: uint64(35), }, - }, nil).Twice() - - rpcBatch := rpctypes.NewRPCBatch(lastVerifiedBatchNum+1, common.Hash{}, []string{}, batchL2Data, common.Hash{}, common.BytesToHash([]byte("mock LocalExitRoot")), common.BytesToHash([]byte("mock StateRoot")), common.Address{}, false) - rpcBatch.SetLastL2BLockTimestamp(uint64(time.Now().Unix())) - m.rpcMock.On("GetBatch", lastVerifiedBatchNum+1).Return(rpcBatch, nil) - m.rpcMock.On("GetWitness", lastVerifiedBatchNum+1, false).Return([]byte("witness"), nil) - - virtualBatch := synchronizer.VirtualBatch{ - BatchNumber: lastVerifiedBatchNum + 1, - BatchL2Data: batchL2Data, - L1InfoRoot: &l1InfoRoot, - } - - m.synchronizerMock.On("GetVirtualBatchByBatchNumber", mock.Anything, lastVerifiedBatchNum+1).Return(&virtualBatch, nil).Once() - - expectedInputProver, err := a.buildInputProver(context.Background(), &batch, []byte("witness")) - require.NoError(err) - - m.proverMock.On("BatchProof", expectedInputProver).Return(&proofID, nil).Once() - m.proverMock.On("WaitRecursiveProof", mock.MatchedBy(matchProverCtxFn), proofID).Return(recursiveProof, common.Hash{}, nil).Once() - m.stateMock.On("UpdateGeneratedProof", mock.MatchedBy(matchAggregatorCtxFn), mock.Anything, nil).Run( - func(args mock.Arguments) { - proof, ok := args[1].(*state.Proof) - if !ok { - t.Fatalf("expected args[1] to be of type *state.Proof, got %T", args[1]) - } - assert.Equal(batchToProve.BatchNumber, proof.BatchNumber) - assert.Equal(batchToProve.BatchNumber, proof.BatchNumberFinal) - assert.Equal(&proverName, proof.Prover) - assert.Equal(&proverID, proof.ProverID) - assert.Equal("", proof.InputProver) - assert.Equal(recursiveProof, proof.Proof) - assert.Nil(proof.GeneratingSince) - }, - ).Return(nil).Once() - }, - asserts: func(result bool, a *Aggregator, err error) { - assert.True(result) - assert.NoError(err) - }, - }, - { - name: "time to send final, state error ok", - setup: func(m mox, a *Aggregator) { - a.cfg.VerifyProofInterval = types.NewDuration(0) - a.cfg.BatchProofSanityCheckEnabled = false - m.proverMock.On("Name").Return(proverName).Times(3) - m.proverMock.On("ID").Return(proverID).Times(3) - m.proverMock.On("Addr").Return("addr").Times(3) - - batchL2Data, err := hex.DecodeString(codedL2Block1) - require.NoError(err) - l1InfoRoot := common.HexToHash("0x057e9950fbd39b002e323f37c2330d0c096e66919e24cc96fb4b2dfa8f4af782") - batch := state.Batch{ - BatchNumber: lastVerifiedBatchNum + 1, - BatchL2Data: batchL2Data, - L1InfoRoot: l1InfoRoot, - Timestamp: time.Now(), - Coinbase: common.Address{}, - ChainID: uint64(1), - ForkID: uint64(12), - } - - m.etherman.On("GetLatestVerifiedBatchNum").Return(lastVerifiedBatchNum, nil).Once() - m.stateMock.On("CheckProofExistsForBatch", mock.MatchedBy(matchProverCtxFn), mock.AnythingOfType("uint64"), nil).Return(false, nil).Once() - sequence := synchronizer.SequencedBatches{ - FromBatchNumber: uint64(10), - ToBatchNumber: uint64(20), - } - m.synchronizerMock.On("GetSequenceByBatchNumber", mock.MatchedBy(matchProverCtxFn), lastVerifiedBatchNum+1).Return(&sequence, nil).Once() + }, nil) m.rpcMock.On("GetWitness", lastVerifiedBatchNum+1, false).Return([]byte("witness"), nil) @@ -1854,63 +1797,18 @@ func Test_tryGenerateBatchProof(t *testing.T) { m.synchronizerMock.On("GetVirtualBatchByBatchNumber", mock.Anything, lastVerifiedBatchNum+1).Return(&virtualBatch, nil).Once() - m.rpcMock.On("GetWitness", lastVerifiedBatchNum+1, false).Return([]byte("witness"), nil) - rpcBatch := rpctypes.NewRPCBatch(lastVerifiedBatchNum+1, common.Hash{}, []string{}, batchL2Data, common.Hash{}, common.BytesToHash([]byte("mock LocalExitRoot")), common.BytesToHash([]byte("mock StateRoot")), common.Address{}, false) - rpcBatch.SetLastL2BLockTimestamp(uint64(time.Now().Unix())) - m.rpcMock.On("GetBatch", lastVerifiedBatchNum+1).Return(rpcBatch, nil) - - m.stateMock.On("AddSequence", mock.MatchedBy(matchProverCtxFn), mock.Anything, nil).Return(nil).Once() - m.stateMock.On("AddGeneratedProof", mock.MatchedBy(matchProverCtxFn), mock.Anything, nil).Run( - func(args mock.Arguments) { - proof, ok := args[1].(*state.Proof) - if !ok { - t.Fatalf("expected args[1] to be of type *state.Proof, got %T", args[1]) - } - assert.Equal(batchToProve.BatchNumber, proof.BatchNumber) - assert.Equal(batchToProve.BatchNumber, proof.BatchNumberFinal) - assert.Equal(&proverName, proof.Prover) - assert.Equal(&proverID, proof.ProverID) - assert.InDelta(time.Now().Unix(), proof.GeneratingSince.Unix(), float64(time.Second)) - }, - ).Return(nil).Once() - - m.synchronizerMock.On("GetLeafsByL1InfoRoot", mock.Anything, l1InfoRoot).Return(l1InfoTreeLeaf, nil).Twice() - m.synchronizerMock.On("GetL1InfoTreeLeaves", mock.Anything, mock.Anything).Return(map[uint32]synchronizer.L1InfoTreeLeaf{ - 1: { - BlockNumber: uint64(35), - }, - }, nil).Twice() - - expectedInputProver, err := a.buildInputProver(context.Background(), &batch, []byte("witness")) - require.NoError(err) - - m.proverMock.On("BatchProof", expectedInputProver).Return(&proofID, nil).Once() - m.proverMock.On("WaitRecursiveProof", mock.MatchedBy(matchProverCtxFn), proofID).Return(recursiveProof, common.Hash{}, nil).Once() - m.etherman.On("GetLatestVerifiedBatchNum").Return(uint64(42), errTest).Once() - m.stateMock.On("UpdateGeneratedProof", mock.MatchedBy(matchAggregatorCtxFn), mock.Anything, nil).Run( - func(args mock.Arguments) { - proof, ok := args[1].(*state.Proof) - if !ok { - t.Fatalf("expected args[1] to be of type *state.Proof, got %T", args[1]) - } - assert.Equal(batchToProve.BatchNumber, proof.BatchNumber) - assert.Equal(batchToProve.BatchNumber, proof.BatchNumberFinal) - assert.Equal(&proverName, proof.Prover) - assert.Equal(&proverID, proof.ProverID) - assert.Equal("", proof.InputProver) - assert.Equal(recursiveProof, proof.Proof) - assert.Nil(proof.GeneratingSince) - }, - ).Return(nil).Once() + m.proverMock.On("BatchProof", mock.Anything).Return(&proofID, nil).Once() + m.proverMock.On("WaitRecursiveProof", mock.MatchedBy(matchProverCtxFn), proofID).Return("", common.Hash{}, common.Hash{}, errTest).Once() + m.stateMock.On("DeleteGeneratedProofs", mock.Anything, batchToProve.BatchNumber, batchToProve.BatchNumber, nil).Return(errTest).Once() }, asserts: func(result bool, a *Aggregator, err error) { - assert.True(result) - assert.NoError(err) + assert.False(result) + assert.ErrorIs(err, errTest) }, }, } - for _, tc := range testCases { + for x, tc := range testCases { t.Run(tc.name, func(t *testing.T) { stateMock := mocks.NewStateInterfaceMock(t) ethTxManager := mocks.NewEthTxManagerClientMock(t) @@ -1935,6 +1833,9 @@ func Test_tryGenerateBatchProof(t *testing.T) { accInputHashes: make(map[uint64]common.Hash), accInputHashesMutex: &sync.Mutex{}, } + if x > 0 { + a.accInputHashes = populateAccInputHashes() + } aggregatorCtx := context.WithValue(context.Background(), "owner", ownerAggregator) //nolint:staticcheck a.ctx, a.exit = context.WithCancel(aggregatorCtx) @@ -1960,6 +1861,14 @@ func Test_tryGenerateBatchProof(t *testing.T) { } } +func populateAccInputHashes() map[uint64]common.Hash { + accInputHashes := make(map[uint64]common.Hash) + for i := 10; i < 200; i++ { + accInputHashes[uint64(i)] = common.BytesToHash([]byte(fmt.Sprintf("hash%d", i))) + } + return accInputHashes +} + func Test_accInputHashFunctions(t *testing.T) { aggregator := Aggregator{ accInputHashes: make(map[uint64]common.Hash), @@ -1980,3 +1889,32 @@ func Test_accInputHashFunctions(t *testing.T) { aggregator.removeAccInputHashes(1, 2) assert.Equal(t, 0, len(aggregator.accInputHashes)) } + +func Test_sanityChecks(t *testing.T) { + batchToProve := state.Batch{ + BatchNumber: 1, + StateRoot: common.HexToHash("0x01"), + AccInputHash: common.HexToHash("0x02"), + } + + aggregator := Aggregator{ + accInputHashes: make(map[uint64]common.Hash), + accInputHashesMutex: &sync.Mutex{}, + } + + aggregator.performSanityChecks(log.GetDefaultLogger(), batchToProve.StateRoot, batchToProve.AccInputHash, &batchToProve) + + // Halt by SR sanity check + go func() { + aggregator.performSanityChecks(log.GetDefaultLogger(), common.HexToHash("0x03"), batchToProve.AccInputHash, &batchToProve) + time.Sleep(5 * time.Second) + return + }() + + // Halt by AIH sanity check + go func() { + aggregator.performSanityChecks(log.GetDefaultLogger(), batchToProve.StateRoot, common.HexToHash("0x04"), &batchToProve) + time.Sleep(5 * time.Second) + return + }() +} diff --git a/aggregator/interfaces.go b/aggregator/interfaces.go index 81f63d94..f1673c46 100644 --- a/aggregator/interfaces.go +++ b/aggregator/interfaces.go @@ -30,7 +30,7 @@ type ProverInterface interface { BatchProof(input *prover.StatelessInputProver) (*string, error) AggregatedProof(inputProof1, inputProof2 string) (*string, error) FinalProof(inputProof string, aggregatorAddr string) (*string, error) - WaitRecursiveProof(ctx context.Context, proofID string) (string, common.Hash, error) + WaitRecursiveProof(ctx context.Context, proofID string) (string, common.Hash, common.Hash, error) WaitFinalProof(ctx context.Context, proofID string) (*prover.FinalProof, error) } diff --git a/aggregator/mocks/mock_prover.go b/aggregator/mocks/mock_prover.go index 72bd66dc..b6ce1011 100644 --- a/aggregator/mocks/mock_prover.go +++ b/aggregator/mocks/mock_prover.go @@ -220,7 +220,7 @@ func (_m *ProverInterfaceMock) WaitFinalProof(ctx context.Context, proofID strin } // WaitRecursiveProof provides a mock function with given fields: ctx, proofID -func (_m *ProverInterfaceMock) WaitRecursiveProof(ctx context.Context, proofID string) (string, common.Hash, error) { +func (_m *ProverInterfaceMock) WaitRecursiveProof(ctx context.Context, proofID string) (string, common.Hash, common.Hash, error) { ret := _m.Called(ctx, proofID) if len(ret) == 0 { @@ -229,8 +229,9 @@ func (_m *ProverInterfaceMock) WaitRecursiveProof(ctx context.Context, proofID s var r0 string var r1 common.Hash - var r2 error - if rf, ok := ret.Get(0).(func(context.Context, string) (string, common.Hash, error)); ok { + var r2 common.Hash + var r3 error + if rf, ok := ret.Get(0).(func(context.Context, string) (string, common.Hash, common.Hash, error)); ok { return rf(ctx, proofID) } if rf, ok := ret.Get(0).(func(context.Context, string) string); ok { @@ -247,13 +248,21 @@ func (_m *ProverInterfaceMock) WaitRecursiveProof(ctx context.Context, proofID s } } - if rf, ok := ret.Get(2).(func(context.Context, string) error); ok { + if rf, ok := ret.Get(2).(func(context.Context, string) common.Hash); ok { r2 = rf(ctx, proofID) } else { - r2 = ret.Error(2) + if ret.Get(2) != nil { + r2 = ret.Get(2).(common.Hash) + } + } + + if rf, ok := ret.Get(3).(func(context.Context, string) error); ok { + r3 = rf(ctx, proofID) + } else { + r3 = ret.Error(3) } - return r0, r1, r2 + return r0, r1, r2, r3 } // NewProverInterfaceMock creates a new instance of ProverInterfaceMock. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. diff --git a/aggregator/prover/mocks/mock_channel.go b/aggregator/prover/mocks/mock_channel.go new file mode 100644 index 00000000..d125896d --- /dev/null +++ b/aggregator/prover/mocks/mock_channel.go @@ -0,0 +1,176 @@ +// Code generated by mockery v2.39.0. DO NOT EDIT. + +package mocks + +import ( + context "context" + + mock "github.com/stretchr/testify/mock" + metadata "google.golang.org/grpc/metadata" + + prover "github.com/0xPolygon/cdk/aggregator/prover" +) + +// ChannelMock is an autogenerated mock type for the AggregatorService_ChannelServer type +type ChannelMock struct { + mock.Mock +} + +// Context provides a mock function with given fields: +func (_m *ChannelMock) Context() context.Context { + ret := _m.Called() + + if len(ret) == 0 { + panic("no return value specified for Context") + } + + var r0 context.Context + if rf, ok := ret.Get(0).(func() context.Context); ok { + r0 = rf() + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(context.Context) + } + } + + return r0 +} + +// Recv provides a mock function with given fields: +func (_m *ChannelMock) Recv() (*prover.ProverMessage, error) { + ret := _m.Called() + + if len(ret) == 0 { + panic("no return value specified for Recv") + } + + var r0 *prover.ProverMessage + var r1 error + if rf, ok := ret.Get(0).(func() (*prover.ProverMessage, error)); ok { + return rf() + } + if rf, ok := ret.Get(0).(func() *prover.ProverMessage); ok { + r0 = rf() + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*prover.ProverMessage) + } + } + + if rf, ok := ret.Get(1).(func() error); ok { + r1 = rf() + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// RecvMsg provides a mock function with given fields: m +func (_m *ChannelMock) RecvMsg(m interface{}) error { + ret := _m.Called(m) + + if len(ret) == 0 { + panic("no return value specified for RecvMsg") + } + + var r0 error + if rf, ok := ret.Get(0).(func(interface{}) error); ok { + r0 = rf(m) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// Send provides a mock function with given fields: _a0 +func (_m *ChannelMock) Send(_a0 *prover.AggregatorMessage) error { + ret := _m.Called(_a0) + + if len(ret) == 0 { + panic("no return value specified for Send") + } + + var r0 error + if rf, ok := ret.Get(0).(func(*prover.AggregatorMessage) error); ok { + r0 = rf(_a0) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// SendHeader provides a mock function with given fields: _a0 +func (_m *ChannelMock) SendHeader(_a0 metadata.MD) error { + ret := _m.Called(_a0) + + if len(ret) == 0 { + panic("no return value specified for SendHeader") + } + + var r0 error + if rf, ok := ret.Get(0).(func(metadata.MD) error); ok { + r0 = rf(_a0) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// SendMsg provides a mock function with given fields: m +func (_m *ChannelMock) SendMsg(m interface{}) error { + ret := _m.Called(m) + + if len(ret) == 0 { + panic("no return value specified for SendMsg") + } + + var r0 error + if rf, ok := ret.Get(0).(func(interface{}) error); ok { + r0 = rf(m) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// SetHeader provides a mock function with given fields: _a0 +func (_m *ChannelMock) SetHeader(_a0 metadata.MD) error { + ret := _m.Called(_a0) + + if len(ret) == 0 { + panic("no return value specified for SetHeader") + } + + var r0 error + if rf, ok := ret.Get(0).(func(metadata.MD) error); ok { + r0 = rf(_a0) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// SetTrailer provides a mock function with given fields: _a0 +func (_m *ChannelMock) SetTrailer(_a0 metadata.MD) { + _m.Called(_a0) +} + +// NewChannelMock creates a new instance of ChannelMock. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. +// The first argument is typically a *testing.T value. +func NewChannelMock(t interface { + mock.TestingT + Cleanup(func()) +}) *ChannelMock { + mock := &ChannelMock{} + mock.Mock.Test(t) + + t.Cleanup(func() { mock.AssertExpectations(t) }) + + return mock +} diff --git a/aggregator/prover/prover.go b/aggregator/prover/prover.go index 8cb13b1d..ad9c9895 100644 --- a/aggregator/prover/prover.go +++ b/aggregator/prover/prover.go @@ -18,8 +18,10 @@ import ( ) const ( - stateRootStartIndex = 19 - stateRootFinalIndex = stateRootStartIndex + 8 + StateRootStartIndex = 19 + StateRootFinalIndex = StateRootStartIndex + 8 + AccInputHashStartIndex = 27 + AccInputHashFinalIndex = AccInputHashStartIndex + 8 ) var ( @@ -282,30 +284,36 @@ func (p *Prover) CancelProofRequest(proofID string) error { // WaitRecursiveProof waits for a recursive proof to be generated by the prover // and returns it. -func (p *Prover) WaitRecursiveProof(ctx context.Context, proofID string) (string, common.Hash, error) { +func (p *Prover) WaitRecursiveProof(ctx context.Context, proofID string) (string, common.Hash, common.Hash, error) { res, err := p.waitProof(ctx, proofID) if err != nil { - return "", common.Hash{}, err + return "", common.Hash{}, common.Hash{}, err } resProof, ok := res.Proof.(*GetProofResponse_RecursiveProof) if !ok { - return "", common.Hash{}, fmt.Errorf( + return "", common.Hash{}, common.Hash{}, fmt.Errorf( "%w, wanted %T, got %T", ErrBadProverResponse, &GetProofResponse_RecursiveProof{}, res.Proof, ) } - sr, err := GetStateRootFromProof(p.logger, resProof.RecursiveProof) + sr, err := GetSanityCheckHashFromProof(p.logger, resProof.RecursiveProof, StateRootStartIndex, StateRootFinalIndex) if err != nil && sr != (common.Hash{}) { p.logger.Errorf("Error getting state root from proof: %v", err) } + accInputHash, err := GetSanityCheckHashFromProof(p.logger, resProof.RecursiveProof, + AccInputHashStartIndex, AccInputHashFinalIndex) + if err != nil && accInputHash != (common.Hash{}) { + p.logger.Errorf("Error getting acc input hash from proof: %v", err) + } + if sr == (common.Hash{}) { p.logger.Info("Recursive proof does not contain state root. Possibly mock prover is in use.") } - return resProof.RecursiveProof, sr, nil + return resProof.RecursiveProof, sr, accInputHash, nil } // WaitFinalProof waits for the final proof to be generated by the prover and @@ -395,11 +403,8 @@ func (p *Prover) call(req *AggregatorMessage) (*ProverMessage, error) { return res, nil } -// GetStateRootFromProof returns the state root from the proof. -func GetStateRootFromProof(logger *log.Logger, proof string) (common.Hash, error) { - // Log received proof - logger.Debugf("Received proof to get SR from: %s", proof) - +// GetSanityCheckHashFromProof returns info from the proof +func GetSanityCheckHashFromProof(logger *log.Logger, proof string, startIndex, endIndex int) (common.Hash, error) { type Publics struct { Publics []string `mapstructure:"publics"` } @@ -420,7 +425,7 @@ func GetStateRootFromProof(logger *log.Logger, proof string) (common.Hash, error v [8]uint64 j = 0 ) - for i := stateRootStartIndex; i < stateRootFinalIndex; i++ { + for i := startIndex; i < endIndex; i++ { u64, err := strconv.ParseInt(publics.Publics[i], 10, 64) if err != nil { logger.Fatal(err) diff --git a/aggregator/prover/prover_test.go b/aggregator/prover/prover_test.go index 737d5592..438718a3 100644 --- a/aggregator/prover/prover_test.go +++ b/aggregator/prover/prover_test.go @@ -1,12 +1,19 @@ package prover_test import ( + "context" "fmt" + "net" "os" "testing" + "time" "github.com/0xPolygon/cdk/aggregator/prover" + "github.com/0xPolygon/cdk/aggregator/prover/mocks" + "github.com/0xPolygon/cdk/config/types" "github.com/0xPolygon/cdk/log" + "github.com/ethereum/go-ethereum/common" + "github.com/stretchr/testify/mock" "github.com/stretchr/testify/require" ) @@ -18,6 +25,50 @@ type TestStateRoot struct { Publics []string `mapstructure:"publics"` } +func TestProver(t *testing.T) { + mockChannel := mocks.ChannelMock{} + var addr net.Addr + + mockChannel.On("Send", mock.Anything).Return(nil) + mockChannel.On("Recv").Return(&prover.ProverMessage{ + Id: "test", + Response: &prover.ProverMessage_GetStatusResponse{ + GetStatusResponse: &prover.GetStatusResponse{ + Status: prover.GetStatusResponse_STATUS_IDLE, + ProverName: "testName", + ProverId: "testId", + }, + }, + }, nil).Times(1) + + p, err := prover.New(log.GetDefaultLogger(), &mockChannel, addr, types.Duration{Duration: time.Second * 5}) + require.NoError(t, err) + name := p.Name() + require.Equal(t, "testName", name, "name does not match") + address := p.Addr() + require.Equal(t, "", address, "address does not match") + id := p.ID() + require.Equal(t, "testId", id, "id does not match") + + mockChannel.On("Recv").Return(&prover.ProverMessage{ + Id: "test", + Response: &prover.ProverMessage_GetProofResponse{ + GetProofResponse: &prover.GetProofResponse{ + Proof: &prover.GetProofResponse_RecursiveProof{ + RecursiveProof: "this is a proof", + }, + Result: prover.GetProofResponse_RESULT_COMPLETED_OK, + }, + }, + }, nil) + + proof, sr, accinputHash, err := p.WaitRecursiveProof(context.Background(), "proofID") + require.NoError(t, err) + + require.NotNil(t, proof, "proof is nil") + require.NotNil(t, sr, "state root is nil") + require.Equal(t, common.Hash{}, accinputHash, "state root is not empty") +} func TestCalculateStateRoots(t *testing.T) { var expectedStateRoots = map[string]string{ "1871.json": "0x0ed594d8bc0bb38f3190ff25fb1e5b4fe1baf0e2e0c1d7bf3307f07a55d3a60f", @@ -40,13 +91,18 @@ func TestCalculateStateRoots(t *testing.T) { require.NoError(t, err) // Get the state root from the batch proof - fileStateRoot, err := prover.GetStateRootFromProof(log.GetDefaultLogger(), string(data)) + fileStateRoot, err := prover.GetSanityCheckHashFromProof(log.GetDefaultLogger(), string(data), prover.StateRootStartIndex, prover.StateRootFinalIndex) require.NoError(t, err) // Get the expected state root expectedStateRoot, ok := expectedStateRoots[file.Name()] require.True(t, ok, "Expected state root not found") + // Check Acc Input Hash + accInputHash, err := prover.GetSanityCheckHashFromProof(log.GetDefaultLogger(), string(data), prover.AccInputHashStartIndex, prover.AccInputHashFinalIndex) + require.NotEqual(t, common.Hash{}, accInputHash, "Acc Input Hash is empty") + require.NoError(t, err) + // Compare the state roots require.Equal(t, expectedStateRoot, fileStateRoot.String(), "State roots do not match") } diff --git a/go.mod b/go.mod index c42bb806..70ec5e69 100644 --- a/go.mod +++ b/go.mod @@ -7,7 +7,7 @@ require ( github.com/0xPolygon/cdk-data-availability v0.0.10 github.com/0xPolygon/cdk-rpc v0.0.0-20241004114257-6c3cb6eebfb6 github.com/0xPolygon/zkevm-ethtx-manager v0.2.1 - github.com/0xPolygonHermez/zkevm-synchronizer-l1 v1.0.5 + github.com/0xPolygonHermez/zkevm-synchronizer-l1 v1.0.6 github.com/ethereum/go-ethereum v1.14.8 github.com/golang-collections/collections v0.0.0-20130729185459-604e922904d3 github.com/hermeznetwork/tracerr v0.3.2 diff --git a/go.sum b/go.sum index d6cbdb2b..ccf812c4 100644 --- a/go.sum +++ b/go.sum @@ -6,8 +6,8 @@ github.com/0xPolygon/cdk-rpc v0.0.0-20241004114257-6c3cb6eebfb6 h1:FXL/rcO7/GtZ3 github.com/0xPolygon/cdk-rpc v0.0.0-20241004114257-6c3cb6eebfb6/go.mod h1:2scWqMMufrQXu7TikDgQ3BsyaKoX8qP26D6E262vSOg= github.com/0xPolygon/zkevm-ethtx-manager v0.2.1 h1:2Yb+KdJFMpVrS9LIkd658XiWuN+MCTs7SgeWaopXScg= github.com/0xPolygon/zkevm-ethtx-manager v0.2.1/go.mod h1:lqQmzSo2OXEZItD0R4Cd+lqKFxphXEWgqHefVcGDZZc= -github.com/0xPolygonHermez/zkevm-synchronizer-l1 v1.0.5 h1:YmnhuCl349MoNASN0fMeGKU1o9HqJhiZkfMsA/1cTRA= -github.com/0xPolygonHermez/zkevm-synchronizer-l1 v1.0.5/go.mod h1:X4Su/M/+hSISqdl9yomKlRsbTyuZHsRohporyHsP8gg= +github.com/0xPolygonHermez/zkevm-synchronizer-l1 v1.0.6 h1:+XsCHXvQezRdMnkI37Wa/nV4sOZshJavxNzRpH/R6dw= +github.com/0xPolygonHermez/zkevm-synchronizer-l1 v1.0.6/go.mod h1:X4Su/M/+hSISqdl9yomKlRsbTyuZHsRohporyHsP8gg= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/DataDog/zstd v1.5.6 h1:LbEglqepa/ipmmQJUDnSsfvA8e8IStVcGaFWDuxvGOY= github.com/DataDog/zstd v1.5.6/go.mod h1:g4AWEaM3yOg3HYfnJ3YIawPnVdXJh9QME85blwSAmyw= diff --git a/test/Makefile b/test/Makefile index 12f406fd..2e3445db 100644 --- a/test/Makefile +++ b/test/Makefile @@ -59,7 +59,8 @@ generate-mocks-aggregator: ## Generates mocks for aggregator, using mockery tool export "GOROOT=$$(go env GOROOT)" && $$(go env GOPATH)/bin/mockery --name=EthTxManagerClient --dir=../aggregator --output=../aggregator/mocks --outpkg=mocks --structname=EthTxManagerClientMock --filename=mock_eth_tx_manager.go export "GOROOT=$$(go env GOROOT)" && $$(go env GOPATH)/bin/mockery --name=Tx --srcpkg=github.com/jackc/pgx/v4 --output=../aggregator/mocks --outpkg=mocks --structname=DbTxMock --filename=mock_dbtx.go export "GOROOT=$$(go env GOROOT)" && $$(go env GOPATH)/bin/mockery --name=RPCInterface --dir=../aggregator --output=../aggregator/mocks --outpkg=mocks --structname=RPCInterfaceMock --filename=mock_rpc.go - + export "GOROOT=$$(go env GOROOT)" && $$(go env GOPATH)/bin/mockery --name=AggregatorService_ChannelServer --dir=../aggregator/prover --output=../aggregator/prover/mocks --outpkg=mocks --structname=ChannelMock --filename=mock_channel.go + .PHONY: generate-mocks-aggsender generate-mocks-aggsender: ## Generates mocks for aggsender, using mockery tool From df57e65c9ac878b6735f284459f3c57f1bc8e4f5 Mon Sep 17 00:00:00 2001 From: Joan Esteban <129153821+joanestebanr@users.noreply.github.com> Date: Tue, 19 Nov 2024 15:40:01 +0100 Subject: [PATCH 10/23] feat: aggsender e2e (#183) - Split FEP and PP e2e tests - Add PP that check on aggsender database if there are 1 settle certificate - Add sqlite client to docker --- .github/workflows/test-e2e.yml | 3 +- Dockerfile | 2 +- aggsender/types/epoch_notifier.go | 3 - test/Makefile | 13 +++- test/{ => bats/fep}/access-list-e2e.bats | 4 +- test/{ => bats/fep}/basic-e2e.bats | 4 +- test/{ => bats/fep}/bridge-e2e.bats | 6 +- test/{ => bats/fep}/e2e.bats | 4 +- test/bats/pp/bridge-e2e.bats | 76 +++++++++++++++++++ test/bats/pp/e2e-pp.bats | 10 +++ test/combinations/fork12-pessimistic.yml | 14 ++++ test/scripts/agglayer_certificates_monitor.sh | 70 +++++++++++++++++ 12 files changed, 191 insertions(+), 18 deletions(-) rename test/{ => bats/fep}/access-list-e2e.bats (98%) rename test/{ => bats/fep}/basic-e2e.bats (99%) rename test/{ => bats/fep}/bridge-e2e.bats (98%) rename test/{ => bats/fep}/e2e.bats (56%) create mode 100644 test/bats/pp/bridge-e2e.bats create mode 100644 test/bats/pp/e2e-pp.bats create mode 100644 test/combinations/fork12-pessimistic.yml create mode 100755 test/scripts/agglayer_certificates_monitor.sh diff --git a/.github/workflows/test-e2e.yml b/.github/workflows/test-e2e.yml index 8994d8e6..abde0c6b 100644 --- a/.github/workflows/test-e2e.yml +++ b/.github/workflows/test-e2e.yml @@ -18,6 +18,7 @@ jobs: - "fork11-rollup" - "fork12-validium" - "fork12-rollup" + - "fork12-pessimistic" runs-on: ubuntu-latest steps: - name: Checkout code @@ -70,7 +71,7 @@ jobs: with: repository: 0xPolygon/kurtosis-cdk path: "kurtosis-cdk" - ref: "v0.2.19" + ref: "v0.2.21" - name: Setup Bats and bats libs uses: bats-core/bats-action@2.0.0 diff --git a/Dockerfile b/Dockerfile index ac5e759b..22e36595 100644 --- a/Dockerfile +++ b/Dockerfile @@ -42,7 +42,7 @@ RUN cargo build --release --bin cdk # CONTAINER FOR RUNNING BINARY FROM --platform=${BUILDPLATFORM} debian:bookworm-slim -RUN apt-get update && apt-get install -y ca-certificates postgresql-client libssl-dev && rm -rf /var/lib/apt/lists/* +RUN apt-get update && apt-get install -y ca-certificates sqlite3 procps libssl-dev && rm -rf /var/lib/apt/lists/* COPY --from=builder /app/target/release/cdk /usr/local/bin/ COPY --from=build /go/src/github.com/0xPolygon/cdk/target/cdk-node /usr/local/bin/ diff --git a/aggsender/types/epoch_notifier.go b/aggsender/types/epoch_notifier.go index 045ba7ff..426ad362 100644 --- a/aggsender/types/epoch_notifier.go +++ b/aggsender/types/epoch_notifier.go @@ -23,6 +23,3 @@ type EpochNotifier interface { Start(ctx context.Context) String() string } - -type BridgeL2Syncer interface { -} diff --git a/test/Makefile b/test/Makefile index 2e3445db..49c22f95 100644 --- a/test/Makefile +++ b/test/Makefile @@ -80,22 +80,27 @@ generate-mocks-bridgesync: ## Generates mocks for bridgesync, using mockery tool .PHONY: test-e2e-fork9-validium test-e2e-fork9-validium: stop ./run-e2e.sh fork9 cdk-validium - bats . + bats bats/fep/ .PHONY: test-e2e-fork11-rollup test-e2e-fork11-rollup: stop ./run-e2e.sh fork11 rollup - bats . + bats bats/fep/ .PHONY: test-e2e-fork12-validium test-e2e-fork12-validium: stop ./run-e2e.sh fork12 cdk-validium - bats . + bats bats/fep/ .PHONY: test-e2e-fork12-rollup test-e2e-fork12-rollup: stop ./run-e2e.sh fork12 rollup - bats . + bats bats/fep/ + +.PHONY: test-e2e-fork12-pessimistic +test-e2e-fork12-pessimistic: stop + ./run-e2e.sh fork12 pessimistic + bats bats/pp/ .PHONY: stop stop: diff --git a/test/access-list-e2e.bats b/test/bats/fep/access-list-e2e.bats similarity index 98% rename from test/access-list-e2e.bats rename to test/bats/fep/access-list-e2e.bats index 83947c03..b1e07f78 100644 --- a/test/access-list-e2e.bats +++ b/test/bats/fep/access-list-e2e.bats @@ -1,6 +1,6 @@ setup() { - load 'helpers/common-setup' - load 'helpers/common' + load '../../helpers/common-setup' + load '../../helpers/common' _common_setup readonly erigon_sequencer_node=${KURTOSIS_ERIGON_SEQUENCER:-cdk-erigon-sequencer-001} diff --git a/test/basic-e2e.bats b/test/bats/fep/basic-e2e.bats similarity index 99% rename from test/basic-e2e.bats rename to test/bats/fep/basic-e2e.bats index 1024ac4a..088ee239 100644 --- a/test/basic-e2e.bats +++ b/test/bats/fep/basic-e2e.bats @@ -1,6 +1,6 @@ setup() { - load 'helpers/common-setup' - load 'helpers/common' + load '../../helpers/common-setup' + load '../../helpers/common' _common_setup readonly sender_private_key=${SENDER_PRIVATE_KEY:-"12d7de8621a77640c9241b2595ba78ce443d05e94090365ab3bb5e19df82c625"} diff --git a/test/bridge-e2e.bats b/test/bats/fep/bridge-e2e.bats similarity index 98% rename from test/bridge-e2e.bats rename to test/bats/fep/bridge-e2e.bats index e754ef70..2514ee5f 100644 --- a/test/bridge-e2e.bats +++ b/test/bats/fep/bridge-e2e.bats @@ -1,8 +1,8 @@ setup() { - load 'helpers/common-setup' + load '../../helpers/common-setup' _common_setup - load 'helpers/common' - load 'helpers/lxly-bridge-test' + load '../../helpers/common' + load '../../helpers/lxly-bridge-test' if [ -z "$BRIDGE_ADDRESS" ]; then local combined_json_file="/opt/zkevm/combined.json" diff --git a/test/e2e.bats b/test/bats/fep/e2e.bats similarity index 56% rename from test/e2e.bats rename to test/bats/fep/e2e.bats index c85e33ce..2b3206ba 100644 --- a/test/e2e.bats +++ b/test/bats/fep/e2e.bats @@ -1,10 +1,10 @@ setup() { - load 'helpers/common-setup' + load '../../helpers/common-setup' _common_setup } @test "Verify batches" { echo "Waiting 10 minutes to get some verified batch...." - run $PROJECT_ROOT/test/scripts/batch_verification_monitor.sh 0 600 + run $PROJECT_ROOT/../scripts/batch_verification_monitor.sh 0 600 assert_success } diff --git a/test/bats/pp/bridge-e2e.bats b/test/bats/pp/bridge-e2e.bats new file mode 100644 index 00000000..953082a9 --- /dev/null +++ b/test/bats/pp/bridge-e2e.bats @@ -0,0 +1,76 @@ +setup() { + load '../../helpers/common-setup' + _common_setup + load '../../helpers/common' + load '../../helpers/lxly-bridge-test' + + if [ -z "$BRIDGE_ADDRESS" ]; then + local combined_json_file="/opt/zkevm/combined.json" + echo "BRIDGE_ADDRESS env variable is not provided, resolving the bridge address from the Kurtosis CDK '$combined_json_file'" >&3 + + # Fetching the combined JSON output and filtering to get polygonZkEVMBridgeAddress + combined_json_output=$($contracts_service_wrapper "cat $combined_json_file" | tail -n +2) + bridge_default_address=$(echo "$combined_json_output" | jq -r .polygonZkEVMBridgeAddress) + BRIDGE_ADDRESS=$bridge_default_address + fi + echo "Bridge address=$BRIDGE_ADDRESS" >&3 + + readonly sender_private_key=${SENDER_PRIVATE_KEY:-"12d7de8621a77640c9241b2595ba78ce443d05e94090365ab3bb5e19df82c625"} + readonly sender_addr="$(cast wallet address --private-key $sender_private_key)" + destination_net=${DESTINATION_NET:-"1"} + destination_addr=${DESTINATION_ADDRESS:-"0x0bb7AA0b4FdC2D2862c088424260e99ed6299148"} + ether_value=${ETHER_VALUE:-"0.0200000054"} + amount=$(cast to-wei $ether_value ether) + readonly native_token_addr=${NATIVE_TOKEN_ADDRESS:-"0x0000000000000000000000000000000000000000"} + if [[ -n "$GAS_TOKEN_ADDR" ]]; then + echo "Using provided GAS_TOKEN_ADDR: $GAS_TOKEN_ADDR" >&3 + gas_token_addr="$GAS_TOKEN_ADDR" + else + echo "GAS_TOKEN_ADDR not provided, retrieving from rollup parameters file." >&3 + readonly rollup_params_file=/opt/zkevm/create_rollup_parameters.json + run bash -c "$contracts_service_wrapper 'cat $rollup_params_file' | tail -n +2 | jq -r '.gasTokenAddress'" + assert_success + assert_output --regexp "0x[a-fA-F0-9]{40}" + gas_token_addr=$output + fi + readonly is_forced=${IS_FORCED:-"true"} + readonly bridge_addr=$BRIDGE_ADDRESS + readonly meta_bytes=${META_BYTES:-"0x"} + + readonly l1_rpc_url=${L1_ETH_RPC_URL:-"$(kurtosis port print $enclave el-1-geth-lighthouse rpc)"} + readonly bridge_api_url=${BRIDGE_API_URL:-"$(kurtosis port print $enclave zkevm-bridge-service-001 rpc)"} + + readonly dry_run=${DRY_RUN:-"false"} + readonly l1_rpc_network_id=$(cast call --rpc-url $l1_rpc_url $bridge_addr 'networkID() (uint32)') + readonly l2_rpc_network_id=$(cast call --rpc-url $l2_rpc_url $bridge_addr 'networkID() (uint32)') + gas_price=$(cast gas-price --rpc-url "$l2_rpc_url") + readonly weth_token_addr=$(cast call --rpc-url $l2_rpc_url $bridge_addr 'WETHToken()' | cast parse-bytes32-address) +} + +@test "Native gas token deposit to WETH" { + destination_addr=$sender_addr + local initial_receiver_balance=$(cast call --rpc-url "$l2_rpc_url" "$weth_token_addr" "$balance_of_fn_sig" "$destination_addr" | awk '{print $1}') + echo "Initial receiver balance of native token on L2 $initial_receiver_balance" >&3 + + echo "=== Running LxLy deposit on L1 to network: $l2_rpc_network_id native_token: $native_token_addr" >&3 + + destination_net=$l2_rpc_network_id + run bridgeAsset "$native_token_addr" "$l1_rpc_url" + assert_success + + echo "=== Running LxLy claim on L2" >&3 + timeout="120" + claim_frequency="10" + run wait_for_claim "$timeout" "$claim_frequency" "$l2_rpc_url" + assert_success + + run verify_balance "$l2_rpc_url" "$weth_token_addr" "$destination_addr" "$initial_receiver_balance" "$ether_value" + assert_success + + echo "=== bridgeAsset L2 WETH: $weth_token_addr to L1 ETH" >&3 + destination_addr=$sender_addr + destination_net=0 + run bridgeAsset "$weth_token_addr" "$l2_rpc_url" + assert_success +} + diff --git a/test/bats/pp/e2e-pp.bats b/test/bats/pp/e2e-pp.bats new file mode 100644 index 00000000..77d7910d --- /dev/null +++ b/test/bats/pp/e2e-pp.bats @@ -0,0 +1,10 @@ +setup() { + load '../../helpers/common-setup' + _common_setup +} + +@test "Verify batches" { + echo "Waiting 10 minutes to get some settle certificate...." + run $PROJECT_ROOT/../scripts/agglayer_certificates_monitor.sh 1 600 + assert_success +} diff --git a/test/combinations/fork12-pessimistic.yml b/test/combinations/fork12-pessimistic.yml new file mode 100644 index 00000000..088822c5 --- /dev/null +++ b/test/combinations/fork12-pessimistic.yml @@ -0,0 +1,14 @@ +args: + agglayer_image: ghcr.io/agglayer/agglayer:0.2.0-rc.11 + cdk_erigon_node_image: hermeznetwork/cdk-erigon:v2.60.0-beta8 + cdk_node_image: cdk + zkevm_bridge_proxy_image: haproxy:3.0-bookworm + zkevm_bridge_service_image: hermeznetwork/zkevm-bridge-service:v0.6.0-RC1 + zkevm_bridge_ui_image: leovct/zkevm-bridge-ui:multi-network + zkevm_contracts_image: nulyjkdhthz/zkevm-contracts:v9.0.0-rc.3-pp-fork.12 + additional_services: [] + consensus_contract_type: pessimistic + sequencer_type: erigon + erigon_strict_mode: false + zkevm_use_gas_token_contract: true + enable_normalcy: true \ No newline at end of file diff --git a/test/scripts/agglayer_certificates_monitor.sh b/test/scripts/agglayer_certificates_monitor.sh new file mode 100755 index 00000000..47c116b6 --- /dev/null +++ b/test/scripts/agglayer_certificates_monitor.sh @@ -0,0 +1,70 @@ +#!/usr/bin/env bash +# This script monitors the agglayer certificates progress of pessimistic proof. + +function parse_params(){ + # Check if the required arguments are provided. + if [ "$#" -lt 2 ]; then + echo "Usage: $0 " + exit 1 + fi + + # The number of batches to be verified. + settle_certificates_target="$1" + + # The script timeout (in seconds). + timeout="$2" +} + +function check_timeout(){ + local _end_time=$1 + current_time=$(date +%s) + if ((current_time > _end_time)); then + echo "[$(date '+%Y-%m-%d %H:%M:%S')] ❌ Exiting... Timeout reached not found the expected numbers of settled certs!" + exit 1 + fi +} + +function check_num_certificates(){ + local _cmd="echo 'select status, count(*) from certificate_info group by status;' | sqlite3 /tmp/aggsender.sqlite" + local _outcmd=$(mktemp) + kurtosis service exec cdk cdk-node-001 "$_cmd" > $_outcmd + if [ $? -ne 0 ]; then + echo "[$(date '+%Y-%m-%d %H:%M:%S')] Error executing command kurtosis service: $_cmd" + # clean temp file + rm $_outcmd + return + fi + local num_certs=$(cat $_outcmd | tail -n +2 | cut -f 2 -d '|' | xargs |tr ' ' '+' | bc) + # Get the number of settled certificates "4|0" + local _num_settle_certs=$(cat $_outcmd | tail -n +2 | grep ^${aggsender_status_settled} | cut -d'|' -f2) + [ -z "$_num_settle_certs" ] && _num_settle_certs=0 + # clean temp file + rm $_outcmd + echo "[$(date '+%Y-%m-%d %H:%M:%S')] Num certificates on aggsender: $num_certs. Settled certificates : $_num_settle_certs" + if [ $num_certs -ge $settle_certificates_target ]; then + echo "[$(date '+%Y-%m-%d %H:%M:%S')] ✅ Exiting... $num_certs certificates were settled! (total certs $num_certs)" + exit 0 + fi +} + +# MAIN +declare -A aggsender_status_map=( + [0]="Pending" + [1]="Proven" + [2]="Candidate" + [3]="InError" + [4]="Settled" +) + +readonly aggsender_status_settled=4 + + +parse_params $* +start_time=$(date +%s) +end_time=$((start_time + timeout)) +echo "[$(date '+%Y-%m-%d %H:%M:%S')] Start monitoring agglayer certificates progress..." +while true; do + check_num_certificates + check_timeout $end_time + sleep 10 +done From d2cf7cc5e4e92091cf4735d3b700ec27337bf4f4 Mon Sep 17 00:00:00 2001 From: Goran Rojovic <100121253+goran-ethernal@users.noreply.github.com> Date: Thu, 21 Nov 2024 14:29:24 +0100 Subject: [PATCH 11/23] feat: BridgeMessage e2e test (#184) * feat: e2e * fix: refactor * fix: comments * fix: comments --- test/bats/fep/bridge-e2e.bats | 49 ++++++++++++++++++++++-------- test/helpers/lxly-bridge-test.bash | 48 +++++++++++++++++++++++++---- 2 files changed, 78 insertions(+), 19 deletions(-) diff --git a/test/bats/fep/bridge-e2e.bats b/test/bats/fep/bridge-e2e.bats index 2514ee5f..35ca9382 100644 --- a/test/bats/fep/bridge-e2e.bats +++ b/test/bats/fep/bridge-e2e.bats @@ -47,39 +47,62 @@ setup() { readonly weth_token_addr=$(cast call --rpc-url $l2_rpc_url $bridge_addr 'WETHToken()' | cast parse-bytes32-address) } -@test "Native gas token deposit to WETH" { +# Helper function to run native gas token deposit to WETH +native_gas_token_deposit_to_WETH() { + local bridge_type="$1" + + echo "Bridge_type: $bridge_type" >&3 + destination_addr=$sender_addr local initial_receiver_balance=$(cast call --rpc-url "$l2_rpc_url" "$weth_token_addr" "$balance_of_fn_sig" "$destination_addr" | awk '{print $1}') echo "Initial receiver balance of native token on L2 $initial_receiver_balance" >&3 - echo "=== Running LxLy deposit on L1 to network: $l2_rpc_network_id native_token: $native_token_addr" >&3 + echo "=== Running LxLy deposit $bridge_type on L1 to network: $l2_rpc_network_id native_token: $native_token_addr" >&3 destination_net=$l2_rpc_network_id - run bridgeAsset "$native_token_addr" "$l1_rpc_url" + + if [[ $bridge_type == "bridgeMessage" ]]; then + run bridge_message "$native_token_addr" "$l1_rpc_url" + else + run bridge_asset "$native_token_addr" "$l1_rpc_url" + fi assert_success - echo "=== Running LxLy claim on L2" >&3 + echo "=== Claiming on L2..." >&3 timeout="120" claim_frequency="10" - run wait_for_claim "$timeout" "$claim_frequency" "$l2_rpc_url" + run wait_for_claim "$timeout" "$claim_frequency" "$l2_rpc_url" "$bridge_type" assert_success run verify_balance "$l2_rpc_url" "$weth_token_addr" "$destination_addr" "$initial_receiver_balance" "$ether_value" assert_success - echo "=== bridgeAsset L2 WETH: $weth_token_addr to L1 ETH" >&3 + echo "=== $bridge_type L2 WETH: $weth_token_addr to L1 ETH" >&3 destination_addr=$sender_addr destination_net=0 - run bridgeAsset "$weth_token_addr" "$l2_rpc_url" + + if [[ $bridge_type == "bridgeMessage" ]]; then + run bridge_message "$weth_token_addr" "$l2_rpc_url" + else + run bridge_asset "$weth_token_addr" "$l2_rpc_url" + fi assert_success - echo "=== Claim in L1 ETH" >&3 + echo "=== Claiming on L1..." >&3 timeout="400" claim_frequency="60" - run wait_for_claim "$timeout" "$claim_frequency" "$l1_rpc_url" + run wait_for_claim "$timeout" "$claim_frequency" "$l1_rpc_url" "$bridge_type" assert_success } +@test "Native gas token deposit to WETH - BridgeAsset" { + run native_gas_token_deposit_to_WETH "bridgeAsset" +} + +@test "Native gas token deposit to WETH - BridgeMessage" { + run native_gas_token_deposit_to_WETH "bridgeMessage" +} + @test "Custom gas token deposit" { echo "Gas token addr $gas_token_addr, L1 RPC: $l1_rpc_url" >&3 @@ -127,13 +150,13 @@ setup() { destination_addr=$receiver destination_net=$l2_rpc_network_id amount=$wei_amount - run bridgeAsset "$gas_token_addr" "$l1_rpc_url" + run bridge_asset "$gas_token_addr" "$l1_rpc_url" assert_success # Claim deposits (settle them on the L2) timeout="120" claim_frequency="10" - run wait_for_claim "$timeout" "$claim_frequency" "$l2_rpc_url" + run wait_for_claim "$timeout" "$claim_frequency" "$l2_rpc_url" "bridgeAsset" assert_success # Validate that the native token of receiver on L2 has increased by the bridge tokens amount @@ -150,14 +173,14 @@ setup() { echo "Receiver balance of gas token on L1 $initial_receiver_balance" >&3 destination_net=$l1_rpc_network_id - run bridgeAsset "$native_token_addr" "$l2_rpc_url" + run bridge_asset "$native_token_addr" "$l2_rpc_url" assert_success # Claim withdrawals (settle them on the L1) timeout="360" claim_frequency="10" destination_net=$l1_rpc_network_id - run wait_for_claim "$timeout" "$claim_frequency" "$l1_rpc_url" + run wait_for_claim "$timeout" "$claim_frequency" "$l1_rpc_url" "bridgeAsset" assert_success # Validate that the token of receiver on L1 has increased by the bridge tokens amount diff --git a/test/helpers/lxly-bridge-test.bash b/test/helpers/lxly-bridge-test.bash index ad5ab943..53af437e 100644 --- a/test/helpers/lxly-bridge-test.bash +++ b/test/helpers/lxly-bridge-test.bash @@ -1,9 +1,9 @@ #!/usr/bin/env bash # Error code reference https://hackmd.io/WwahVBZERJKdfK3BbKxzQQ -function bridgeAsset() { +function bridge_message() { local token_addr="$1" local rpc_url="$2" - readonly bridge_sig='bridgeAsset(uint32,address,uint256,address,bool,bytes)' + local bridge_sig='bridgeMessage(uint32,address,bool,bytes)' if [[ $token_addr == "0x0000000000000000000000000000000000000000" ]]; then echo "The ETH balance for sender "$sender_addr":" >&3 @@ -15,7 +15,37 @@ function bridgeAsset() { echo "$(cast --from-wei "$balance_wei")" >&3 fi - echo "Attempting to deposit $amount [wei] to $destination_addr, token $token_addr (sender=$sender_addr, network id=$destination_net, rpc url=$rpc_url)" >&3 + echo "Attempting to deposit $amount [wei] using bridgeMessage to $destination_addr, token $token_addr (sender=$sender_addr, network id=$destination_net, rpc url=$rpc_url)" >&3 + + if [[ $dry_run == "true" ]]; then + cast calldata $bridge_sig $destination_net $destination_addr $amount $token_addr $is_forced $meta_bytes + else + if [[ $token_addr == "0x0000000000000000000000000000000000000000" ]]; then + echo "cast send --legacy --private-key $sender_private_key --value $amount --rpc-url $rpc_url $bridge_addr $bridge_sig $destination_net $destination_addr $is_forced $meta_bytes" >&3 + cast send --legacy --private-key $sender_private_key --value $amount --rpc-url $rpc_url $bridge_addr $bridge_sig $destination_net $destination_addr $is_forced $meta_bytes + else + echo "cast send --legacy --private-key $sender_private_key --rpc-url $rpc_url $bridge_addr $bridge_sig $destination_net $destination_addr $is_forced $meta_bytes" + cast send --legacy --private-key $sender_private_key --rpc-url $rpc_url $bridge_addr $bridge_sig $destination_net $destination_addr $is_forced $meta_bytes + fi + fi +} + +function bridge_asset() { + local token_addr="$1" + local rpc_url="$2" + local bridge_sig='bridgeAsset(uint32,address,uint256,address,bool,bytes)' + + if [[ $token_addr == "0x0000000000000000000000000000000000000000" ]]; then + echo "The ETH balance for sender "$sender_addr":" >&3 + cast balance -e --rpc-url $rpc_url $sender_addr >&3 + else + echo "The "$token_addr" token balance for sender "$sender_addr":" >&3 + echo "cast call --rpc-url $rpc_url $token_addr \"$balance_of_fn_sig\" $sender_addr" >&3 + balance_wei=$(cast call --rpc-url "$rpc_url" "$token_addr" "$balance_of_fn_sig" "$sender_addr" | awk '{print $1}') + echo "$(cast --from-wei "$balance_wei")" >&3 + fi + + echo "Attempting to deposit $amount [wei] using bridgeAsset to $destination_addr, token $token_addr (sender=$sender_addr, network id=$destination_net, rpc url=$rpc_url)" >&3 if [[ $dry_run == "true" ]]; then cast calldata $bridge_sig $destination_net $destination_addr $amount $token_addr $is_forced $meta_bytes @@ -24,7 +54,7 @@ function bridgeAsset() { echo "cast send --legacy --private-key $sender_private_key --value $amount --rpc-url $rpc_url $bridge_addr $bridge_sig $destination_net $destination_addr $amount $token_addr $is_forced $meta_bytes" >&3 cast send --legacy --private-key $sender_private_key --value $amount --rpc-url $rpc_url $bridge_addr $bridge_sig $destination_net $destination_addr $amount $token_addr $is_forced $meta_bytes else - echo "cast send --legacy --private-key $sender_private_key --rpc-url $rpc_url $bridge_addr \"$bridge_sig\" $destination_net $destination_addr $amount $token_addr $is_forced $meta_bytes" + echo "cast send --legacy --private-key $sender_private_key --rpc-url $rpc_url $bridge_addr $bridge_sig $destination_net $destination_addr $amount $token_addr $is_forced $meta_bytes" cast send --legacy --private-key $sender_private_key --rpc-url $rpc_url $bridge_addr $bridge_sig $destination_net $destination_addr $amount $token_addr $is_forced $meta_bytes fi fi @@ -32,7 +62,12 @@ function bridgeAsset() { function claim() { local destination_rpc_url="$1" - readonly claim_sig="claimAsset(bytes32[32],bytes32[32],uint256,bytes32,bytes32,uint32,address,uint32,address,uint256,bytes)" + local bridge_type="$2" + local claim_sig="claimAsset(bytes32[32],bytes32[32],uint256,bytes32,bytes32,uint32,address,uint32,address,uint256,bytes)" + if [[ $bridge_type == "bridgeMessage" ]]; then + claim_sig="claimMessage(bytes32[32],bytes32[32],uint256,bytes32,bytes32,uint32,address,uint32,address,uint256,bytes)" + fi + readonly bridge_deposit_file=$(mktemp) readonly claimable_deposit_file=$(mktemp) echo "Getting full list of deposits" >&3 @@ -94,6 +129,7 @@ function wait_for_claim() { local timeout="$1" # timeout (in seconds) local claim_frequency="$2" # claim frequency (in seconds) local destination_rpc_url="$3" # destination rpc url + local bridge_type="$4" # bridgeAsset or bridgeMessage local start_time=$(date +%s) local end_time=$((start_time + timeout)) @@ -104,7 +140,7 @@ function wait_for_claim() { exit 1 fi - run claim $destination_rpc_url + run claim $destination_rpc_url $bridge_type if [ $status -eq 0 ]; then break fi From 46a67897a8c92bb7782b23cacb13714f5902594d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Toni=20Ram=C3=ADrez?= <58293609+ToniRamirezM@users.noreply.github.com> Date: Fri, 22 Nov 2024 13:56:41 +0100 Subject: [PATCH 12/23] feat: sqlite aggregator (#189) * feat: sqlite aggregator --- aggregator/aggregator.go | 138 +++---- aggregator/aggregator_test.go | 367 +++++++++--------- aggregator/config.go | 13 +- aggregator/db/config.go | 25 -- aggregator/db/db.go | 31 -- aggregator/db/dbstorage/dbstorage.go | 35 ++ aggregator/db/dbstorage/proof.go | 356 +++++++++++++++++ aggregator/db/dbstorage/proof_test.go | 150 +++++++ aggregator/db/dbstorage/sequence.go | 21 + aggregator/db/migrations.go | 37 +- aggregator/db/migrations/0001.sql | 34 +- aggregator/db/migrations/0002.sql | 8 - aggregator/db/migrations/0003.sql | 7 - aggregator/db/migrations/0004.sql | 23 -- aggregator/db/migrations_test.go | 9 + aggregator/interfaces.go | 29 +- aggregator/mocks/mock_dbtx.go | 350 ----------------- .../mocks/{mock_state.go => mock_storage.go} | 106 ++--- aggregator/mocks/mock_txer.go | 163 ++++++++ aggregator/profitabilitychecker.go | 92 ----- cmd/run.go | 41 +- config/default.go | 9 +- scripts/local_config | 3 - state/config.go | 13 - state/interfaces.go | 26 -- state/pgstatestorage/interfaces.go | 14 - state/pgstatestorage/pgstatestorage.go | 29 -- state/pgstatestorage/proof.go | 266 ------------- state/pgstatestorage/sequence.go | 21 - state/state.go | 40 -- test/Makefile | 4 +- .../kurtosis-cdk-node-config.toml.template | 8 - test/config/test.config.toml | 8 - 33 files changed, 1080 insertions(+), 1396 deletions(-) delete mode 100644 aggregator/db/config.go delete mode 100644 aggregator/db/db.go create mode 100644 aggregator/db/dbstorage/dbstorage.go create mode 100644 aggregator/db/dbstorage/proof.go create mode 100644 aggregator/db/dbstorage/proof_test.go create mode 100644 aggregator/db/dbstorage/sequence.go delete mode 100644 aggregator/db/migrations/0002.sql delete mode 100644 aggregator/db/migrations/0003.sql delete mode 100644 aggregator/db/migrations/0004.sql delete mode 100644 aggregator/mocks/mock_dbtx.go rename aggregator/mocks/{mock_state.go => mock_storage.go} (54%) create mode 100644 aggregator/mocks/mock_txer.go delete mode 100644 aggregator/profitabilitychecker.go delete mode 100644 state/config.go delete mode 100644 state/interfaces.go delete mode 100644 state/pgstatestorage/interfaces.go delete mode 100644 state/pgstatestorage/pgstatestorage.go delete mode 100644 state/pgstatestorage/proof.go delete mode 100644 state/pgstatestorage/sequence.go delete mode 100644 state/state.go diff --git a/aggregator/aggregator.go b/aggregator/aggregator.go index 0659180f..d9ecb1a2 100644 --- a/aggregator/aggregator.go +++ b/aggregator/aggregator.go @@ -17,6 +17,7 @@ import ( cdkTypes "github.com/0xPolygon/cdk-rpc/types" "github.com/0xPolygon/cdk/agglayer" + "github.com/0xPolygon/cdk/aggregator/db/dbstorage" ethmanTypes "github.com/0xPolygon/cdk/aggregator/ethmantypes" "github.com/0xPolygon/cdk/aggregator/prover" cdkcommon "github.com/0xPolygon/cdk/common" @@ -59,7 +60,7 @@ type Aggregator struct { cfg Config logger *log.Logger - state StateInterface + storage StorageInterface etherman Etherman ethTxManager EthTxManagerClient l1Syncr synchronizer.Synchronizer @@ -67,10 +68,9 @@ type Aggregator struct { accInputHashes map[uint64]common.Hash accInputHashesMutex *sync.Mutex - profitabilityChecker aggregatorTxProfitabilityChecker timeSendFinalProof time.Time timeCleanupLockedProofs types.Duration - stateDBMutex *sync.Mutex + storageMutex *sync.Mutex timeSendFinalProofMutex *sync.RWMutex finalProof chan finalProofMsg @@ -93,21 +93,7 @@ func New( ctx context.Context, cfg Config, logger *log.Logger, - stateInterface StateInterface, etherman Etherman) (*Aggregator, error) { - var profitabilityChecker aggregatorTxProfitabilityChecker - - switch cfg.TxProfitabilityCheckerType { - case ProfitabilityBase: - profitabilityChecker = NewTxProfitabilityCheckerBase( - stateInterface, cfg.IntervalAfterWhichBatchConsolidateAnyway.Duration, cfg.TxProfitabilityMinReward.Int, - ) - case ProfitabilityAcceptAll: - profitabilityChecker = NewTxProfitabilityCheckerAcceptAll( - stateInterface, cfg.IntervalAfterWhichBatchConsolidateAnyway.Duration, - ) - } - // Create ethtxmanager client cfg.EthTxManager.Log = ethtxlog.Config{ Environment: ethtxlog.LogEnvironment(cfg.Log.Environment), @@ -150,18 +136,22 @@ func New( } } + storage, err := dbstorage.NewDBStorage(cfg.DBPath) + if err != nil { + return nil, err + } + a := &Aggregator{ ctx: ctx, cfg: cfg, logger: logger, - state: stateInterface, + storage: storage, etherman: etherman, ethTxManager: ethTxManager, l1Syncr: l1Syncr, accInputHashes: make(map[uint64]common.Hash), accInputHashesMutex: &sync.Mutex{}, - profitabilityChecker: profitabilityChecker, - stateDBMutex: &sync.Mutex{}, + storageMutex: &sync.Mutex{}, timeSendFinalProofMutex: &sync.RWMutex{}, timeCleanupLockedProofs: cfg.CleanupLockedProofsInterval, finalProof: make(chan finalProofMsg), @@ -213,7 +203,7 @@ func (a *Aggregator) handleReorg(reorgData synchronizer.ReorgExecutionResult) { a.logger.Errorf("Error getting last virtual batch number: %v", err) } else { // Delete wip proofs - err = a.state.DeleteUngeneratedProofs(a.ctx, nil) + err = a.storage.DeleteUngeneratedProofs(a.ctx, nil) if err != nil { a.logger.Errorf("Error deleting ungenerated proofs: %v", err) } else { @@ -221,7 +211,7 @@ func (a *Aggregator) handleReorg(reorgData synchronizer.ReorgExecutionResult) { } // Delete any proof for the batches that have been rolled back - err = a.state.DeleteGeneratedProofs(a.ctx, lastVBatchNumber+1, maxDBBigIntValue, nil) + err = a.storage.DeleteGeneratedProofs(a.ctx, lastVBatchNumber+1, maxDBBigIntValue, nil) if err != nil { a.logger.Errorf("Error deleting generated proofs: %v", err) } else { @@ -275,7 +265,7 @@ func (a *Aggregator) handleRollbackBatches(rollbackData synchronizer.RollbackBat // Delete wip proofs if err == nil { - err = a.state.DeleteUngeneratedProofs(a.ctx, nil) + err = a.storage.DeleteUngeneratedProofs(a.ctx, nil) if err != nil { a.logger.Errorf("Error deleting ungenerated proofs: %v", err) } else { @@ -285,7 +275,7 @@ func (a *Aggregator) handleRollbackBatches(rollbackData synchronizer.RollbackBat // Delete any proof for the batches that have been rolled back if err == nil { - err = a.state.DeleteGeneratedProofs(a.ctx, rollbackData.LastBatchNumber+1, maxDBBigIntValue, nil) + err = a.storage.DeleteGeneratedProofs(a.ctx, rollbackData.LastBatchNumber+1, maxDBBigIntValue, nil) if err != nil { a.logger.Errorf("Error deleting generated proofs: %v", err) } else { @@ -336,7 +326,7 @@ func (a *Aggregator) Start() error { grpchealth.RegisterHealthServer(a.srv, healthService) // Delete ungenerated recursive proofs - err = a.state.DeleteUngeneratedProofs(a.ctx, nil) + err = a.storage.DeleteUngeneratedProofs(a.ctx, nil) if err != nil { return fmt.Errorf("failed to initialize proofs cache %w", err) } @@ -608,7 +598,7 @@ func (a *Aggregator) handleFailureToAddVerifyBatchToBeMonitored(ctx context.Cont "batches", fmt.Sprintf("%d-%d", proof.BatchNumber, proof.BatchNumberFinal), ) proof.GeneratingSince = nil - err := a.state.UpdateGeneratedProof(ctx, proof, nil) + err := a.storage.UpdateGeneratedProof(ctx, proof, nil) if err != nil { tmpLogger.Errorf("Failed updating proof state (false): %v", err) } @@ -703,7 +693,7 @@ func (a *Aggregator) tryBuildFinalProof(ctx context.Context, prover ProverInterf if err != nil { // Set the generating state to false for the proof ("unlock" it) proof.GeneratingSince = nil - err2 := a.state.UpdateGeneratedProof(a.ctx, proof, nil) + err2 := a.storage.UpdateGeneratedProof(a.ctx, proof, nil) if err2 != nil { tmpLogger.Errorf("Failed to unlock proof: %v", err2) } @@ -766,7 +756,7 @@ func (a *Aggregator) validateEligibleFinalProof( // We have a proof that contains batches below that the last batch verified, we need to delete this proof a.logger.Warnf("Proof %d-%d lower than next batch to verify %d. Deleting it", proof.BatchNumber, proof.BatchNumberFinal, batchNumberToVerify) - err := a.state.DeleteGeneratedProofs(ctx, proof.BatchNumber, proof.BatchNumberFinal, nil) + err := a.storage.DeleteGeneratedProofs(ctx, proof.BatchNumber, proof.BatchNumberFinal, nil) if err != nil { return false, fmt.Errorf("failed to delete discarded proof, err: %w", err) } @@ -779,7 +769,7 @@ func (a *Aggregator) validateEligibleFinalProof( } } - bComplete, err := a.state.CheckProofContainsCompleteSequences(ctx, proof, nil) + bComplete, err := a.storage.CheckProofContainsCompleteSequences(ctx, proof, nil) if err != nil { return false, fmt.Errorf("failed to check if proof contains complete sequences, %w", err) } @@ -795,11 +785,11 @@ func (a *Aggregator) validateEligibleFinalProof( func (a *Aggregator) getAndLockProofReadyToVerify( ctx context.Context, lastVerifiedBatchNum uint64, ) (*state.Proof, error) { - a.stateDBMutex.Lock() - defer a.stateDBMutex.Unlock() + a.storageMutex.Lock() + defer a.storageMutex.Unlock() // Get proof ready to be verified - proofToVerify, err := a.state.GetProofReadyToVerify(ctx, lastVerifiedBatchNum, nil) + proofToVerify, err := a.storage.GetProofReadyToVerify(ctx, lastVerifiedBatchNum, nil) if err != nil { return nil, err } @@ -807,7 +797,7 @@ func (a *Aggregator) getAndLockProofReadyToVerify( now := time.Now().Round(time.Microsecond) proofToVerify.GeneratingSince = &now - err = a.state.UpdateGeneratedProof(ctx, proofToVerify, nil) + err = a.storage.UpdateGeneratedProof(ctx, proofToVerify, nil) if err != nil { return nil, err } @@ -817,21 +807,21 @@ func (a *Aggregator) getAndLockProofReadyToVerify( func (a *Aggregator) unlockProofsToAggregate(ctx context.Context, proof1 *state.Proof, proof2 *state.Proof) error { // Release proofs from generating state in a single transaction - dbTx, err := a.state.BeginStateTransaction(ctx) + dbTx, err := a.storage.BeginTx(ctx, nil) if err != nil { a.logger.Warnf("Failed to begin transaction to release proof aggregation state, err: %v", err) return err } proof1.GeneratingSince = nil - err = a.state.UpdateGeneratedProof(ctx, proof1, dbTx) + err = a.storage.UpdateGeneratedProof(ctx, proof1, dbTx) if err == nil { proof2.GeneratingSince = nil - err = a.state.UpdateGeneratedProof(ctx, proof2, dbTx) + err = a.storage.UpdateGeneratedProof(ctx, proof2, dbTx) } if err != nil { - if err := dbTx.Rollback(ctx); err != nil { + if err := dbTx.Rollback(); err != nil { err := fmt.Errorf("failed to rollback proof aggregation state: %w", err) a.logger.Error(FirstToUpper(err.Error())) return err @@ -840,7 +830,7 @@ func (a *Aggregator) unlockProofsToAggregate(ctx context.Context, proof1 *state. return fmt.Errorf("failed to release proof aggregation state: %w", err) } - err = dbTx.Commit(ctx) + err = dbTx.Commit() if err != nil { return fmt.Errorf("failed to release proof aggregation state %w", err) } @@ -856,16 +846,16 @@ func (a *Aggregator) getAndLockProofsToAggregate( "proverAddr", prover.Addr(), ) - a.stateDBMutex.Lock() - defer a.stateDBMutex.Unlock() + a.storageMutex.Lock() + defer a.storageMutex.Unlock() - proof1, proof2, err := a.state.GetProofsToAggregate(ctx, nil) + proof1, proof2, err := a.storage.GetProofsToAggregate(ctx, nil) if err != nil { return nil, nil, err } // Set proofs in generating state in a single transaction - dbTx, err := a.state.BeginStateTransaction(ctx) + dbTx, err := a.storage.BeginTx(ctx, nil) if err != nil { tmpLogger.Errorf("Failed to begin transaction to set proof aggregation state, err: %v", err) return nil, nil, err @@ -873,14 +863,14 @@ func (a *Aggregator) getAndLockProofsToAggregate( now := time.Now().Round(time.Microsecond) proof1.GeneratingSince = &now - err = a.state.UpdateGeneratedProof(ctx, proof1, dbTx) + err = a.storage.UpdateGeneratedProof(ctx, proof1, dbTx) if err == nil { proof2.GeneratingSince = &now - err = a.state.UpdateGeneratedProof(ctx, proof2, dbTx) + err = a.storage.UpdateGeneratedProof(ctx, proof2, dbTx) } if err != nil { - if err := dbTx.Rollback(ctx); err != nil { + if err := dbTx.Rollback(); err != nil { err := fmt.Errorf("failed to rollback proof aggregation state %w", err) tmpLogger.Error(FirstToUpper(err.Error())) return nil, nil, err @@ -889,7 +879,7 @@ func (a *Aggregator) getAndLockProofsToAggregate( return nil, nil, fmt.Errorf("failed to set proof aggregation state %w", err) } - err = dbTx.Commit(ctx) + err = dbTx.Commit() if err != nil { return nil, nil, fmt.Errorf("failed to set proof aggregation state %w", err) } @@ -983,16 +973,16 @@ func (a *Aggregator) tryAggregateProofs(ctx context.Context, prover ProverInterf // update the state by removing the 2 aggregated proofs and storing the // newly generated recursive proof - dbTx, err := a.state.BeginStateTransaction(ctx) + dbTx, err := a.storage.BeginTx(ctx, nil) if err != nil { err = fmt.Errorf("failed to begin transaction to update proof aggregation state, %w", err) tmpLogger.Error(FirstToUpper(err.Error())) return false, err } - err = a.state.DeleteGeneratedProofs(ctx, proof1.BatchNumber, proof2.BatchNumberFinal, dbTx) + err = a.storage.DeleteGeneratedProofs(ctx, proof1.BatchNumber, proof2.BatchNumberFinal, dbTx) if err != nil { - if err := dbTx.Rollback(ctx); err != nil { + if err := dbTx.Rollback(); err != nil { err := fmt.Errorf("failed to rollback proof aggregation state, %w", err) tmpLogger.Error(FirstToUpper(err.Error())) return false, err @@ -1005,9 +995,9 @@ func (a *Aggregator) tryAggregateProofs(ctx context.Context, prover ProverInterf now := time.Now().Round(time.Microsecond) proof.GeneratingSince = &now - err = a.state.AddGeneratedProof(ctx, proof, dbTx) + err = a.storage.AddGeneratedProof(ctx, proof, dbTx) if err != nil { - if err := dbTx.Rollback(ctx); err != nil { + if err := dbTx.Rollback(); err != nil { err := fmt.Errorf("failed to rollback proof aggregation state, %w", err) tmpLogger.Error(FirstToUpper(err.Error())) return false, err @@ -1017,7 +1007,7 @@ func (a *Aggregator) tryAggregateProofs(ctx context.Context, prover ProverInterf return false, err } - err = dbTx.Commit(ctx) + err = dbTx.Commit() if err != nil { err = fmt.Errorf("failed to store the recursive proof, %w", err) tmpLogger.Error(FirstToUpper(err.Error())) @@ -1041,7 +1031,7 @@ func (a *Aggregator) tryAggregateProofs(ctx context.Context, prover ProverInterf proof.GeneratingSince = nil // final proof has not been generated, update the recursive proof - err := a.state.UpdateGeneratedProof(a.ctx, proof, nil) + err := a.storage.UpdateGeneratedProof(a.ctx, proof, nil) if err != nil { err = fmt.Errorf("failed to store batch proof result, %w", err) tmpLogger.Error(FirstToUpper(err.Error())) @@ -1073,8 +1063,8 @@ func (a *Aggregator) getAndLockBatchToProve( "proverAddr", prover.Addr(), ) - a.stateDBMutex.Lock() - defer a.stateDBMutex.Unlock() + a.storageMutex.Lock() + defer a.storageMutex.Unlock() // Get last virtual batch number from L1 lastVerifiedBatchNumber, err := a.etherman.GetLatestVerifiedBatchNum() @@ -1088,7 +1078,7 @@ func (a *Aggregator) getAndLockBatchToProve( // Look for the batch number to verify for proofExists { batchNumberToVerify++ - proofExists, err = a.state.CheckProofExistsForBatch(ctx, batchNumberToVerify, nil) + proofExists, err = a.storage.CheckProofExistsForBatch(ctx, batchNumberToVerify, nil) if err != nil { tmpLogger.Infof("Error checking proof exists for batch %d", batchNumberToVerify) @@ -1101,7 +1091,7 @@ func (a *Aggregator) getAndLockBatchToProve( tmpLogger.Warnf("AccInputHash for batch %d is not in memory, "+ "deleting proofs to regenerate acc input hash chain in memory", batchNumberToVerify) - err := a.state.CleanupGeneratedProofs(ctx, math.MaxInt, nil) + err := a.storage.CleanupGeneratedProofs(ctx, math.MaxInt, nil) if err != nil { tmpLogger.Infof("Error cleaning up generated proofs for batch %d", batchNumberToVerify) return nil, nil, nil, err @@ -1201,7 +1191,6 @@ func (a *Aggregator) getAndLockBatchToProve( a.logger.Debugf("Calculated acc input hash for batch %d: %v", batchNumberToVerify, accInputHash) a.logger.Debugf("OldAccInputHash: %v", oldAccInputHash) a.logger.Debugf("L1InfoRoot: %v", virtualBatch.L1InfoRoot) - // a.logger.Debugf("LastL2BLockTimestamp: %v", rpcBatch.LastL2BLockTimestamp()) a.logger.Debugf("TimestampLimit: %v", uint64(sequence.Timestamp.Unix())) a.logger.Debugf("LastCoinbase: %v", rpcBatch.LastCoinbase()) a.logger.Debugf("ForcedBlockHashL1: %v", rpcBatch.ForcedBlockHashL1()) @@ -1242,7 +1231,7 @@ func (a *Aggregator) getAndLockBatchToProve( a.logger.Debugf("Time to get witness for batch %d: %v", batchNumberToVerify, end.Sub(start)) // Store the sequence in aggregator DB - err = a.state.AddSequence(ctx, stateSequence, nil) + err = a.storage.AddSequence(ctx, stateSequence, nil) if err != nil { tmpLogger.Infof("Error storing sequence for batch %d", batchNumberToVerify) @@ -1250,25 +1239,9 @@ func (a *Aggregator) getAndLockBatchToProve( } // All the data required to generate a proof is ready - tmpLogger.Infof("Found virtual batch %d pending to generate proof", virtualBatch.BatchNumber) + tmpLogger.Infof("All information to generate proof for batch %d is ready", virtualBatch.BatchNumber) tmpLogger = tmpLogger.WithFields("batch", virtualBatch.BatchNumber) - tmpLogger.Info("Checking profitability to aggregate batch") - - // pass pol collateral as zero here, bcs in smart contract fee for aggregator is not defined yet - isProfitable, err := a.profitabilityChecker.IsProfitable(ctx, big.NewInt(0)) - if err != nil { - tmpLogger.Errorf("Failed to check aggregator profitability, err: %v", err) - - return nil, nil, nil, err - } - - if !isProfitable { - tmpLogger.Infof("Batch is not profitable, pol collateral %d", big.NewInt(0)) - - return nil, nil, nil, err - } - now := time.Now().Round(time.Microsecond) proof := &state.Proof{ BatchNumber: virtualBatch.BatchNumber, @@ -1279,9 +1252,9 @@ func (a *Aggregator) getAndLockBatchToProve( } // Avoid other prover to process the same batch - err = a.state.AddGeneratedProof(ctx, proof, nil) + err = a.storage.AddGeneratedProof(ctx, proof, nil) if err != nil { - tmpLogger.Errorf("Failed to add batch proof, err: %v", err) + tmpLogger.Errorf("Failed to add batch proof to DB for batch %d, err: %v", virtualBatch.BatchNumber, err) return nil, nil, nil, err } @@ -1317,7 +1290,7 @@ func (a *Aggregator) tryGenerateBatchProof(ctx context.Context, prover ProverInt defer func() { if err != nil { tmpLogger.Debug("Deleting proof in progress") - err2 := a.state.DeleteGeneratedProofs(a.ctx, proof.BatchNumber, proof.BatchNumberFinal, nil) + err2 := a.storage.DeleteGeneratedProofs(a.ctx, proof.BatchNumber, proof.BatchNumberFinal, nil) if err2 != nil { tmpLogger.Errorf("Failed to delete proof in progress, err: %v", err2) } @@ -1377,7 +1350,7 @@ func (a *Aggregator) tryGenerateBatchProof(ctx context.Context, prover ProverInt proof.GeneratingSince = nil // final proof has not been generated, update the batch proof - err := a.state.UpdateGeneratedProof(a.ctx, proof, nil) + err := a.storage.UpdateGeneratedProof(a.ctx, proof, nil) if err != nil { err = fmt.Errorf("failed to store batch proof result, %w", err) tmpLogger.Error(FirstToUpper(err.Error())) @@ -1583,7 +1556,6 @@ func printInputProver(logger *log.Logger, inputProver *prover.StatelessInputProv logger.Debugf("Witness length: %v", len(inputProver.PublicInputs.Witness)) logger.Debugf("BatchL2Data length: %v", len(inputProver.PublicInputs.BatchL2Data)) - // logger.Debugf("Full DataStream: %v", common.Bytes2Hex(inputProver.PublicInputs.DataStream)) logger.Debugf("OldAccInputHash: %v", common.BytesToHash(inputProver.PublicInputs.OldAccInputHash)) logger.Debugf("L1InfoRoot: %v", common.BytesToHash(inputProver.PublicInputs.L1InfoRoot)) logger.Debugf("TimestampLimit: %v", inputProver.PublicInputs.TimestampLimit) @@ -1652,7 +1624,7 @@ func (a *Aggregator) handleMonitoredTxResult(result ethtxtypes.MonitoredTxResult } } - err = a.state.DeleteGeneratedProofs(a.ctx, firstBatch, lastBatch, nil) + err = a.storage.DeleteGeneratedProofs(a.ctx, firstBatch, lastBatch, nil) if err != nil { mTxResultLogger.Errorf("failed to delete generated proofs from %d to %d: %v", firstBatch, lastBatch, err) } @@ -1670,7 +1642,7 @@ func (a *Aggregator) cleanupLockedProofs() { case <-a.ctx.Done(): return case <-time.After(a.timeCleanupLockedProofs.Duration): - n, err := a.state.CleanupLockedProofs(a.ctx, a.cfg.GeneratingProofCleanupThreshold, nil) + n, err := a.storage.CleanupLockedProofs(a.ctx, a.cfg.GeneratingProofCleanupThreshold, nil) if err != nil { a.logger.Errorf("Failed to cleanup locked proofs: %v", err) } diff --git a/aggregator/aggregator_test.go b/aggregator/aggregator_test.go index 8d5b5392..95b55367 100644 --- a/aggregator/aggregator_test.go +++ b/aggregator/aggregator_test.go @@ -6,6 +6,7 @@ import ( "crypto/ecdsa" "crypto/elliptic" "crypto/rand" + "database/sql" "encoding/hex" "encoding/json" "errors" @@ -50,13 +51,14 @@ const ( ) type mox struct { - stateMock *mocks.StateInterfaceMock + storageMock *mocks.StorageInterfaceMock ethTxManager *mocks.EthTxManagerClientMock etherman *mocks.EthermanMock proverMock *mocks.ProverInterfaceMock aggLayerClientMock *agglayer.AgglayerClientMock synchronizerMock *mocks.SynchronizerInterfaceMock rpcMock *mocks.RPCInterfaceMock + txerMock *mocks.TxerMock } func WaitUntil(t *testing.T, wg *sync.WaitGroup, timeout time.Duration) { @@ -76,7 +78,7 @@ func WaitUntil(t *testing.T, wg *sync.WaitGroup, timeout time.Duration) { } func Test_Start(t *testing.T) { - mockState := new(mocks.StateInterfaceMock) + mockStorage := new(mocks.StorageInterfaceMock) mockL1Syncr := new(mocks.SynchronizerInterfaceMock) mockEtherman := new(mocks.EthermanMock) mockEthTxManager := new(mocks.EthTxManagerClientMock) @@ -84,21 +86,21 @@ func Test_Start(t *testing.T) { mockL1Syncr.On("Sync", mock.Anything).Return(nil) mockEtherman.On("GetLatestVerifiedBatchNum").Return(uint64(90), nil).Once() mockEtherman.On("GetBatchAccInputHash", mock.Anything, uint64(90)).Return(common.Hash{}, nil).Once() - mockState.On("DeleteUngeneratedProofs", mock.Anything, nil).Return(nil).Once() - mockState.On("CleanupLockedProofs", mock.Anything, "", nil).Return(int64(0), nil) + mockStorage.On("DeleteUngeneratedProofs", mock.Anything, nil).Return(nil).Once() + mockStorage.On("CleanupLockedProofs", mock.Anything, "", nil).Return(int64(0), nil) mockEthTxManager.On("Start").Return(nil) ctx := context.Background() a := &Aggregator{ - state: mockState, + storage: mockStorage, logger: log.GetDefaultLogger(), halted: atomic.Bool{}, l1Syncr: mockL1Syncr, etherman: mockEtherman, ethTxManager: mockEthTxManager, ctx: ctx, - stateDBMutex: &sync.Mutex{}, + storageMutex: &sync.Mutex{}, timeSendFinalProofMutex: &sync.RWMutex{}, timeCleanupLockedProofs: types.Duration{Duration: 5 * time.Second}, accInputHashes: make(map[uint64]common.Hash), @@ -117,26 +119,26 @@ func Test_handleReorg(t *testing.T) { t.Parallel() mockL1Syncr := new(mocks.SynchronizerInterfaceMock) - mockState := new(mocks.StateInterfaceMock) + mockStorage := new(mocks.StorageInterfaceMock) reorgData := synchronizer.ReorgExecutionResult{} a := &Aggregator{ l1Syncr: mockL1Syncr, - state: mockState, + storage: mockStorage, logger: log.GetDefaultLogger(), halted: atomic.Bool{}, ctx: context.Background(), } mockL1Syncr.On("GetLastestVirtualBatchNumber", mock.Anything).Return(uint64(100), nil).Once() - mockState.On("DeleteGeneratedProofs", mock.Anything, mock.Anything, mock.Anything, nil).Return(nil).Once() - mockState.On("DeleteUngeneratedProofs", mock.Anything, nil).Return(nil).Once() + mockStorage.On("DeleteGeneratedProofs", mock.Anything, mock.Anything, mock.Anything, nil).Return(nil).Once() + mockStorage.On("DeleteUngeneratedProofs", mock.Anything, nil).Return(nil).Once() go a.handleReorg(reorgData) time.Sleep(3 * time.Second) assert.True(t, a.halted.Load()) - mockState.AssertExpectations(t) + mockStorage.AssertExpectations(t) mockL1Syncr.AssertExpectations(t) } @@ -144,7 +146,7 @@ func Test_handleRollbackBatches(t *testing.T) { t.Parallel() mockEtherman := new(mocks.EthermanMock) - mockState := new(mocks.StateInterfaceMock) + mockStorage := new(mocks.StorageInterfaceMock) // Test data rollbackData := synchronizer.RollbackBatchesData{ @@ -153,13 +155,13 @@ func Test_handleRollbackBatches(t *testing.T) { mockEtherman.On("GetLatestVerifiedBatchNum").Return(uint64(90), nil).Once() mockEtherman.On("GetBatchAccInputHash", mock.Anything, uint64(90)).Return(common.Hash{}, nil).Once() - mockState.On("DeleteUngeneratedProofs", mock.Anything, mock.Anything).Return(nil).Once() - mockState.On("DeleteGeneratedProofs", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(nil).Once() + mockStorage.On("DeleteUngeneratedProofs", mock.Anything, mock.Anything).Return(nil).Once() + mockStorage.On("DeleteGeneratedProofs", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(nil).Once() a := Aggregator{ ctx: context.Background(), etherman: mockEtherman, - state: mockState, + storage: mockStorage, logger: log.GetDefaultLogger(), halted: atomic.Bool{}, accInputHashes: make(map[uint64]common.Hash), @@ -171,18 +173,18 @@ func Test_handleRollbackBatches(t *testing.T) { assert.False(t, a.halted.Load()) mockEtherman.AssertExpectations(t) - mockState.AssertExpectations(t) + mockStorage.AssertExpectations(t) } func Test_handleRollbackBatchesHalt(t *testing.T) { t.Parallel() mockEtherman := new(mocks.EthermanMock) - mockState := new(mocks.StateInterfaceMock) + mockStorage := new(mocks.StorageInterfaceMock) mockEtherman.On("GetLatestVerifiedBatchNum").Return(uint64(110), nil).Once() - mockState.On("DeleteUngeneratedProofs", mock.Anything, mock.Anything).Return(nil).Once() - mockState.On("DeleteGeneratedProofs", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(nil).Once() + mockStorage.On("DeleteUngeneratedProofs", mock.Anything, mock.Anything).Return(nil).Once() + mockStorage.On("DeleteGeneratedProofs", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(nil).Once() // Test data rollbackData := synchronizer.RollbackBatchesData{ @@ -192,7 +194,7 @@ func Test_handleRollbackBatchesHalt(t *testing.T) { a := Aggregator{ ctx: context.Background(), etherman: mockEtherman, - state: mockState, + storage: mockStorage, logger: log.GetDefaultLogger(), halted: atomic.Bool{}, accInputHashes: make(map[uint64]common.Hash), @@ -211,7 +213,7 @@ func Test_handleRollbackBatchesError(t *testing.T) { t.Parallel() mockEtherman := new(mocks.EthermanMock) - mockState := new(mocks.StateInterfaceMock) + mockStorage := new(mocks.StorageInterfaceMock) mockEtherman.On("GetLatestVerifiedBatchNum").Return(uint64(110), fmt.Errorf("error")).Once() @@ -223,7 +225,7 @@ func Test_handleRollbackBatchesError(t *testing.T) { a := Aggregator{ ctx: context.Background(), etherman: mockEtherman, - state: mockState, + storage: mockStorage, logger: log.GetDefaultLogger(), halted: atomic.Bool{}, accInputHashes: make(map[uint64]common.Hash), @@ -308,7 +310,7 @@ func Test_sendFinalProofSuccess(t *testing.T) { for _, tc := range testCases { tc := tc t.Run(tc.name, func(t *testing.T) { - stateMock := mocks.NewStateInterfaceMock(t) + storageMock := mocks.NewStorageInterfaceMock(t) ethTxManager := mocks.NewEthTxManagerClientMock(t) etherman := mocks.NewEthermanMock(t) aggLayerClient := agglayer.NewAgglayerClientMock(t) @@ -319,14 +321,14 @@ func Test_sendFinalProofSuccess(t *testing.T) { require.NoError(err, "error generating key") a := Aggregator{ - state: stateMock, + storage: storageMock, etherman: etherman, ethTxManager: ethTxManager, aggLayerClient: aggLayerClient, finalProof: make(chan finalProofMsg), logger: log.GetDefaultLogger(), verifyingProof: false, - stateDBMutex: &sync.Mutex{}, + storageMutex: &sync.Mutex{}, timeSendFinalProofMutex: &sync.RWMutex{}, sequencerPrivateKey: privateKey, rpcClient: rpcMock, @@ -336,7 +338,7 @@ func Test_sendFinalProofSuccess(t *testing.T) { a.ctx, a.exit = context.WithCancel(context.Background()) m := mox{ - stateMock: stateMock, + storageMock: storageMock, ethTxManager: ethTxManager, etherman: etherman, aggLayerClientMock: aggLayerClient, @@ -418,7 +420,7 @@ func Test_sendFinalProofError(t *testing.T) { fmt.Println("Stopping sendFinalProof") a.exit() }).Return(nil, errTest).Once() - m.stateMock.On("UpdateGeneratedProof", mock.Anything, mock.Anything, nil).Return(nil).Once() + m.storageMock.On("UpdateGeneratedProof", mock.Anything, mock.Anything, nil).Return(nil).Once() }, asserts: func(a *Aggregator) { assert.False(a.verifyingProof) @@ -442,7 +444,7 @@ func Test_sendFinalProofError(t *testing.T) { fmt.Println("Stopping sendFinalProof") a.exit() }).Return(errTest) - m.stateMock.On("UpdateGeneratedProof", mock.Anything, mock.Anything, nil).Return(nil).Once() + m.storageMock.On("UpdateGeneratedProof", mock.Anything, mock.Anything, nil).Return(nil).Once() }, asserts: func(a *Aggregator) { assert.False(a.verifyingProof) @@ -464,7 +466,7 @@ func Test_sendFinalProofError(t *testing.T) { fmt.Println("Stopping sendFinalProof") a.exit() }).Return(nil, nil, errTest) - m.stateMock.On("UpdateGeneratedProof", mock.Anything, recursiveProof, nil).Return(nil).Once() + m.storageMock.On("UpdateGeneratedProof", mock.Anything, recursiveProof, nil).Return(nil).Once() }, asserts: func(a *Aggregator) { assert.False(a.verifyingProof) @@ -488,7 +490,7 @@ func Test_sendFinalProofError(t *testing.T) { fmt.Println("Stopping sendFinalProof") a.exit() }).Return(nil, errTest).Once() - m.stateMock.On("UpdateGeneratedProof", mock.Anything, recursiveProof, nil).Return(nil).Once() + m.storageMock.On("UpdateGeneratedProof", mock.Anything, recursiveProof, nil).Return(nil).Once() }, asserts: func(a *Aggregator) { assert.False(a.verifyingProof) @@ -499,7 +501,7 @@ func Test_sendFinalProofError(t *testing.T) { for _, tc := range testCases { tc := tc t.Run(tc.name, func(t *testing.T) { - stateMock := mocks.NewStateInterfaceMock(t) + storageMock := mocks.NewStorageInterfaceMock(t) ethTxManager := mocks.NewEthTxManagerClientMock(t) etherman := mocks.NewEthermanMock(t) aggLayerClient := agglayer.NewAgglayerClientMock(t) @@ -510,14 +512,14 @@ func Test_sendFinalProofError(t *testing.T) { require.NoError(err, "error generating key") a := Aggregator{ - state: stateMock, + storage: storageMock, etherman: etherman, ethTxManager: ethTxManager, aggLayerClient: aggLayerClient, finalProof: make(chan finalProofMsg), logger: log.GetDefaultLogger(), verifyingProof: false, - stateDBMutex: &sync.Mutex{}, + storageMutex: &sync.Mutex{}, timeSendFinalProofMutex: &sync.RWMutex{}, sequencerPrivateKey: privateKey, rpcClient: rpcMock, @@ -527,7 +529,7 @@ func Test_sendFinalProofError(t *testing.T) { a.ctx, a.exit = context.WithCancel(context.Background()) m := mox{ - stateMock: stateMock, + storageMock: storageMock, ethTxManager: ethTxManager, etherman: etherman, aggLayerClientMock: aggLayerClient, @@ -626,16 +628,16 @@ func Test_buildFinalProof(t *testing.T) { tc := tc t.Run(tc.name, func(t *testing.T) { proverMock := mocks.NewProverInterfaceMock(t) - stateMock := mocks.NewStateInterfaceMock(t) + storageMock := mocks.NewStorageInterfaceMock(t) rpcMock := mocks.NewRPCInterfaceMock(t) m := mox{ - proverMock: proverMock, - stateMock: stateMock, - rpcMock: rpcMock, + proverMock: proverMock, + storageMock: storageMock, + rpcMock: rpcMock, } a := Aggregator{ - state: stateMock, - logger: log.GetDefaultLogger(), + storage: storageMock, + logger: log.GetDefaultLogger(), cfg: Config{ SenderAddress: common.BytesToAddress([]byte("from")).Hex(), }, @@ -656,9 +658,8 @@ func Test_tryBuildFinalProof(t *testing.T) { errTest := errors.New("test error") from := common.BytesToAddress([]byte("from")) cfg := Config{ - VerifyProofInterval: types.Duration{Duration: time.Millisecond * 1}, - TxProfitabilityCheckerType: ProfitabilityAcceptAll, - SenderAddress: from.Hex(), + VerifyProofInterval: types.Duration{Duration: time.Millisecond * 1}, + SenderAddress: from.Hex(), } latestVerifiedBatchNum := uint64(22) batchNum := uint64(23) @@ -728,10 +729,10 @@ func Test_tryBuildFinalProof(t *testing.T) { m.proverMock.On("ID").Return(proverID).Twice() m.proverMock.On("Addr").Return("addr").Twice() m.etherman.On("GetLatestVerifiedBatchNum").Return(latestVerifiedBatchNum, nil).Once() - m.stateMock.On("GetProofReadyToVerify", mock.MatchedBy(matchProverCtxFn), latestVerifiedBatchNum, nil).Return(&proofToVerify, nil).Once() - proofGeneratingTrueCall := m.stateMock.On("UpdateGeneratedProof", mock.MatchedBy(matchProverCtxFn), &proofToVerify, nil).Return(nil).Once() + m.storageMock.On("GetProofReadyToVerify", mock.MatchedBy(matchProverCtxFn), latestVerifiedBatchNum, nil).Return(&proofToVerify, nil).Once() + proofGeneratingTrueCall := m.storageMock.On("UpdateGeneratedProof", mock.MatchedBy(matchProverCtxFn), &proofToVerify, nil).Return(nil).Once() m.proverMock.On("FinalProof", proofToVerify.Proof, from.String()).Return(nil, errTest).Once() - m.stateMock. + m.storageMock. On("UpdateGeneratedProof", mock.MatchedBy(matchAggregatorCtxFn), &proofToVerify, nil). Return(nil). Once(). @@ -749,11 +750,11 @@ func Test_tryBuildFinalProof(t *testing.T) { m.proverMock.On("ID").Return(proverID).Twice() m.proverMock.On("Addr").Return("addr").Twice() m.etherman.On("GetLatestVerifiedBatchNum").Return(latestVerifiedBatchNum, nil).Once() - m.stateMock.On("GetProofReadyToVerify", mock.MatchedBy(matchProverCtxFn), latestVerifiedBatchNum, nil).Return(&proofToVerify, nil).Once() - proofGeneratingTrueCall := m.stateMock.On("UpdateGeneratedProof", mock.MatchedBy(matchProverCtxFn), &proofToVerify, nil).Return(nil).Once() + m.storageMock.On("GetProofReadyToVerify", mock.MatchedBy(matchProverCtxFn), latestVerifiedBatchNum, nil).Return(&proofToVerify, nil).Once() + proofGeneratingTrueCall := m.storageMock.On("UpdateGeneratedProof", mock.MatchedBy(matchProverCtxFn), &proofToVerify, nil).Return(nil).Once() m.proverMock.On("FinalProof", proofToVerify.Proof, from.String()).Return(&finalProofID, nil).Once() m.proverMock.On("WaitFinalProof", mock.MatchedBy(matchProverCtxFn), finalProofID).Return(nil, errTest).Once() - m.stateMock. + m.storageMock. On("UpdateGeneratedProof", mock.MatchedBy(matchAggregatorCtxFn), &proofToVerify, nil). Return(nil). Once(). @@ -771,7 +772,7 @@ func Test_tryBuildFinalProof(t *testing.T) { m.proverMock.On("ID").Return(proverID).Once() m.proverMock.On("Addr").Return(proverID).Once() m.etherman.On("GetLatestVerifiedBatchNum").Return(latestVerifiedBatchNum, nil).Once() - m.stateMock.On("GetProofReadyToVerify", mock.MatchedBy(matchProverCtxFn), latestVerifiedBatchNum, nil).Return(nil, errTest).Once() + m.storageMock.On("GetProofReadyToVerify", mock.MatchedBy(matchProverCtxFn), latestVerifiedBatchNum, nil).Return(nil, errTest).Once() }, asserts: func(result bool, a *Aggregator, err error) { assert.False(result) @@ -785,7 +786,7 @@ func Test_tryBuildFinalProof(t *testing.T) { m.proverMock.On("ID").Return(proverID).Once() m.proverMock.On("Addr").Return(proverID).Once() m.etherman.On("GetLatestVerifiedBatchNum").Return(latestVerifiedBatchNum, nil).Once() - m.stateMock.On("GetProofReadyToVerify", mock.MatchedBy(matchProverCtxFn), latestVerifiedBatchNum, nil).Return(nil, state.ErrNotFound).Once() + m.storageMock.On("GetProofReadyToVerify", mock.MatchedBy(matchProverCtxFn), latestVerifiedBatchNum, nil).Return(nil, state.ErrNotFound).Once() }, asserts: func(result bool, a *Aggregator, err error) { assert.False(result) @@ -799,8 +800,8 @@ func Test_tryBuildFinalProof(t *testing.T) { m.proverMock.On("ID").Return(proverID).Twice() m.proverMock.On("Addr").Return(proverID).Twice() m.etherman.On("GetLatestVerifiedBatchNum").Return(latestVerifiedBatchNum, nil).Once() - m.stateMock.On("GetProofReadyToVerify", mock.MatchedBy(matchProverCtxFn), latestVerifiedBatchNum, nil).Return(&proofToVerify, nil).Once() - m.stateMock.On("UpdateGeneratedProof", mock.MatchedBy(matchProverCtxFn), &proofToVerify, nil).Return(nil).Once() + m.storageMock.On("GetProofReadyToVerify", mock.MatchedBy(matchProverCtxFn), latestVerifiedBatchNum, nil).Return(&proofToVerify, nil).Once() + m.storageMock.On("UpdateGeneratedProof", mock.MatchedBy(matchProverCtxFn), &proofToVerify, nil).Return(nil).Once() m.proverMock.On("FinalProof", proofToVerify.Proof, from.String()).Return(&finalProofID, nil).Once() m.proverMock.On("WaitFinalProof", mock.MatchedBy(matchProverCtxFn), finalProofID).Return(&finalProof, nil).Once() }, @@ -822,7 +823,7 @@ func Test_tryBuildFinalProof(t *testing.T) { m.proverMock.On("ID").Return(proverID).Once() m.proverMock.On("Addr").Return(proverID).Once() m.etherman.On("GetLatestVerifiedBatchNum").Return(latestVerifiedBatchNum, nil).Once() - m.stateMock.On("CheckProofContainsCompleteSequences", mock.MatchedBy(matchProverCtxFn), &proofToVerify, nil).Return(false, errTest).Once() + m.storageMock.On("CheckProofContainsCompleteSequences", mock.MatchedBy(matchProverCtxFn), &proofToVerify, nil).Return(false, errTest).Once() }, asserts: func(result bool, a *Aggregator, err error) { assert.False(result) @@ -851,7 +852,7 @@ func Test_tryBuildFinalProof(t *testing.T) { m.proverMock.On("ID").Return(proverID).Once() m.proverMock.On("Addr").Return(proverID).Once() m.etherman.On("GetLatestVerifiedBatchNum").Return(latestVerifiedBatchNum, nil).Once() - m.stateMock.On("CheckProofContainsCompleteSequences", mock.MatchedBy(matchProverCtxFn), &proofToVerify, nil).Return(false, nil).Once() + m.storageMock.On("CheckProofContainsCompleteSequences", mock.MatchedBy(matchProverCtxFn), &proofToVerify, nil).Return(false, nil).Once() }, asserts: func(result bool, a *Aggregator, err error) { assert.False(result) @@ -866,7 +867,7 @@ func Test_tryBuildFinalProof(t *testing.T) { m.proverMock.On("ID").Return(proverID).Twice() m.proverMock.On("Addr").Return(proverID).Twice() m.etherman.On("GetLatestVerifiedBatchNum").Return(latestVerifiedBatchNum, nil).Once() - m.stateMock.On("CheckProofContainsCompleteSequences", mock.MatchedBy(matchProverCtxFn), &proofToVerify, nil).Return(true, nil).Once() + m.storageMock.On("CheckProofContainsCompleteSequences", mock.MatchedBy(matchProverCtxFn), &proofToVerify, nil).Return(true, nil).Once() m.proverMock.On("FinalProof", proofToVerify.Proof, from.String()).Return(&finalProofID, nil).Once() m.proverMock.On("WaitFinalProof", mock.MatchedBy(matchProverCtxFn), finalProofID).Return(&finalProof, nil).Once() }, @@ -885,18 +886,18 @@ func Test_tryBuildFinalProof(t *testing.T) { for _, tc := range testCases { tc := tc t.Run(tc.name, func(t *testing.T) { - stateMock := mocks.NewStateInterfaceMock(t) + storageMock := mocks.NewStorageInterfaceMock(t) ethTxManager := mocks.NewEthTxManagerClientMock(t) etherman := mocks.NewEthermanMock(t) proverMock := mocks.NewProverInterfaceMock(t) a := Aggregator{ cfg: cfg, - state: stateMock, + storage: storageMock, etherman: etherman, ethTxManager: ethTxManager, logger: log.GetDefaultLogger(), - stateDBMutex: &sync.Mutex{}, + storageMutex: &sync.Mutex{}, timeSendFinalProofMutex: &sync.RWMutex{}, timeCleanupLockedProofs: cfg.CleanupLockedProofsInterval, finalProof: make(chan finalProofMsg), @@ -907,7 +908,7 @@ func Test_tryBuildFinalProof(t *testing.T) { aggregatorCtx := context.WithValue(context.Background(), "owner", ownerAggregator) //nolint:staticcheck a.ctx, a.exit = context.WithCancel(aggregatorCtx) m := mox{ - stateMock: stateMock, + storageMock: storageMock, ethTxManager: ethTxManager, etherman: etherman, proverMock: proverMock, @@ -974,7 +975,7 @@ func Test_tryAggregateProofs(t *testing.T) { m.proverMock.On("Name").Return(proverName).Twice() m.proverMock.On("ID").Return(proverID).Twice() m.proverMock.On("Addr").Return("addr") - m.stateMock.On("GetProofsToAggregate", mock.MatchedBy(matchProverCtxFn), nil).Return(nil, nil, errTest).Once() + m.storageMock.On("GetProofsToAggregate", mock.MatchedBy(matchProverCtxFn), nil).Return(nil, nil, errTest).Once() }, asserts: func(result bool, a *Aggregator, err error) { assert.False(result) @@ -987,7 +988,7 @@ func Test_tryAggregateProofs(t *testing.T) { m.proverMock.On("Name").Return(proverName).Twice() m.proverMock.On("ID").Return(proverID).Twice() m.proverMock.On("Addr").Return("addr") - m.stateMock.On("GetProofsToAggregate", mock.MatchedBy(matchProverCtxFn), nil).Return(nil, nil, state.ErrNotFound).Once() + m.storageMock.On("GetProofsToAggregate", mock.MatchedBy(matchProverCtxFn), nil).Return(nil, nil, state.ErrNotFound).Once() }, asserts: func(result bool, a *Aggregator, err error) { assert.False(result) @@ -1000,12 +1001,12 @@ func Test_tryAggregateProofs(t *testing.T) { m.proverMock.On("Name").Return(proverName).Twice() m.proverMock.On("ID").Return(proverID).Twice() m.proverMock.On("Addr").Return("addr") - dbTx := &mocks.DbTxMock{} - dbTx.On("Rollback", mock.MatchedBy(matchProverCtxFn)).Return(nil).Once() - m.stateMock.On("BeginStateTransaction", mock.MatchedBy(matchProverCtxFn)).Return(dbTx, nil).Once() - m.stateMock.On("GetProofsToAggregate", mock.MatchedBy(matchProverCtxFn), nil).Return(&proof1, &proof2, nil).Once() - m.stateMock. - On("UpdateGeneratedProof", mock.MatchedBy(matchProverCtxFn), &proof1, dbTx). + m.txerMock.On("Rollback").Return(nil).Once() + m.storageMock.On("BeginTx", mock.Anything, (*sql.TxOptions)(nil)).Return(m.txerMock, nil).Once() + m.storageMock.On("GetProofsToAggregate", mock.MatchedBy(matchProverCtxFn), nil).Return(&proof1, &proof2, nil).Once() + + m.storageMock. + On("UpdateGeneratedProof", mock.MatchedBy(matchProverCtxFn), &proof1, mock.Anything). Run(func(args mock.Arguments) { proofArg, ok := args[1].(*state.Proof) assert.True(ok, "Expected argument of type *state.Proof") @@ -1019,18 +1020,19 @@ func Test_tryAggregateProofs(t *testing.T) { assert.ErrorIs(err, errTest) }, }, + { name: "AggregatedProof error", setup: func(m mox, a *Aggregator) { m.proverMock.On("Name").Return(proverName).Twice() m.proverMock.On("ID").Return(proverID).Twice() m.proverMock.On("Addr").Return("addr") - dbTx := &mocks.DbTxMock{} - lockProofsTxBegin := m.stateMock.On("BeginStateTransaction", mock.MatchedBy(matchProverCtxFn)).Return(dbTx, nil).Once() - lockProofsTxCommit := dbTx.On("Commit", mock.MatchedBy(matchProverCtxFn)).Return(nil).Once() - m.stateMock.On("GetProofsToAggregate", mock.MatchedBy(matchProverCtxFn), nil).Return(&proof1, &proof2, nil).Once() - proof1GeneratingTrueCall := m.stateMock. - On("UpdateGeneratedProof", mock.MatchedBy(matchProverCtxFn), &proof1, dbTx). + + lockProofsTxBegin := m.storageMock.On("BeginTx", mock.MatchedBy(matchProverCtxFn), (*sql.TxOptions)(nil)).Return(m.txerMock, nil).Once() + // lockProofsTxCommit := m.proverMock.On("Commit").Return(nil).Once() + m.storageMock.On("GetProofsToAggregate", mock.MatchedBy(matchProverCtxFn), nil).Return(&proof1, &proof2, nil).Once() + proof1GeneratingTrueCall := m.storageMock. + On("UpdateGeneratedProof", mock.MatchedBy(matchProverCtxFn), &proof1, mock.Anything). Run(func(args mock.Arguments) { proofArg, ok := args[1].(*state.Proof) assert.True(ok, "Expected argument of type *state.Proof") @@ -1038,8 +1040,8 @@ func Test_tryAggregateProofs(t *testing.T) { }). Return(nil). Once() - proof2GeneratingTrueCall := m.stateMock. - On("UpdateGeneratedProof", mock.MatchedBy(matchProverCtxFn), &proof2, dbTx). + proof2GeneratingTrueCall := m.storageMock. + On("UpdateGeneratedProof", mock.MatchedBy(matchProverCtxFn), &proof2, mock.Anything). Run(func(args mock.Arguments) { // Use a type assertion with a check proofArg, ok := args[1].(*state.Proof) @@ -1051,9 +1053,9 @@ func Test_tryAggregateProofs(t *testing.T) { Return(nil). Once() m.proverMock.On("AggregatedProof", proof1.Proof, proof2.Proof).Return(nil, errTest).Once() - m.stateMock.On("BeginStateTransaction", mock.MatchedBy(matchAggregatorCtxFn)).Return(dbTx, nil).Once().NotBefore(lockProofsTxBegin) - m.stateMock. - On("UpdateGeneratedProof", mock.MatchedBy(matchAggregatorCtxFn), &proof1, dbTx). + m.storageMock.On("BeginTx", mock.Anything, (*sql.TxOptions)(nil)).Return(m.txerMock, nil).Once().NotBefore(lockProofsTxBegin) + m.storageMock. + On("UpdateGeneratedProof", mock.MatchedBy(matchAggregatorCtxFn), &proof1, mock.Anything). Run(func(args mock.Arguments) { proofArg, ok := args[1].(*state.Proof) if !ok { @@ -1064,8 +1066,8 @@ func Test_tryAggregateProofs(t *testing.T) { Return(nil). Once(). NotBefore(proof1GeneratingTrueCall) - m.stateMock. - On("UpdateGeneratedProof", mock.MatchedBy(matchAggregatorCtxFn), &proof2, dbTx). + m.storageMock. + On("UpdateGeneratedProof", mock.MatchedBy(matchAggregatorCtxFn), &proof2, mock.Anything). Run(func(args mock.Arguments) { proofArg, ok := args[1].(*state.Proof) if !ok { @@ -1076,25 +1078,25 @@ func Test_tryAggregateProofs(t *testing.T) { Return(nil). Once(). NotBefore(proof2GeneratingTrueCall) - dbTx.On("Commit", mock.MatchedBy(matchAggregatorCtxFn)).Return(nil).Once().NotBefore(lockProofsTxCommit) + m.txerMock.On("Commit").Return(nil) }, asserts: func(result bool, a *Aggregator, err error) { assert.False(result) assert.ErrorIs(err, errTest) }, }, + { name: "WaitRecursiveProof prover error", setup: func(m mox, a *Aggregator) { m.proverMock.On("Name").Return(proverName).Twice() m.proverMock.On("ID").Return(proverID).Twice() m.proverMock.On("Addr").Return("addr") - dbTx := &mocks.DbTxMock{} - lockProofsTxBegin := m.stateMock.On("BeginStateTransaction", mock.MatchedBy(matchProverCtxFn)).Return(dbTx, nil).Once() - lockProofsTxCommit := dbTx.On("Commit", mock.MatchedBy(matchProverCtxFn)).Return(nil).Once() - m.stateMock.On("GetProofsToAggregate", mock.MatchedBy(matchProverCtxFn), nil).Return(&proof1, &proof2, nil).Once() - proof1GeneratingTrueCall := m.stateMock. - On("UpdateGeneratedProof", mock.MatchedBy(matchProverCtxFn), &proof1, dbTx). + lockProofsTxBegin := m.storageMock.On("BeginTx", mock.MatchedBy(matchProverCtxFn), (*sql.TxOptions)(nil)).Return(m.txerMock, nil).Once() + // lockProofsTxCommit := dbTx.On("Commit", mock.MatchedBy(matchProverCtxFn)).Return(nil).Once() + m.storageMock.On("GetProofsToAggregate", mock.MatchedBy(matchProverCtxFn), nil).Return(&proof1, &proof2, nil).Once() + proof1GeneratingTrueCall := m.storageMock. + On("UpdateGeneratedProof", mock.MatchedBy(matchProverCtxFn), &proof1, mock.Anything). Run(func(args mock.Arguments) { proofArg, ok := args[1].(*state.Proof) if !ok { @@ -1104,8 +1106,8 @@ func Test_tryAggregateProofs(t *testing.T) { }). Return(nil). Once() - proof2GeneratingTrueCall := m.stateMock. - On("UpdateGeneratedProof", mock.MatchedBy(matchProverCtxFn), &proof2, dbTx). + proof2GeneratingTrueCall := m.storageMock. + On("UpdateGeneratedProof", mock.MatchedBy(matchProverCtxFn), &proof2, mock.Anything). Run(func(args mock.Arguments) { proofArg, ok := args[1].(*state.Proof) assert.True(ok, "Expected argument of type *state.Proof") @@ -1114,10 +1116,11 @@ func Test_tryAggregateProofs(t *testing.T) { Return(nil). Once() m.proverMock.On("AggregatedProof", proof1.Proof, proof2.Proof).Return(&proofID, nil).Once() + m.proverMock.On("WaitRecursiveProof", mock.MatchedBy(matchProverCtxFn), proofID).Return("", common.Hash{}, common.Hash{}, errTest).Once() - m.stateMock.On("BeginStateTransaction", mock.MatchedBy(matchAggregatorCtxFn)).Return(dbTx, nil).Once().NotBefore(lockProofsTxBegin) - m.stateMock. - On("UpdateGeneratedProof", mock.MatchedBy(matchAggregatorCtxFn), &proof1, dbTx). + m.storageMock.On("BeginTx", mock.MatchedBy(matchAggregatorCtxFn), (*sql.TxOptions)(nil)).Return(m.txerMock, nil).Once().NotBefore(lockProofsTxBegin) + m.storageMock. + On("UpdateGeneratedProof", mock.MatchedBy(matchAggregatorCtxFn), &proof1, mock.Anything). Run(func(args mock.Arguments) { proofArg, ok := args[1].(*state.Proof) assert.True(ok, "Expected argument of type *state.Proof") @@ -1126,8 +1129,8 @@ func Test_tryAggregateProofs(t *testing.T) { Return(nil). Once(). NotBefore(proof1GeneratingTrueCall) - m.stateMock. - On("UpdateGeneratedProof", mock.MatchedBy(matchAggregatorCtxFn), &proof2, dbTx). + m.storageMock. + On("UpdateGeneratedProof", mock.MatchedBy(matchAggregatorCtxFn), &proof2, mock.Anything). Run(func(args mock.Arguments) { proofArg, ok := args[1].(*state.Proof) assert.True(ok, "Expected argument of type *state.Proof") @@ -1136,25 +1139,25 @@ func Test_tryAggregateProofs(t *testing.T) { Return(nil). Once(). NotBefore(proof2GeneratingTrueCall) - dbTx.On("Commit", mock.MatchedBy(matchAggregatorCtxFn)).Return(nil).Once().NotBefore(lockProofsTxCommit) + m.txerMock.On("Commit").Return(nil) }, asserts: func(result bool, a *Aggregator, err error) { assert.False(result) assert.ErrorIs(err, errTest) }, }, + { name: "unlockProofsToAggregate error after WaitRecursiveProof prover error", setup: func(m mox, a *Aggregator) { m.proverMock.On("Name").Return(proverName).Twice() m.proverMock.On("ID").Return(proverID).Twice() m.proverMock.On("Addr").Return(proverID) - dbTx := &mocks.DbTxMock{} - lockProofsTxBegin := m.stateMock.On("BeginStateTransaction", mock.MatchedBy(matchProverCtxFn)).Return(dbTx, nil).Once() - dbTx.On("Commit", mock.MatchedBy(matchProverCtxFn)).Return(nil).Once() - m.stateMock.On("GetProofsToAggregate", mock.MatchedBy(matchProverCtxFn), nil).Return(&proof1, &proof2, nil).Once() - proof1GeneratingTrueCall := m.stateMock. - On("UpdateGeneratedProof", mock.MatchedBy(matchProverCtxFn), &proof1, dbTx). + // lockProofsTxBegin := m.storageMock.On("BeginTx", mock.MatchedBy(matchProverCtxFn)).Return(m.txerMock, nil).Once() + m.txerMock.On("Commit").Return(nil) + m.storageMock.On("GetProofsToAggregate", mock.MatchedBy(matchProverCtxFn), nil).Return(&proof1, &proof2, nil).Once() + proof1GeneratingTrueCall := m.storageMock. + On("UpdateGeneratedProof", mock.MatchedBy(matchProverCtxFn), &proof1, mock.Anything). Run(func(args mock.Arguments) { proofArg, ok := args[1].(*state.Proof) assert.True(ok, "Expected argument of type *state.Proof") @@ -1162,8 +1165,8 @@ func Test_tryAggregateProofs(t *testing.T) { }). Return(nil). Once() - m.stateMock. - On("UpdateGeneratedProof", mock.MatchedBy(matchProverCtxFn), &proof2, dbTx). + m.storageMock. + On("UpdateGeneratedProof", mock.MatchedBy(matchProverCtxFn), &proof2, mock.Anything). Run(func(args mock.Arguments) { proofArg, ok := args[1].(*state.Proof) assert.True(ok, "Expected argument of type *state.Proof") @@ -1173,9 +1176,9 @@ func Test_tryAggregateProofs(t *testing.T) { Once() m.proverMock.On("AggregatedProof", proof1.Proof, proof2.Proof).Return(&proofID, nil).Once() m.proverMock.On("WaitRecursiveProof", mock.MatchedBy(matchProverCtxFn), proofID).Return("", common.Hash{}, common.Hash{}, errTest).Once() - m.stateMock.On("BeginStateTransaction", mock.MatchedBy(matchAggregatorCtxFn)).Return(dbTx, nil).Once().NotBefore(lockProofsTxBegin) - m.stateMock. - On("UpdateGeneratedProof", mock.MatchedBy(matchAggregatorCtxFn), &proof1, dbTx). + m.storageMock.On("BeginTx", mock.Anything, (*sql.TxOptions)(nil)).Return(m.txerMock, nil) + m.storageMock. + On("UpdateGeneratedProof", mock.MatchedBy(matchAggregatorCtxFn), &proof1, mock.Anything). Run(func(args mock.Arguments) { proofArg, ok := args[1].(*state.Proof) assert.True(ok, "Expected argument of type *state.Proof") @@ -1184,7 +1187,7 @@ func Test_tryAggregateProofs(t *testing.T) { Return(errTest). Once(). NotBefore(proof1GeneratingTrueCall) - dbTx.On("Rollback", mock.MatchedBy(matchAggregatorCtxFn)).Return(nil).Once() + m.txerMock.On("Rollback").Return(nil).Once() }, asserts: func(result bool, a *Aggregator, err error) { assert.False(result) @@ -1197,12 +1200,11 @@ func Test_tryAggregateProofs(t *testing.T) { m.proverMock.On("Name").Return(proverName).Twice() m.proverMock.On("ID").Return(proverID).Twice() m.proverMock.On("Addr").Return("addr") - dbTx := &mocks.DbTxMock{} - lockProofsTxBegin := m.stateMock.On("BeginStateTransaction", mock.MatchedBy(matchProverCtxFn)).Return(dbTx, nil).Twice() - lockProofsTxCommit := dbTx.On("Commit", mock.MatchedBy(matchProverCtxFn)).Return(nil).Once() - m.stateMock.On("GetProofsToAggregate", mock.MatchedBy(matchProverCtxFn), nil).Return(&proof1, &proof2, nil).Once() - proof1GeneratingTrueCall := m.stateMock. - On("UpdateGeneratedProof", mock.MatchedBy(matchProverCtxFn), &proof1, dbTx). + // lockProofsTxBegin := m.storageMock.On("BeginTx", mock.MatchedBy(matchProverCtxFn)).Return(dbTx, nil).Twice() + // lockProofsTxCommit := dbTx.On("Commit", mock.MatchedBy(matchProverCtxFn)).Return(nil).Once() + m.storageMock.On("GetProofsToAggregate", mock.MatchedBy(matchProverCtxFn), nil).Return(&proof1, &proof2, nil).Once() + proof1GeneratingTrueCall := m.storageMock. + On("UpdateGeneratedProof", mock.MatchedBy(matchProverCtxFn), &proof1, mock.Anything). Run(func(args mock.Arguments) { proofArg, ok := args[1].(*state.Proof) assert.True(ok, "Expected argument of type *state.Proof") @@ -1210,8 +1212,8 @@ func Test_tryAggregateProofs(t *testing.T) { }). Return(nil). Once() - proof2GeneratingTrueCall := m.stateMock. - On("UpdateGeneratedProof", mock.MatchedBy(matchProverCtxFn), &proof2, dbTx). + proof2GeneratingTrueCall := m.storageMock. + On("UpdateGeneratedProof", mock.MatchedBy(matchProverCtxFn), &proof2, mock.Anything). Run(func(args mock.Arguments) { proofArg, ok := args[1].(*state.Proof) assert.True(ok, "Expected argument of type *state.Proof") @@ -1221,11 +1223,11 @@ func Test_tryAggregateProofs(t *testing.T) { Once() m.proverMock.On("AggregatedProof", proof1.Proof, proof2.Proof).Return(&proofID, nil).Once() m.proverMock.On("WaitRecursiveProof", mock.MatchedBy(matchProverCtxFn), proofID).Return(recursiveProof, common.Hash{}, common.Hash{}, nil).Once() - m.stateMock.On("DeleteGeneratedProofs", mock.MatchedBy(matchProverCtxFn), proof1.BatchNumber, proof2.BatchNumberFinal, dbTx).Return(errTest).Once() - dbTx.On("Rollback", mock.MatchedBy(matchProverCtxFn)).Return(nil).Once() - m.stateMock.On("BeginStateTransaction", mock.MatchedBy(matchAggregatorCtxFn)).Return(dbTx, nil).Once().NotBefore(lockProofsTxBegin) - m.stateMock. - On("UpdateGeneratedProof", mock.MatchedBy(matchAggregatorCtxFn), &proof1, dbTx). + m.storageMock.On("DeleteGeneratedProofs", mock.MatchedBy(matchProverCtxFn), proof1.BatchNumber, proof2.BatchNumberFinal, mock.Anything).Return(errTest).Once() + m.txerMock.On("Rollback").Return(nil).Once() + m.storageMock.On("BeginTx", mock.Anything, (*sql.TxOptions)(nil)).Return(m.txerMock, nil) + m.storageMock. + On("UpdateGeneratedProof", mock.MatchedBy(matchAggregatorCtxFn), &proof1, mock.Anything). Run(func(args mock.Arguments) { proofArg, ok := args[1].(*state.Proof) assert.True(ok, "Expected argument of type *state.Proof") @@ -1234,8 +1236,8 @@ func Test_tryAggregateProofs(t *testing.T) { Return(nil). Once(). NotBefore(proof1GeneratingTrueCall) - m.stateMock. - On("UpdateGeneratedProof", mock.MatchedBy(matchAggregatorCtxFn), &proof2, dbTx). + m.storageMock. + On("UpdateGeneratedProof", mock.MatchedBy(matchAggregatorCtxFn), &proof2, mock.Anything). Run(func(args mock.Arguments) { proofArg, ok := args[1].(*state.Proof) assert.True(ok, "Expected argument of type *state.Proof") @@ -1244,25 +1246,25 @@ func Test_tryAggregateProofs(t *testing.T) { Return(nil). Once(). NotBefore(proof2GeneratingTrueCall) - dbTx.On("Commit", mock.MatchedBy(matchAggregatorCtxFn)).Return(nil).Once().NotBefore(lockProofsTxCommit) + m.txerMock.On("Commit").Return(nil) }, asserts: func(result bool, a *Aggregator, err error) { assert.False(result) assert.ErrorIs(err, errTest) }, }, + { name: "rollback after AddGeneratedProof error in db transaction", setup: func(m mox, a *Aggregator) { m.proverMock.On("Name").Return(proverName).Twice() m.proverMock.On("ID").Return(proverID).Twice() m.proverMock.On("Addr").Return("addr") - dbTx := &mocks.DbTxMock{} - lockProofsTxBegin := m.stateMock.On("BeginStateTransaction", mock.MatchedBy(matchProverCtxFn)).Return(dbTx, nil).Twice() - lockProofsTxCommit := dbTx.On("Commit", mock.MatchedBy(matchProverCtxFn)).Return(nil).Once() - m.stateMock.On("GetProofsToAggregate", mock.MatchedBy(matchProverCtxFn), nil).Return(&proof1, &proof2, nil).Once() - proof1GeneratingTrueCall := m.stateMock. - On("UpdateGeneratedProof", mock.MatchedBy(matchProverCtxFn), &proof1, dbTx). + // lockProofsTxBegin := m.storageMock.On("BeginTx", mock.MatchedBy(matchProverCtxFn)).Return(dbTx, nil).Twice() + // lockProofsTxCommit := dbTx.On("Commit", mock.MatchedBy(matchProverCtxFn)).Return(nil).Once() + m.storageMock.On("GetProofsToAggregate", mock.MatchedBy(matchProverCtxFn), nil).Return(&proof1, &proof2, nil).Once() + proof1GeneratingTrueCall := m.storageMock. + On("UpdateGeneratedProof", mock.MatchedBy(matchProverCtxFn), &proof1, mock.Anything). Run(func(args mock.Arguments) { proofArg, ok := args[1].(*state.Proof) assert.True(ok, "Expected argument of type *state.Proof") @@ -1270,8 +1272,8 @@ func Test_tryAggregateProofs(t *testing.T) { }). Return(nil). Once() - proof2GeneratingTrueCall := m.stateMock. - On("UpdateGeneratedProof", mock.MatchedBy(matchProverCtxFn), &proof2, dbTx). + proof2GeneratingTrueCall := m.storageMock. + On("UpdateGeneratedProof", mock.MatchedBy(matchProverCtxFn), &proof2, mock.Anything). Run(func(args mock.Arguments) { proofArg, ok := args[1].(*state.Proof) assert.True(ok, "Expected argument of type *state.Proof") @@ -1281,12 +1283,12 @@ func Test_tryAggregateProofs(t *testing.T) { Once() m.proverMock.On("AggregatedProof", proof1.Proof, proof2.Proof).Return(&proofID, nil).Once() m.proverMock.On("WaitRecursiveProof", mock.MatchedBy(matchProverCtxFn), proofID).Return(recursiveProof, common.Hash{}, common.Hash{}, nil).Once() - m.stateMock.On("DeleteGeneratedProofs", mock.MatchedBy(matchProverCtxFn), proof1.BatchNumber, proof2.BatchNumberFinal, dbTx).Return(nil).Once() - m.stateMock.On("AddGeneratedProof", mock.MatchedBy(matchProverCtxFn), mock.Anything, dbTx).Return(errTest).Once() - dbTx.On("Rollback", mock.MatchedBy(matchProverCtxFn)).Return(nil).Once() - m.stateMock.On("BeginStateTransaction", mock.MatchedBy(matchAggregatorCtxFn)).Return(dbTx, nil).Once().NotBefore(lockProofsTxBegin) - m.stateMock. - On("UpdateGeneratedProof", mock.MatchedBy(matchAggregatorCtxFn), &proof1, dbTx). + m.storageMock.On("DeleteGeneratedProofs", mock.MatchedBy(matchProverCtxFn), proof1.BatchNumber, proof2.BatchNumberFinal, mock.Anything).Return(nil).Once() + m.storageMock.On("AddGeneratedProof", mock.MatchedBy(matchProverCtxFn), mock.Anything, mock.Anything).Return(errTest).Once() + m.txerMock.On("Rollback").Return(nil).Once() + m.storageMock.On("BeginTx", mock.Anything, (*sql.TxOptions)(nil)).Return(m.txerMock, nil) + m.storageMock. + On("UpdateGeneratedProof", mock.MatchedBy(matchAggregatorCtxFn), &proof1, mock.Anything). Run(func(args mock.Arguments) { proofArg, ok := args[1].(*state.Proof) assert.True(ok, "Expected argument of type *state.Proof") @@ -1295,8 +1297,8 @@ func Test_tryAggregateProofs(t *testing.T) { Return(nil). Once(). NotBefore(proof1GeneratingTrueCall) - m.stateMock. - On("UpdateGeneratedProof", mock.MatchedBy(matchAggregatorCtxFn), &proof2, dbTx). + m.storageMock. + On("UpdateGeneratedProof", mock.MatchedBy(matchAggregatorCtxFn), &proof2, mock.Anything). Run(func(args mock.Arguments) { proofArg, ok := args[1].(*state.Proof) assert.True(ok, "Expected argument of type *state.Proof") @@ -1305,7 +1307,7 @@ func Test_tryAggregateProofs(t *testing.T) { Return(nil). Once(). NotBefore(proof2GeneratingTrueCall) - dbTx.On("Commit", mock.MatchedBy(matchAggregatorCtxFn)).Return(nil).Once().NotBefore(lockProofsTxCommit) + m.txerMock.On("Commit").Return(nil) }, asserts: func(result bool, a *Aggregator, err error) { assert.False(result) @@ -1320,12 +1322,11 @@ func Test_tryAggregateProofs(t *testing.T) { m.proverMock.On("Name").Return(proverName).Times(3) m.proverMock.On("ID").Return(proverID).Times(3) m.proverMock.On("Addr").Return("addr") - dbTx := &mocks.DbTxMock{} - m.stateMock.On("BeginStateTransaction", mock.MatchedBy(matchProverCtxFn)).Return(dbTx, nil).Twice() - dbTx.On("Commit", mock.MatchedBy(matchProverCtxFn)).Return(nil).Twice() - m.stateMock.On("GetProofsToAggregate", mock.MatchedBy(matchProverCtxFn), nil).Return(&proof1, &proof2, nil).Once() - m.stateMock. - On("UpdateGeneratedProof", mock.MatchedBy(matchProverCtxFn), &proof1, dbTx). + m.storageMock.On("BeginTx", mock.Anything, (*sql.TxOptions)(nil)).Return(m.txerMock, nil) + m.txerMock.On("Commit").Return(nil) + m.storageMock.On("GetProofsToAggregate", mock.MatchedBy(matchProverCtxFn), nil).Return(&proof1, &proof2, nil).Once() + m.storageMock. + On("UpdateGeneratedProof", mock.MatchedBy(matchProverCtxFn), &proof1, mock.Anything). Run(func(args mock.Arguments) { proofArg, ok := args[1].(*state.Proof) assert.True(ok, "Expected argument of type *state.Proof") @@ -1333,8 +1334,8 @@ func Test_tryAggregateProofs(t *testing.T) { }). Return(nil). Once() - m.stateMock. - On("UpdateGeneratedProof", mock.MatchedBy(matchProverCtxFn), &proof2, dbTx). + m.storageMock. + On("UpdateGeneratedProof", mock.MatchedBy(matchProverCtxFn), &proof2, mock.Anything). Run(func(args mock.Arguments) { proofArg, ok := args[1].(*state.Proof) assert.True(ok, "Expected argument of type *state.Proof") @@ -1345,14 +1346,14 @@ func Test_tryAggregateProofs(t *testing.T) { m.proverMock.On("AggregatedProof", proof1.Proof, proof2.Proof).Return(&proofID, nil).Once() m.proverMock.On("WaitRecursiveProof", mock.MatchedBy(matchProverCtxFn), proofID).Return(recursiveProof, common.Hash{}, common.Hash{}, nil).Once() - m.stateMock.On("DeleteGeneratedProofs", mock.MatchedBy(matchProverCtxFn), proof1.BatchNumber, proof2.BatchNumberFinal, dbTx).Return(nil).Once() + m.storageMock.On("DeleteGeneratedProofs", mock.MatchedBy(matchProverCtxFn), proof1.BatchNumber, proof2.BatchNumberFinal, m.txerMock).Return(nil).Once() expectedInputProver := map[string]interface{}{ "recursive_proof_1": proof1.Proof, "recursive_proof_2": proof2.Proof, } b, err := json.Marshal(expectedInputProver) require.NoError(err) - m.stateMock.On("AddGeneratedProof", mock.MatchedBy(matchProverCtxFn), mock.Anything, dbTx).Run( + m.storageMock.On("AddGeneratedProof", mock.MatchedBy(matchProverCtxFn), mock.Anything, mock.Anything).Run( func(args mock.Arguments) { proof, ok := args[1].(*state.Proof) if !ok { @@ -1369,7 +1370,7 @@ func Test_tryAggregateProofs(t *testing.T) { ).Return(nil).Once() m.etherman.On("GetLatestVerifiedBatchNum").Return(uint64(42), errTest).Once() - m.stateMock.On("UpdateGeneratedProof", mock.MatchedBy(matchAggregatorCtxFn), mock.Anything, nil).Run( + m.storageMock.On("UpdateGeneratedProof", mock.MatchedBy(matchAggregatorCtxFn), mock.Anything, nil).Run( func(args mock.Arguments) { proof, ok := args[1].(*state.Proof) if !ok { @@ -1394,17 +1395,18 @@ func Test_tryAggregateProofs(t *testing.T) { for _, tc := range testCases { t.Run(tc.name, func(t *testing.T) { - stateMock := mocks.NewStateInterfaceMock(t) + storageMock := mocks.NewStorageInterfaceMock(t) ethTxManager := mocks.NewEthTxManagerClientMock(t) etherman := mocks.NewEthermanMock(t) proverMock := mocks.NewProverInterfaceMock(t) + txerMock := mocks.NewTxerMock(t) a := Aggregator{ cfg: cfg, - state: stateMock, + storage: storageMock, etherman: etherman, ethTxManager: ethTxManager, logger: log.GetDefaultLogger(), - stateDBMutex: &sync.Mutex{}, + storageMutex: &sync.Mutex{}, timeSendFinalProofMutex: &sync.RWMutex{}, timeCleanupLockedProofs: cfg.CleanupLockedProofsInterval, finalProof: make(chan finalProofMsg), @@ -1414,10 +1416,11 @@ func Test_tryAggregateProofs(t *testing.T) { aggregatorCtx := context.WithValue(context.Background(), "owner", ownerAggregator) //nolint:staticcheck a.ctx, a.exit = context.WithCancel(aggregatorCtx) m := mox{ - stateMock: stateMock, + storageMock: storageMock, ethTxManager: ethTxManager, etherman: etherman, proverMock: proverMock, + txerMock: txerMock, } if tc.setup != nil { tc.setup(m, &a) @@ -1439,7 +1442,6 @@ func Test_tryGenerateBatchProof(t *testing.T) { from := common.BytesToAddress([]byte("from")) cfg := Config{ VerifyProofInterval: types.Duration{Duration: time.Duration(10000000)}, - TxProfitabilityCheckerType: ProfitabilityAcceptAll, SenderAddress: from.Hex(), IntervalAfterWhichBatchConsolidateAnyway: types.Duration{Duration: time.Second * 1}, ChainID: uint64(1), @@ -1515,15 +1517,15 @@ func Test_tryGenerateBatchProof(t *testing.T) { m.proverMock.On("ID").Return(proverID) m.proverMock.On("Addr").Return("addr") m.etherman.On("GetLatestVerifiedBatchNum").Return(uint64(0), nil) - m.stateMock.On("CheckProofExistsForBatch", mock.Anything, uint64(1), nil).Return(false, nil) + m.storageMock.On("CheckProofExistsForBatch", mock.Anything, uint64(1), nil).Return(false, nil) m.synchronizerMock.On("GetSequenceByBatchNumber", mock.Anything, mock.Anything).Return(&sequence, nil) m.synchronizerMock.On("GetVirtualBatchByBatchNumber", mock.Anything, mock.Anything).Return(&virtualBatch, nil) m.synchronizerMock.On("GetL1BlockByNumber", mock.Anything, mock.Anything).Return(&synchronizer.L1Block{ParentHash: common.Hash{}}, nil) m.rpcMock.On("GetBatch", mock.Anything).Return(rpcBatch, nil) m.rpcMock.On("GetWitness", mock.Anything, false).Return([]byte("witness"), nil) - m.stateMock.On("AddGeneratedProof", mock.Anything, mock.Anything, nil).Return(nil) - m.stateMock.On("AddSequence", mock.Anything, mock.Anything, nil).Return(nil) - m.stateMock.On("DeleteGeneratedProofs", mock.Anything, uint64(1), uint64(1), nil).Return(nil) + m.storageMock.On("AddGeneratedProof", mock.Anything, mock.Anything, nil).Return(nil) + m.storageMock.On("AddSequence", mock.Anything, mock.Anything, nil).Return(nil) + m.storageMock.On("DeleteGeneratedProofs", mock.Anything, uint64(1), uint64(1), nil).Return(nil) m.synchronizerMock.On("GetLeafsByL1InfoRoot", mock.Anything, l1InfoRoot).Return(l1InfoTreeLeaf, nil) m.synchronizerMock.On("GetL1InfoTreeLeaves", mock.Anything, mock.Anything).Return(map[uint32]synchronizer.L1InfoTreeLeaf{ 1: { @@ -1581,8 +1583,8 @@ func Test_tryGenerateBatchProof(t *testing.T) { m.synchronizerMock.On("GetVirtualBatchByBatchNumber", mock.Anything, mock.Anything).Return(&virtualBatch, nil).Once() m.etherman.On("GetLatestVerifiedBatchNum").Return(lastVerifiedBatchNum, nil).Once() - m.stateMock.On("CheckProofExistsForBatch", mock.MatchedBy(matchProverCtxFn), mock.Anything, nil).Return(true, nil) - m.stateMock.On("CleanupGeneratedProofs", mock.Anything, mock.Anything, mock.Anything).Return(nil).Once() + m.storageMock.On("CheckProofExistsForBatch", mock.MatchedBy(matchProverCtxFn), mock.Anything, nil).Return(true, nil) + m.storageMock.On("CleanupGeneratedProofs", mock.Anything, mock.Anything, mock.Anything).Return(nil).Once() sequence := synchronizer.SequencedBatches{ FromBatchNumber: uint64(10), ToBatchNumber: uint64(20), @@ -1593,8 +1595,8 @@ func Test_tryGenerateBatchProof(t *testing.T) { rpcBatch.SetLastL2BLockTimestamp(uint64(time.Now().Unix())) m.rpcMock.On("GetWitness", mock.Anything, false).Return([]byte("witness"), nil) m.rpcMock.On("GetBatch", mock.Anything).Return(rpcBatch, nil) - m.stateMock.On("AddSequence", mock.MatchedBy(matchProverCtxFn), mock.Anything, nil).Return(nil).Once() - m.stateMock.On("AddGeneratedProof", mock.MatchedBy(matchProverCtxFn), mock.Anything, nil).Run( + m.storageMock.On("AddSequence", mock.MatchedBy(matchProverCtxFn), mock.Anything, nil).Return(nil).Once() + m.storageMock.On("AddGeneratedProof", mock.MatchedBy(matchProverCtxFn), mock.Anything, nil).Run( func(args mock.Arguments) { proof, ok := args[1].(*state.Proof) if !ok { @@ -1615,7 +1617,7 @@ func Test_tryGenerateBatchProof(t *testing.T) { }, nil) m.proverMock.On("BatchProof", mock.Anything).Return(nil, errTest).Once() - m.stateMock.On("DeleteGeneratedProofs", mock.MatchedBy(matchAggregatorCtxFn), batchToProve.BatchNumber, batchToProve.BatchNumber, nil).Return(nil).Once() + m.storageMock.On("DeleteGeneratedProofs", mock.MatchedBy(matchAggregatorCtxFn), batchToProve.BatchNumber, batchToProve.BatchNumber, nil).Return(nil).Once() }, asserts: func(result bool, a *Aggregator, err error) { assert.False(result) @@ -1642,7 +1644,7 @@ func Test_tryGenerateBatchProof(t *testing.T) { m.synchronizerMock.On("GetVirtualBatchByBatchNumber", mock.Anything, lastVerifiedBatchNum+1).Return(&virtualBatch, nil).Once() m.etherman.On("GetLatestVerifiedBatchNum").Return(lastVerifiedBatchNum, nil).Once() - m.stateMock.On("CheckProofExistsForBatch", mock.MatchedBy(matchProverCtxFn), mock.AnythingOfType("uint64"), nil).Return(false, nil).Once() + m.storageMock.On("CheckProofExistsForBatch", mock.MatchedBy(matchProverCtxFn), mock.AnythingOfType("uint64"), nil).Return(false, nil).Once() sequence := synchronizer.SequencedBatches{ FromBatchNumber: uint64(10), ToBatchNumber: uint64(20), @@ -1651,8 +1653,8 @@ func Test_tryGenerateBatchProof(t *testing.T) { rpcBatch := rpctypes.NewRPCBatch(lastVerifiedBatchNum+1, common.Hash{}, []string{}, batchL2Data, common.Hash{}, common.BytesToHash([]byte("mock LocalExitRoot")), common.BytesToHash([]byte("mock StateRoot")), common.Address{}, false) rpcBatch.SetLastL2BLockTimestamp(uint64(time.Now().Unix())) m.rpcMock.On("GetWitness", lastVerifiedBatchNum+1, false).Return([]byte("witness"), nil) - m.stateMock.On("AddSequence", mock.MatchedBy(matchProverCtxFn), mock.Anything, nil).Return(nil).Once() - m.stateMock.On("AddGeneratedProof", mock.MatchedBy(matchProverCtxFn), mock.Anything, nil).Run( + m.storageMock.On("AddSequence", mock.MatchedBy(matchProverCtxFn), mock.Anything, nil).Return(nil).Once() + m.storageMock.On("AddGeneratedProof", mock.MatchedBy(matchProverCtxFn), mock.Anything, nil).Run( func(args mock.Arguments) { proof, ok := args[1].(*state.Proof) if !ok { @@ -1676,7 +1678,7 @@ func Test_tryGenerateBatchProof(t *testing.T) { m.rpcMock.On("GetBatch", lastVerifiedBatchNum+1).Return(rpcBatch, nil) m.proverMock.On("BatchProof", mock.Anything).Return(&proofID, nil).Once() m.proverMock.On("WaitRecursiveProof", mock.MatchedBy(matchProverCtxFn), proofID).Return("", common.Hash{}, common.Hash{}, errTest).Once() - m.stateMock.On("DeleteGeneratedProofs", mock.MatchedBy(matchAggregatorCtxFn), batchToProve.BatchNumber, batchToProve.BatchNumber, nil).Return(nil) + m.storageMock.On("DeleteGeneratedProofs", mock.MatchedBy(matchAggregatorCtxFn), batchToProve.BatchNumber, batchToProve.BatchNumber, nil).Return(nil) }, asserts: func(result bool, a *Aggregator, err error) { assert.False(result) @@ -1703,7 +1705,7 @@ func Test_tryGenerateBatchProof(t *testing.T) { m.synchronizerMock.On("GetVirtualBatchByBatchNumber", mock.Anything, lastVerifiedBatchNum+1).Return(&virtualBatch, nil) m.etherman.On("GetLatestVerifiedBatchNum").Return(lastVerifiedBatchNum, nil) - m.stateMock.On("CheckProofExistsForBatch", mock.MatchedBy(matchProverCtxFn), mock.AnythingOfType("uint64"), nil).Return(false, nil) + m.storageMock.On("CheckProofExistsForBatch", mock.MatchedBy(matchProverCtxFn), mock.AnythingOfType("uint64"), nil).Return(false, nil) sequence := synchronizer.SequencedBatches{ FromBatchNumber: uint64(10), ToBatchNumber: uint64(20), @@ -1712,8 +1714,8 @@ func Test_tryGenerateBatchProof(t *testing.T) { rpcBatch := rpctypes.NewRPCBatch(lastVerifiedBatchNum+1, common.Hash{}, []string{}, batchL2Data, common.Hash{}, common.BytesToHash([]byte("mock LocalExitRoot")), common.BytesToHash([]byte("mock StateRoot")), common.Address{}, false) rpcBatch.SetLastL2BLockTimestamp(uint64(time.Now().Unix())) m.rpcMock.On("GetWitness", lastVerifiedBatchNum+1, false).Return([]byte("witness"), nil) - m.stateMock.On("AddSequence", mock.MatchedBy(matchProverCtxFn), mock.Anything, nil).Return(nil) - m.stateMock.On("AddGeneratedProof", mock.MatchedBy(matchProverCtxFn), mock.Anything, nil).Run( + m.storageMock.On("AddSequence", mock.MatchedBy(matchProverCtxFn), mock.Anything, nil).Return(nil) + m.storageMock.On("AddGeneratedProof", mock.MatchedBy(matchProverCtxFn), mock.Anything, nil).Run( func(args mock.Arguments) { proof, ok := args[1].(*state.Proof) if !ok { @@ -1737,7 +1739,7 @@ func Test_tryGenerateBatchProof(t *testing.T) { m.rpcMock.On("GetBatch", lastVerifiedBatchNum+1).Return(rpcBatch, nil) m.proverMock.On("BatchProof", mock.Anything).Return(&proofID, nil) m.proverMock.On("WaitRecursiveProof", mock.MatchedBy(matchProverCtxFn), proofID).Return("", common.Hash{}, common.Hash{}, nil) - m.stateMock.On("UpdateGeneratedProof", mock.Anything, mock.Anything, nil).Return(nil) + m.storageMock.On("UpdateGeneratedProof", mock.Anything, mock.Anything, nil).Return(nil) }, asserts: func(result bool, a *Aggregator, err error) { assert.True(result) @@ -1756,7 +1758,7 @@ func Test_tryGenerateBatchProof(t *testing.T) { l1InfoRoot := common.HexToHash("0x057e9950fbd39b002e323f37c2330d0c096e66919e24cc96fb4b2dfa8f4af782") m.etherman.On("GetLatestVerifiedBatchNum").Return(lastVerifiedBatchNum, nil).Once() - m.stateMock.On("CheckProofExistsForBatch", mock.MatchedBy(matchProverCtxFn), mock.AnythingOfType("uint64"), nil).Return(false, nil).Once() + m.storageMock.On("CheckProofExistsForBatch", mock.MatchedBy(matchProverCtxFn), mock.AnythingOfType("uint64"), nil).Return(false, nil).Once() sequence := synchronizer.SequencedBatches{ FromBatchNumber: uint64(10), ToBatchNumber: uint64(20), @@ -1765,8 +1767,8 @@ func Test_tryGenerateBatchProof(t *testing.T) { rpcBatch := rpctypes.NewRPCBatch(lastVerifiedBatchNum+1, common.Hash{}, []string{}, batchL2Data, common.Hash{}, common.BytesToHash([]byte("mock LocalExitRoot")), common.BytesToHash([]byte("mock StateRoot")), common.Address{}, false) rpcBatch.SetLastL2BLockTimestamp(uint64(time.Now().Unix())) m.rpcMock.On("GetBatch", lastVerifiedBatchNum+1).Return(rpcBatch, nil) - m.stateMock.On("AddSequence", mock.MatchedBy(matchProverCtxFn), mock.Anything, nil).Return(nil).Once() - m.stateMock.On("AddGeneratedProof", mock.MatchedBy(matchProverCtxFn), mock.Anything, nil).Run( + m.storageMock.On("AddSequence", mock.MatchedBy(matchProverCtxFn), mock.Anything, nil).Return(nil).Once() + m.storageMock.On("AddGeneratedProof", mock.MatchedBy(matchProverCtxFn), mock.Anything, nil).Run( func(args mock.Arguments) { proof, ok := args[1].(*state.Proof) if !ok { @@ -1799,7 +1801,7 @@ func Test_tryGenerateBatchProof(t *testing.T) { m.proverMock.On("BatchProof", mock.Anything).Return(&proofID, nil).Once() m.proverMock.On("WaitRecursiveProof", mock.MatchedBy(matchProverCtxFn), proofID).Return("", common.Hash{}, common.Hash{}, errTest).Once() - m.stateMock.On("DeleteGeneratedProofs", mock.Anything, batchToProve.BatchNumber, batchToProve.BatchNumber, nil).Return(errTest).Once() + m.storageMock.On("DeleteGeneratedProofs", mock.MatchedBy(matchAggregatorCtxFn), batchToProve.BatchNumber, batchToProve.BatchNumber, nil).Return(errTest).Once() }, asserts: func(result bool, a *Aggregator, err error) { assert.False(result) @@ -1810,7 +1812,7 @@ func Test_tryGenerateBatchProof(t *testing.T) { for x, tc := range testCases { t.Run(tc.name, func(t *testing.T) { - stateMock := mocks.NewStateInterfaceMock(t) + storageMock := mocks.NewStorageInterfaceMock(t) ethTxManager := mocks.NewEthTxManagerClientMock(t) etherman := mocks.NewEthermanMock(t) proverMock := mocks.NewProverInterfaceMock(t) @@ -1819,15 +1821,14 @@ func Test_tryGenerateBatchProof(t *testing.T) { a := Aggregator{ cfg: cfg, - state: stateMock, + storage: storageMock, etherman: etherman, ethTxManager: ethTxManager, logger: log.GetDefaultLogger(), - stateDBMutex: &sync.Mutex{}, + storageMutex: &sync.Mutex{}, timeSendFinalProofMutex: &sync.RWMutex{}, timeCleanupLockedProofs: cfg.CleanupLockedProofsInterval, finalProof: make(chan finalProofMsg), - profitabilityChecker: NewTxProfitabilityCheckerAcceptAll(stateMock, cfg.IntervalAfterWhichBatchConsolidateAnyway.Duration), l1Syncr: synchronizerMock, rpcClient: mockRPC, accInputHashes: make(map[uint64]common.Hash), @@ -1840,7 +1841,7 @@ func Test_tryGenerateBatchProof(t *testing.T) { a.ctx, a.exit = context.WithCancel(aggregatorCtx) m := mox{ - stateMock: stateMock, + storageMock: storageMock, ethTxManager: ethTxManager, etherman: etherman, proverMock: proverMock, diff --git a/aggregator/config.go b/aggregator/config.go index 2d7178f7..e17d68af 100644 --- a/aggregator/config.go +++ b/aggregator/config.go @@ -4,7 +4,6 @@ import ( "fmt" "math/big" - "github.com/0xPolygon/cdk/aggregator/db" "github.com/0xPolygon/cdk/config/types" "github.com/0xPolygon/cdk/log" "github.com/0xPolygon/zkevm-ethtx-manager/ethtxmanager" @@ -62,14 +61,6 @@ type Config struct { // ProofStatePollingInterval is the interval time to polling the prover about the generation state of a proof ProofStatePollingInterval types.Duration `mapstructure:"ProofStatePollingInterval"` - // TxProfitabilityCheckerType type for checking is it profitable for aggregator to validate batch - // possible values: base/acceptall - TxProfitabilityCheckerType TxProfitabilityCheckerType `mapstructure:"TxProfitabilityCheckerType"` - - // TxProfitabilityMinReward min reward for base tx profitability checker when aggregator will validate batch - // this parameter is used for the base tx profitability checker - TxProfitabilityMinReward TokenAmountWithDecimals `mapstructure:"TxProfitabilityMinReward"` - // IntervalAfterWhichBatchConsolidateAnyway is the interval duration for the main sequencer to check // if there are no transactions. If there are no transactions in this interval, the sequencer will // consolidate the batch anyway. @@ -117,8 +108,8 @@ type Config struct { // UseFullWitness is a flag to enable the use of full witness in the aggregator UseFullWitness bool `mapstructure:"UseFullWitness"` - // DB is the database configuration - DB db.Config `mapstructure:"DB"` + // DBPath is the path to the database + DBPath string `mapstructure:"DBPath"` // EthTxManager is the config for the ethtxmanager EthTxManager ethtxmanager.Config `mapstructure:"EthTxManager"` diff --git a/aggregator/db/config.go b/aggregator/db/config.go deleted file mode 100644 index ad56155f..00000000 --- a/aggregator/db/config.go +++ /dev/null @@ -1,25 +0,0 @@ -package db - -// Config provide fields to configure the pool -type Config struct { - // Database name - Name string `mapstructure:"Name"` - - // Database User name - User string `mapstructure:"User"` - - // Database Password of the user - Password string `mapstructure:"Password"` - - // Host address of database - Host string `mapstructure:"Host"` - - // Port Number of database - Port string `mapstructure:"Port"` - - // EnableLog - EnableLog bool `mapstructure:"EnableLog"` - - // MaxConns is the maximum number of connections in the pool. - MaxConns int `mapstructure:"MaxConns"` -} diff --git a/aggregator/db/db.go b/aggregator/db/db.go deleted file mode 100644 index ecfffc11..00000000 --- a/aggregator/db/db.go +++ /dev/null @@ -1,31 +0,0 @@ -package db - -import ( - "context" - "fmt" - - "github.com/0xPolygon/cdk/log" - "github.com/jackc/pgx/v4/pgxpool" -) - -// NewSQLDB creates a new SQL DB -func NewSQLDB(logger *log.Logger, cfg Config) (*pgxpool.Pool, error) { - config, err := pgxpool.ParseConfig(fmt.Sprintf("postgres://%s:%s@%s:%s/%s?pool_max_conns=%d", - cfg.User, cfg.Password, cfg.Host, cfg.Port, cfg.Name, cfg.MaxConns)) - if err != nil { - logger.Errorf("Unable to parse DB config: %v\n", err) - return nil, err - } - - if cfg.EnableLog { - config.ConnConfig.Logger = dbLoggerImpl{} - } - - conn, err := pgxpool.ConnectConfig(context.Background(), config) - if err != nil { - logger.Errorf("Unable to connect to database: %v\n", err) - return nil, err - } - - return conn, nil -} diff --git a/aggregator/db/dbstorage/dbstorage.go b/aggregator/db/dbstorage/dbstorage.go new file mode 100644 index 00000000..b20a1c71 --- /dev/null +++ b/aggregator/db/dbstorage/dbstorage.go @@ -0,0 +1,35 @@ +package dbstorage + +import ( + "context" + "database/sql" + + "github.com/0xPolygon/cdk/db" +) + +// DBStorage implements the Storage interface +type DBStorage struct { + DB *sql.DB +} + +// NewDBStorage creates a new DBStorage instance +func NewDBStorage(dbPath string) (*DBStorage, error) { + db, err := db.NewSQLiteDB(dbPath) + if err != nil { + return nil, err + } + + return &DBStorage{DB: db}, nil +} + +func (d *DBStorage) BeginTx(ctx context.Context, options *sql.TxOptions) (db.Txer, error) { + return db.NewTx(ctx, d.DB) +} + +func (d *DBStorage) getExecQuerier(dbTx db.Txer) db.Querier { + if dbTx == nil { + return d.DB + } + + return dbTx +} diff --git a/aggregator/db/dbstorage/proof.go b/aggregator/db/dbstorage/proof.go new file mode 100644 index 00000000..d3065c7e --- /dev/null +++ b/aggregator/db/dbstorage/proof.go @@ -0,0 +1,356 @@ +package dbstorage + +import ( + "context" + "database/sql" + "errors" + "fmt" + "time" + + "github.com/0xPolygon/cdk/db" + "github.com/0xPolygon/cdk/state" +) + +// CheckProofExistsForBatch checks if the batch is already included in any proof +func (d *DBStorage) CheckProofExistsForBatch(ctx context.Context, batchNumber uint64, dbTx db.Txer) (bool, error) { + const checkProofExistsForBatchSQL = ` + SELECT EXISTS (SELECT 1 FROM proof p WHERE $1 >= p.batch_num AND $1 <= p.batch_num_final) + ` + e := d.getExecQuerier(dbTx) + var exists bool + err := e.QueryRow(checkProofExistsForBatchSQL, batchNumber).Scan(&exists) + if err != nil && !errors.Is(err, sql.ErrNoRows) { + return exists, err + } + return exists, nil +} + +// CheckProofContainsCompleteSequences checks if a recursive proof contains complete sequences +func (d *DBStorage) CheckProofContainsCompleteSequences( + ctx context.Context, proof *state.Proof, dbTx db.Txer, +) (bool, error) { + const getProofContainsCompleteSequencesSQL = ` + SELECT EXISTS (SELECT 1 FROM sequence s1 WHERE s1.from_batch_num = $1) AND + EXISTS (SELECT 1 FROM sequence s2 WHERE s2.to_batch_num = $2) + ` + e := d.getExecQuerier(dbTx) + var exists bool + err := e.QueryRow(getProofContainsCompleteSequencesSQL, proof.BatchNumber, proof.BatchNumberFinal).Scan(&exists) + if err != nil && !errors.Is(err, sql.ErrNoRows) { + return exists, err + } + return exists, nil +} + +// GetProofReadyToVerify return the proof that is ready to verify +func (d *DBStorage) GetProofReadyToVerify( + ctx context.Context, lastVerfiedBatchNumber uint64, dbTx db.Txer, +) (*state.Proof, error) { + const getProofReadyToVerifySQL = ` + SELECT + p.batch_num, + p.batch_num_final, + p.proof, + p.proof_id, + p.input_prover, + p.prover, + p.prover_id, + p.generating_since, + p.created_at, + p.updated_at + FROM proof p + WHERE batch_num = $1 AND generating_since IS NULL AND + EXISTS (SELECT 1 FROM sequence s1 WHERE s1.from_batch_num = p.batch_num) AND + EXISTS (SELECT 1 FROM sequence s2 WHERE s2.to_batch_num = p.batch_num_final) + ` + + var proof = &state.Proof{} + + e := d.getExecQuerier(dbTx) + row := e.QueryRow(getProofReadyToVerifySQL, lastVerfiedBatchNumber+1) + + var ( + generatingSince *uint64 + createdAt *uint64 + updatedAt *uint64 + ) + err := row.Scan( + &proof.BatchNumber, &proof.BatchNumberFinal, &proof.Proof, &proof.ProofID, + &proof.InputProver, &proof.Prover, &proof.ProverID, &generatingSince, + &createdAt, &updatedAt, + ) + + if generatingSince != nil { + timeSince := time.Unix(int64(*generatingSince), 0) + proof.GeneratingSince = &timeSince + } + + if createdAt != nil { + proof.CreatedAt = time.Unix(int64(*createdAt), 0) + } + + if updatedAt != nil { + proof.UpdatedAt = time.Unix(int64(*updatedAt), 0) + } + + if errors.Is(err, sql.ErrNoRows) { + return nil, state.ErrNotFound + } else if err != nil { + return nil, err + } + + return proof, err +} + +// GetProofsToAggregate return the next to proof that it is possible to aggregate +func (d *DBStorage) GetProofsToAggregate(ctx context.Context, dbTx db.Txer) (*state.Proof, *state.Proof, error) { + var ( + proof1 = &state.Proof{} + proof2 = &state.Proof{} + ) + + // TODO: add comments to explain the query + const getProofsToAggregateSQL = ` + SELECT + p1.batch_num as p1_batch_num, + p1.batch_num_final as p1_batch_num_final, + p1.proof as p1_proof, + p1.proof_id as p1_proof_id, + p1.input_prover as p1_input_prover, + p1.prover as p1_prover, + p1.prover_id as p1_prover_id, + p1.generating_since as p1_generating_since, + p1.created_at as p1_created_at, + p1.updated_at as p1_updated_at, + p2.batch_num as p2_batch_num, + p2.batch_num_final as p2_batch_num_final, + p2.proof as p2_proof, + p2.proof_id as p2_proof_id, + p2.input_prover as p2_input_prover, + p2.prover as p2_prover, + p2.prover_id as p2_prover_id, + p2.generating_since as p2_generating_since, + p2.created_at as p2_created_at, + p2.updated_at as p2_updated_at + FROM proof p1 INNER JOIN proof p2 ON p1.batch_num_final = p2.batch_num - 1 + WHERE p1.generating_since IS NULL AND p2.generating_since IS NULL AND + p1.proof IS NOT NULL AND p2.proof IS NOT NULL AND + ( + EXISTS ( + SELECT 1 FROM sequence s + WHERE p1.batch_num >= s.from_batch_num AND p1.batch_num <= s.to_batch_num AND + p1.batch_num_final >= s.from_batch_num AND p1.batch_num_final <= s.to_batch_num AND + p2.batch_num >= s.from_batch_num AND p2.batch_num <= s.to_batch_num AND + p2.batch_num_final >= s.from_batch_num AND p2.batch_num_final <= s.to_batch_num + ) + OR + ( + EXISTS ( SELECT 1 FROM sequence s WHERE p1.batch_num = s.from_batch_num) AND + EXISTS ( SELECT 1 FROM sequence s WHERE p1.batch_num_final = s.to_batch_num) AND + EXISTS ( SELECT 1 FROM sequence s WHERE p2.batch_num = s.from_batch_num) AND + EXISTS ( SELECT 1 FROM sequence s WHERE p2.batch_num_final = s.to_batch_num) + ) + ) + ORDER BY p1.batch_num ASC + LIMIT 1 + ` + + e := d.getExecQuerier(dbTx) + row := e.QueryRow(getProofsToAggregateSQL) + + var ( + generatingSince1, generatingSince2 *uint64 + createdAt1, createdAt2 *uint64 + updatedAt1, updatedAt2 *uint64 + ) + + err := row.Scan( + &proof1.BatchNumber, &proof1.BatchNumberFinal, &proof1.Proof, &proof1.ProofID, + &proof1.InputProver, &proof1.Prover, &proof1.ProverID, &generatingSince1, + &createdAt1, &updatedAt1, + &proof2.BatchNumber, &proof2.BatchNumberFinal, &proof2.Proof, &proof2.ProofID, + &proof2.InputProver, &proof2.Prover, &proof2.ProverID, &generatingSince2, + &createdAt1, &updatedAt1, + ) + + if generatingSince1 != nil { + timeSince1 := time.Unix(int64(*generatingSince1), 0) + proof1.GeneratingSince = &timeSince1 + } + + if generatingSince2 != nil { + timeSince2 := time.Unix(int64(*generatingSince2), 0) + proof2.GeneratingSince = &timeSince2 + } + + if createdAt1 != nil { + proof1.CreatedAt = time.Unix(int64(*createdAt1), 0) + } + + if createdAt2 != nil { + proof2.CreatedAt = time.Unix(int64(*createdAt2), 0) + } + + if updatedAt1 != nil { + proof1.UpdatedAt = time.Unix(int64(*updatedAt1), 0) + } + + if updatedAt2 != nil { + proof2.UpdatedAt = time.Unix(int64(*updatedAt2), 0) + } + + if errors.Is(err, sql.ErrNoRows) { + return nil, nil, state.ErrNotFound + } else if err != nil { + return nil, nil, err + } + + return proof1, proof2, err +} + +// AddGeneratedProof adds a generated proof to the storage +func (d *DBStorage) AddGeneratedProof(ctx context.Context, proof *state.Proof, dbTx db.Txer) error { + const addGeneratedProofSQL = ` + INSERT INTO proof ( + batch_num, batch_num_final, proof, proof_id, input_prover, prover, + prover_id, generating_since, created_at, updated_at + ) VALUES ( + $1, $2, $3, $4, $5, $6, $7, $8, $9, $10 + ) + ` + e := d.getExecQuerier(dbTx) + now := time.Now().UTC().Round(time.Microsecond) + + var ( + generatingSince *uint64 + createdAt *uint64 + updatedAt *uint64 + ) + + if proof.GeneratingSince != nil { + generatingSince = new(uint64) + *generatingSince = uint64(proof.GeneratingSince.Unix()) + } + + if !proof.CreatedAt.IsZero() { + createdAt = new(uint64) + *createdAt = uint64(proof.CreatedAt.Unix()) + } else { + createdAt = new(uint64) + *createdAt = uint64(now.Unix()) + } + + if !proof.UpdatedAt.IsZero() { + updatedAt = new(uint64) + *updatedAt = uint64(proof.UpdatedAt.Unix()) + } else { + updatedAt = new(uint64) + *updatedAt = uint64(now.Unix()) + } + + _, err := e.Exec( + addGeneratedProofSQL, proof.BatchNumber, proof.BatchNumberFinal, proof.Proof, proof.ProofID, + proof.InputProver, proof.Prover, proof.ProverID, generatingSince, createdAt, updatedAt, + ) + return err +} + +// UpdateGeneratedProof updates a generated proof in the storage +func (d *DBStorage) UpdateGeneratedProof(ctx context.Context, proof *state.Proof, dbTx db.Txer) error { + const updateGeneratedProofSQL = ` + UPDATE proof + SET proof = $3, + proof_id = $4, + input_prover = $5, + prover = $6, + prover_id = $7, + generating_since = $8, + updated_at = $9 + WHERE batch_num = $1 + AND batch_num_final = $2 + ` + e := d.getExecQuerier(dbTx) + now := time.Now().UTC().Round(time.Microsecond) + + var ( + generatingSince *uint64 + updatedAt *uint64 + ) + + if proof.GeneratingSince != nil { + generatingSince = new(uint64) + *generatingSince = uint64(proof.GeneratingSince.Unix()) + } + + if !proof.UpdatedAt.IsZero() { + updatedAt = new(uint64) + *updatedAt = uint64(proof.UpdatedAt.Unix()) + } else { + updatedAt = new(uint64) + *updatedAt = uint64(now.Unix()) + } + _, err := e.Exec( + updateGeneratedProofSQL, proof.Proof, proof.ProofID, proof.InputProver, + proof.Prover, proof.ProverID, generatingSince, updatedAt, proof.BatchNumber, proof.BatchNumberFinal, + ) + return err +} + +// DeleteGeneratedProofs deletes from the storage the generated proofs falling +// inside the batch numbers range. +func (d *DBStorage) DeleteGeneratedProofs( + ctx context.Context, batchNumber uint64, batchNumberFinal uint64, dbTx db.Txer, +) error { + const deleteGeneratedProofSQL = "DELETE FROM proof WHERE batch_num >= $1 AND batch_num_final <= $2" + e := d.getExecQuerier(dbTx) + _, err := e.Exec(deleteGeneratedProofSQL, batchNumber, batchNumberFinal) + return err +} + +// CleanupGeneratedProofs deletes from the storage the generated proofs up to +// the specified batch number included. +func (d *DBStorage) CleanupGeneratedProofs(ctx context.Context, batchNumber uint64, dbTx db.Txer) error { + const deleteGeneratedProofSQL = "DELETE FROM proof WHERE batch_num_final <= $1" + e := d.getExecQuerier(dbTx) + _, err := e.Exec(deleteGeneratedProofSQL, batchNumber) + return err +} + +// CleanupLockedProofs deletes from the storage the proofs locked in generating +// state for more than the provided threshold. +func (d *DBStorage) CleanupLockedProofs(ctx context.Context, duration string, dbTx db.Txer) (int64, error) { + seconds, err := convertDurationToSeconds(duration) + if err != nil { + return 0, err + } + + difference := time.Now().Unix() - seconds + + sql := fmt.Sprintf("DELETE FROM proof WHERE generating_since is not null and generating_since < %d", difference) + e := d.getExecQuerier(dbTx) + ct, err := e.Exec(sql) + if err != nil { + return 0, err + } + return ct.RowsAffected() +} + +// DeleteUngeneratedProofs deletes ungenerated proofs. +// This method is meant to be use during aggregator boot-up sequence +func (d *DBStorage) DeleteUngeneratedProofs(ctx context.Context, dbTx db.Txer) error { + const deleteUngeneratedProofsSQL = "DELETE FROM proof WHERE generating_since IS NOT NULL" + e := d.getExecQuerier(dbTx) + _, err := e.Exec(deleteUngeneratedProofsSQL) + return err +} + +func convertDurationToSeconds(duration string) (int64, error) { + // Parse the duration using time.ParseDuration + parsedDuration, err := time.ParseDuration(duration) + if err != nil { + return 0, fmt.Errorf("invalid duration format: %w", err) + } + + // Return the duration in seconds + return int64(parsedDuration.Seconds()), nil +} diff --git a/aggregator/db/dbstorage/proof_test.go b/aggregator/db/dbstorage/proof_test.go new file mode 100644 index 00000000..f8095086 --- /dev/null +++ b/aggregator/db/dbstorage/proof_test.go @@ -0,0 +1,150 @@ +package dbstorage + +import ( + "context" + "math" + "testing" + "time" + + "github.com/0xPolygon/cdk/aggregator/db" + "github.com/0xPolygon/cdk/state" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +var ( + proofID = "proof_1" + prover = "prover_1" + proverID = "prover_id" +) + +func Test_Proof(t *testing.T) { + dbPath := "file::memory:?cache=shared" + err := db.RunMigrationsUp(dbPath, db.AggregatorMigrationName) + assert.NoError(t, err) + + ctx := context.Background() + now := time.Now() + + DBStorage, err := NewDBStorage(dbPath) + assert.NoError(t, err) + + dbtxer, err := DBStorage.BeginTx(ctx, nil) + require.NoError(t, err) + + exists, err := DBStorage.CheckProofExistsForBatch(ctx, 1, dbtxer) + assert.NoError(t, err) + assert.False(t, exists) + + proof := state.Proof{ + BatchNumber: 1, + BatchNumberFinal: 1, + Proof: "proof content", + InputProver: "input prover", + ProofID: &proofID, + Prover: &prover, + ProverID: &proofID, + GeneratingSince: nil, + CreatedAt: now, + UpdatedAt: now, + } + + err = DBStorage.AddGeneratedProof(ctx, &proof, dbtxer) + assert.NoError(t, err) + + err = DBStorage.AddSequence(ctx, state.Sequence{FromBatchNumber: 1, ToBatchNumber: 1}, dbtxer) + assert.NoError(t, err) + + contains, err := DBStorage.CheckProofContainsCompleteSequences(ctx, &proof, dbtxer) + assert.NoError(t, err) + assert.True(t, contains) + + proof2, err := DBStorage.GetProofReadyToVerify(ctx, 0, dbtxer) + assert.NoError(t, err) + assert.NotNil(t, proof2) + + require.Equal(t, proof.BatchNumber, proof2.BatchNumber) + require.Equal(t, proof.BatchNumberFinal, proof2.BatchNumberFinal) + require.Equal(t, proof.Proof, proof2.Proof) + require.Equal(t, *proof.ProofID, *proof2.ProofID) + require.Equal(t, proof.InputProver, proof2.InputProver) + require.Equal(t, *proof.Prover, *proof2.Prover) + require.Equal(t, *proof.ProverID, *proof2.ProverID) + require.Equal(t, proof.CreatedAt.Unix(), proof2.CreatedAt.Unix()) + require.Equal(t, proof.UpdatedAt.Unix(), proof2.UpdatedAt.Unix()) + + proof = state.Proof{ + BatchNumber: 1, + BatchNumberFinal: 1, + Proof: "proof content", + InputProver: "input prover", + ProofID: &proofID, + Prover: &prover, + ProverID: &proofID, + GeneratingSince: &now, + CreatedAt: time.Now(), + UpdatedAt: time.Now(), + } + + err = DBStorage.UpdateGeneratedProof(ctx, &proof, dbtxer) + assert.NoError(t, err) + + sequence := state.Sequence{FromBatchNumber: 3, ToBatchNumber: 4} + + proof3 := state.Proof{ + BatchNumber: 3, + BatchNumberFinal: 3, + GeneratingSince: nil, + } + + proof4 := state.Proof{ + BatchNumber: 4, + BatchNumberFinal: 4, + GeneratingSince: nil, + } + + err = DBStorage.AddSequence(ctx, sequence, dbtxer) + assert.NoError(t, err) + + err = DBStorage.AddGeneratedProof(ctx, &proof3, dbtxer) + assert.NoError(t, err) + + err = DBStorage.AddGeneratedProof(ctx, &proof4, dbtxer) + assert.NoError(t, err) + + proof5, proof6, err := DBStorage.GetProofsToAggregate(ctx, dbtxer) + assert.NoError(t, err) + assert.NotNil(t, proof5) + assert.NotNil(t, proof6) + + err = DBStorage.DeleteGeneratedProofs(ctx, 1, math.MaxInt, dbtxer) + assert.NoError(t, err) + + err = DBStorage.CleanupGeneratedProofs(ctx, 1, dbtxer) + assert.NoError(t, err) + + now = time.Now() + + proof3.GeneratingSince = &now + proof4.GeneratingSince = &now + + err = DBStorage.AddGeneratedProof(ctx, &proof3, dbtxer) + assert.NoError(t, err) + + err = DBStorage.AddGeneratedProof(ctx, &proof4, dbtxer) + assert.NoError(t, err) + + time.Sleep(5 * time.Second) + + affected, err := DBStorage.CleanupLockedProofs(ctx, "4s", dbtxer) + assert.NoError(t, err) + require.Equal(t, int64(2), affected) + + proof5, proof6, err = DBStorage.GetProofsToAggregate(ctx, dbtxer) + assert.EqualError(t, err, state.ErrNotFound.Error()) + assert.Nil(t, proof5) + assert.Nil(t, proof6) + + err = DBStorage.DeleteUngeneratedProofs(ctx, dbtxer) + assert.NoError(t, err) +} diff --git a/aggregator/db/dbstorage/sequence.go b/aggregator/db/dbstorage/sequence.go new file mode 100644 index 00000000..96063201 --- /dev/null +++ b/aggregator/db/dbstorage/sequence.go @@ -0,0 +1,21 @@ +package dbstorage + +import ( + "context" + + "github.com/0xPolygon/cdk/db" + "github.com/0xPolygon/cdk/state" +) + +// AddSequence stores the sequence information to allow the aggregator verify sequences. +func (d *DBStorage) AddSequence(ctx context.Context, sequence state.Sequence, dbTx db.Txer) error { + const addSequenceSQL = ` + INSERT INTO sequence (from_batch_num, to_batch_num) + VALUES($1, $2) + ON CONFLICT (from_batch_num) DO UPDATE SET to_batch_num = $2 + ` + + e := d.getExecQuerier(dbTx) + _, err := e.Exec(addSequenceSQL, sequence.FromBatchNumber, sequence.ToBatchNumber) + return err +} diff --git a/aggregator/db/migrations.go b/aggregator/db/migrations.go index 20e8c29a..221fb145 100644 --- a/aggregator/db/migrations.go +++ b/aggregator/db/migrations.go @@ -4,15 +4,14 @@ import ( "embed" "fmt" + "github.com/0xPolygon/cdk/db" "github.com/0xPolygon/cdk/log" - "github.com/jackc/pgx/v4" - "github.com/jackc/pgx/v4/stdlib" migrate "github.com/rubenv/sql-migrate" ) const ( // AggregatorMigrationName is the name of the migration used to associate with the migrations dir - AggregatorMigrationName = "zkevm-aggregator-db" + AggregatorMigrationName = "aggregator-db" ) var ( @@ -28,38 +27,33 @@ func init() { } // RunMigrationsUp runs migrate-up for the given config. -func RunMigrationsUp(cfg Config, name string) error { +func RunMigrationsUp(dbPath string, name string) error { log.Info("running migrations up") - return runMigrations(cfg, name, migrate.Up) + return runMigrations(dbPath, name, migrate.Up) } // CheckMigrations runs migrate-up for the given config. -func CheckMigrations(cfg Config, name string) error { - return checkMigrations(cfg, name) +func CheckMigrations(dbPath string, name string) error { + return checkMigrations(dbPath, name) } // RunMigrationsDown runs migrate-down for the given config. -func RunMigrationsDown(cfg Config, name string) error { +func RunMigrationsDown(dbPath string, name string) error { log.Info("running migrations down") - return runMigrations(cfg, name, migrate.Down) + return runMigrations(dbPath, name, migrate.Down) } // runMigrations will execute pending migrations if needed to keep // the database updated with the latest changes in either direction, // up or down. -func runMigrations(cfg Config, name string, direction migrate.MigrationDirection) error { - c, err := pgx.ParseConfig(fmt.Sprintf( - "postgres://%s:%s@%s:%s/%s", - cfg.User, cfg.Password, cfg.Host, cfg.Port, cfg.Name, - )) +func runMigrations(dbPath string, name string, direction migrate.MigrationDirection) error { + db, err := db.NewSQLiteDB(dbPath) if err != nil { return err } - db := stdlib.OpenDB(*c) - embedMigration, ok := embedMigrations[name] if !ok { return fmt.Errorf("migration not found with name: %v", name) @@ -70,7 +64,7 @@ func runMigrations(cfg Config, name string, direction migrate.MigrationDirection Root: "migrations", } - nMigrations, err := migrate.Exec(db, "postgres", migrations, direction) + nMigrations, err := migrate.Exec(db, "sqlite3", migrations, direction) if err != nil { return err } @@ -80,17 +74,12 @@ func runMigrations(cfg Config, name string, direction migrate.MigrationDirection return nil } -func checkMigrations(cfg Config, name string) error { - c, err := pgx.ParseConfig(fmt.Sprintf( - "postgres://%s:%s@%s:%s/%s", - cfg.User, cfg.Password, cfg.Host, cfg.Port, cfg.Name, - )) +func checkMigrations(dbPath string, name string) error { + db, err := db.NewSQLiteDB(dbPath) if err != nil { return err } - db := stdlib.OpenDB(*c) - embedMigration, ok := embedMigrations[name] if !ok { return fmt.Errorf("migration not found with name: %v", name) diff --git a/aggregator/db/migrations/0001.sql b/aggregator/db/migrations/0001.sql index 963dbea7..651597a3 100644 --- a/aggregator/db/migrations/0001.sql +++ b/aggregator/db/migrations/0001.sql @@ -1,32 +1,24 @@ -- +migrate Down -DROP SCHEMA IF EXISTS aggregator CASCADE; +DROP TABLE IF EXISTS proof; +DROP TABLE IF EXISTS sequence; -- +migrate Up -CREATE SCHEMA aggregator; - -CREATE TABLE IF NOT EXISTS aggregator.batch ( +CREATE TABLE IF NOT EXISTS proof ( batch_num BIGINT NOT NULL, - batch jsonb NOT NULL, - datastream varchar NOT NULL, - PRIMARY KEY (batch_num) -); - -CREATE TABLE IF NOT EXISTS aggregator.proof ( - batch_num BIGINT NOT NULL REFERENCES aggregator.batch (batch_num) ON DELETE CASCADE, batch_num_final BIGINT NOT NULL, - proof varchar NULL, - proof_id varchar NULL, - input_prover varchar NULL, - prover varchar NULL, - prover_id varchar NULL, - created_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT now(), - updated_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT now(), - generating_since timestamptz NULL, + proof TEXT NULL, + proof_id TEXT NULL, + input_prover TEXT NULL, + prover TEXT NULL, + prover_id TEXT NULL, + created_at BIGINT NOT NULL, + updated_at BIGINT NOT NULL, + generating_since BIGINT DEFAULT NULL, PRIMARY KEY (batch_num, batch_num_final) ); -CREATE TABLE IF NOT EXISTS aggregator.sequence ( - from_batch_num BIGINT NOT NULL REFERENCES aggregator.batch (batch_num) ON DELETE CASCADE, +CREATE TABLE IF NOT EXISTS sequence ( + from_batch_num BIGINT NOT NULL, to_batch_num BIGINT NOT NULL, PRIMARY KEY (from_batch_num) ); diff --git a/aggregator/db/migrations/0002.sql b/aggregator/db/migrations/0002.sql deleted file mode 100644 index e2290e13..00000000 --- a/aggregator/db/migrations/0002.sql +++ /dev/null @@ -1,8 +0,0 @@ --- +migrate Up -DELETE FROM aggregator.batch; -ALTER TABLE aggregator.batch - ADD COLUMN IF NOT EXISTS witness varchar NOT NULL; - --- +migrate Down -ALTER TABLE aggregator.batch - DROP COLUMN IF EXISTS witness; diff --git a/aggregator/db/migrations/0003.sql b/aggregator/db/migrations/0003.sql deleted file mode 100644 index 5351f8e7..00000000 --- a/aggregator/db/migrations/0003.sql +++ /dev/null @@ -1,7 +0,0 @@ --- +migrate Up -ALTER TABLE aggregator.batch - ALTER COLUMN witness DROP NOT NULL; - --- +migrate Down -ALTER TABLE aggregator.batch - ALTER COLUMN witness SET NOT NULL; diff --git a/aggregator/db/migrations/0004.sql b/aggregator/db/migrations/0004.sql deleted file mode 100644 index cb186fc0..00000000 --- a/aggregator/db/migrations/0004.sql +++ /dev/null @@ -1,23 +0,0 @@ --- +migrate Down -CREATE TABLE IF NOT EXISTS aggregator.batch ( - batch_num BIGINT NOT NULL, - batch jsonb NOT NULL, - datastream varchar NOT NULL, - PRIMARY KEY (batch_num) -); - -ALTER TABLE aggregator.proof - ADD CONSTRAINT IF NOT EXISTS proof_batch_num_fkey FOREIGN KEY (batch_num) REFERENCES aggregator.batch (batch_num) ON DELETE CASCADE; - -ALTER TABLE aggregator.sequence - ADD CONSTRAINT IF NOT EXISTS sequence_from_batch_num_fkey FOREIGN KEY (from_batch_num) REFERENCES aggregator.batch (batch_num) ON DELETE CASCADE; - - --- +migrate Up -ALTER TABLE aggregator.proof - DROP CONSTRAINT IF EXISTS proof_batch_num_fkey; - -ALTER TABLE aggregator.sequence - DROP CONSTRAINT IF EXISTS sequence_from_batch_num_fkey; - -DROP TABLE IF EXISTS aggregator.batch; diff --git a/aggregator/db/migrations_test.go b/aggregator/db/migrations_test.go index 0a118c69..317178e9 100644 --- a/aggregator/db/migrations_test.go +++ b/aggregator/db/migrations_test.go @@ -16,3 +16,12 @@ func Test_checkMigrations(t *testing.T) { _, err := migrationSource.FileSystem.ReadFile("migrations/0001.sql") assert.NoError(t, err) } + +func Test_runMigrations(t *testing.T) { + dbPath := "file::memory:?cache=shared" + err := runMigrations(dbPath, AggregatorMigrationName, migrate.Up) + assert.NoError(t, err) + + err = runMigrations(dbPath, AggregatorMigrationName, migrate.Down) + assert.NoError(t, err) +} diff --git a/aggregator/interfaces.go b/aggregator/interfaces.go index f1673c46..5979272d 100644 --- a/aggregator/interfaces.go +++ b/aggregator/interfaces.go @@ -2,10 +2,12 @@ package aggregator import ( "context" + "database/sql" "math/big" ethmanTypes "github.com/0xPolygon/cdk/aggregator/ethmantypes" "github.com/0xPolygon/cdk/aggregator/prover" + "github.com/0xPolygon/cdk/db" "github.com/0xPolygon/cdk/rpc/types" "github.com/0xPolygon/cdk/state" "github.com/0xPolygon/zkevm-ethtx-manager/ethtxmanager" @@ -13,7 +15,6 @@ import ( "github.com/ethereum/go-ethereum/common" ethtypes "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/crypto/kzg4844" - "github.com/jackc/pgx/v4" ) // Consumer interfaces required by the package. @@ -53,19 +54,19 @@ type aggregatorTxProfitabilityChecker interface { } // StateInterface gathers the methods to interact with the state. -type StateInterface interface { - BeginStateTransaction(ctx context.Context) (pgx.Tx, error) - CheckProofContainsCompleteSequences(ctx context.Context, proof *state.Proof, dbTx pgx.Tx) (bool, error) - GetProofReadyToVerify(ctx context.Context, lastVerfiedBatchNumber uint64, dbTx pgx.Tx) (*state.Proof, error) - GetProofsToAggregate(ctx context.Context, dbTx pgx.Tx) (*state.Proof, *state.Proof, error) - AddGeneratedProof(ctx context.Context, proof *state.Proof, dbTx pgx.Tx) error - UpdateGeneratedProof(ctx context.Context, proof *state.Proof, dbTx pgx.Tx) error - DeleteGeneratedProofs(ctx context.Context, batchNumber uint64, batchNumberFinal uint64, dbTx pgx.Tx) error - DeleteUngeneratedProofs(ctx context.Context, dbTx pgx.Tx) error - CleanupGeneratedProofs(ctx context.Context, batchNumber uint64, dbTx pgx.Tx) error - CleanupLockedProofs(ctx context.Context, duration string, dbTx pgx.Tx) (int64, error) - CheckProofExistsForBatch(ctx context.Context, batchNumber uint64, dbTx pgx.Tx) (bool, error) - AddSequence(ctx context.Context, sequence state.Sequence, dbTx pgx.Tx) error +type StorageInterface interface { + BeginTx(ctx context.Context, options *sql.TxOptions) (db.Txer, error) + CheckProofContainsCompleteSequences(ctx context.Context, proof *state.Proof, dbTx db.Txer) (bool, error) + GetProofReadyToVerify(ctx context.Context, lastVerfiedBatchNumber uint64, dbTx db.Txer) (*state.Proof, error) + GetProofsToAggregate(ctx context.Context, dbTx db.Txer) (*state.Proof, *state.Proof, error) + AddGeneratedProof(ctx context.Context, proof *state.Proof, dbTx db.Txer) error + UpdateGeneratedProof(ctx context.Context, proof *state.Proof, dbTx db.Txer) error + DeleteGeneratedProofs(ctx context.Context, batchNumber uint64, batchNumberFinal uint64, dbTx db.Txer) error + DeleteUngeneratedProofs(ctx context.Context, dbTx db.Txer) error + CleanupGeneratedProofs(ctx context.Context, batchNumber uint64, dbTx db.Txer) error + CleanupLockedProofs(ctx context.Context, duration string, dbTx db.Txer) (int64, error) + CheckProofExistsForBatch(ctx context.Context, batchNumber uint64, dbTx db.Txer) (bool, error) + AddSequence(ctx context.Context, sequence state.Sequence, dbTx db.Txer) error } // EthTxManagerClient represents the eth tx manager interface diff --git a/aggregator/mocks/mock_dbtx.go b/aggregator/mocks/mock_dbtx.go deleted file mode 100644 index f870cd57..00000000 --- a/aggregator/mocks/mock_dbtx.go +++ /dev/null @@ -1,350 +0,0 @@ -// Code generated by mockery v2.39.0. DO NOT EDIT. - -package mocks - -import ( - context "context" - - pgconn "github.com/jackc/pgconn" - mock "github.com/stretchr/testify/mock" - - pgx "github.com/jackc/pgx/v4" -) - -// DbTxMock is an autogenerated mock type for the Tx type -type DbTxMock struct { - mock.Mock -} - -// Begin provides a mock function with given fields: ctx -func (_m *DbTxMock) Begin(ctx context.Context) (pgx.Tx, error) { - ret := _m.Called(ctx) - - if len(ret) == 0 { - panic("no return value specified for Begin") - } - - var r0 pgx.Tx - var r1 error - if rf, ok := ret.Get(0).(func(context.Context) (pgx.Tx, error)); ok { - return rf(ctx) - } - if rf, ok := ret.Get(0).(func(context.Context) pgx.Tx); ok { - r0 = rf(ctx) - } else { - if ret.Get(0) != nil { - r0 = ret.Get(0).(pgx.Tx) - } - } - - if rf, ok := ret.Get(1).(func(context.Context) error); ok { - r1 = rf(ctx) - } else { - r1 = ret.Error(1) - } - - return r0, r1 -} - -// BeginFunc provides a mock function with given fields: ctx, f -func (_m *DbTxMock) BeginFunc(ctx context.Context, f func(pgx.Tx) error) error { - ret := _m.Called(ctx, f) - - if len(ret) == 0 { - panic("no return value specified for BeginFunc") - } - - var r0 error - if rf, ok := ret.Get(0).(func(context.Context, func(pgx.Tx) error) error); ok { - r0 = rf(ctx, f) - } else { - r0 = ret.Error(0) - } - - return r0 -} - -// Commit provides a mock function with given fields: ctx -func (_m *DbTxMock) Commit(ctx context.Context) error { - ret := _m.Called(ctx) - - if len(ret) == 0 { - panic("no return value specified for Commit") - } - - var r0 error - if rf, ok := ret.Get(0).(func(context.Context) error); ok { - r0 = rf(ctx) - } else { - r0 = ret.Error(0) - } - - return r0 -} - -// Conn provides a mock function with given fields: -func (_m *DbTxMock) Conn() *pgx.Conn { - ret := _m.Called() - - if len(ret) == 0 { - panic("no return value specified for Conn") - } - - var r0 *pgx.Conn - if rf, ok := ret.Get(0).(func() *pgx.Conn); ok { - r0 = rf() - } else { - if ret.Get(0) != nil { - r0 = ret.Get(0).(*pgx.Conn) - } - } - - return r0 -} - -// CopyFrom provides a mock function with given fields: ctx, tableName, columnNames, rowSrc -func (_m *DbTxMock) CopyFrom(ctx context.Context, tableName pgx.Identifier, columnNames []string, rowSrc pgx.CopyFromSource) (int64, error) { - ret := _m.Called(ctx, tableName, columnNames, rowSrc) - - if len(ret) == 0 { - panic("no return value specified for CopyFrom") - } - - var r0 int64 - var r1 error - if rf, ok := ret.Get(0).(func(context.Context, pgx.Identifier, []string, pgx.CopyFromSource) (int64, error)); ok { - return rf(ctx, tableName, columnNames, rowSrc) - } - if rf, ok := ret.Get(0).(func(context.Context, pgx.Identifier, []string, pgx.CopyFromSource) int64); ok { - r0 = rf(ctx, tableName, columnNames, rowSrc) - } else { - r0 = ret.Get(0).(int64) - } - - if rf, ok := ret.Get(1).(func(context.Context, pgx.Identifier, []string, pgx.CopyFromSource) error); ok { - r1 = rf(ctx, tableName, columnNames, rowSrc) - } else { - r1 = ret.Error(1) - } - - return r0, r1 -} - -// Exec provides a mock function with given fields: ctx, sql, arguments -func (_m *DbTxMock) Exec(ctx context.Context, sql string, arguments ...interface{}) (pgconn.CommandTag, error) { - var _ca []interface{} - _ca = append(_ca, ctx, sql) - _ca = append(_ca, arguments...) - ret := _m.Called(_ca...) - - if len(ret) == 0 { - panic("no return value specified for Exec") - } - - var r0 pgconn.CommandTag - var r1 error - if rf, ok := ret.Get(0).(func(context.Context, string, ...interface{}) (pgconn.CommandTag, error)); ok { - return rf(ctx, sql, arguments...) - } - if rf, ok := ret.Get(0).(func(context.Context, string, ...interface{}) pgconn.CommandTag); ok { - r0 = rf(ctx, sql, arguments...) - } else { - if ret.Get(0) != nil { - r0 = ret.Get(0).(pgconn.CommandTag) - } - } - - if rf, ok := ret.Get(1).(func(context.Context, string, ...interface{}) error); ok { - r1 = rf(ctx, sql, arguments...) - } else { - r1 = ret.Error(1) - } - - return r0, r1 -} - -// LargeObjects provides a mock function with given fields: -func (_m *DbTxMock) LargeObjects() pgx.LargeObjects { - ret := _m.Called() - - if len(ret) == 0 { - panic("no return value specified for LargeObjects") - } - - var r0 pgx.LargeObjects - if rf, ok := ret.Get(0).(func() pgx.LargeObjects); ok { - r0 = rf() - } else { - r0 = ret.Get(0).(pgx.LargeObjects) - } - - return r0 -} - -// Prepare provides a mock function with given fields: ctx, name, sql -func (_m *DbTxMock) Prepare(ctx context.Context, name string, sql string) (*pgconn.StatementDescription, error) { - ret := _m.Called(ctx, name, sql) - - if len(ret) == 0 { - panic("no return value specified for Prepare") - } - - var r0 *pgconn.StatementDescription - var r1 error - if rf, ok := ret.Get(0).(func(context.Context, string, string) (*pgconn.StatementDescription, error)); ok { - return rf(ctx, name, sql) - } - if rf, ok := ret.Get(0).(func(context.Context, string, string) *pgconn.StatementDescription); ok { - r0 = rf(ctx, name, sql) - } else { - if ret.Get(0) != nil { - r0 = ret.Get(0).(*pgconn.StatementDescription) - } - } - - if rf, ok := ret.Get(1).(func(context.Context, string, string) error); ok { - r1 = rf(ctx, name, sql) - } else { - r1 = ret.Error(1) - } - - return r0, r1 -} - -// Query provides a mock function with given fields: ctx, sql, args -func (_m *DbTxMock) Query(ctx context.Context, sql string, args ...interface{}) (pgx.Rows, error) { - var _ca []interface{} - _ca = append(_ca, ctx, sql) - _ca = append(_ca, args...) - ret := _m.Called(_ca...) - - if len(ret) == 0 { - panic("no return value specified for Query") - } - - var r0 pgx.Rows - var r1 error - if rf, ok := ret.Get(0).(func(context.Context, string, ...interface{}) (pgx.Rows, error)); ok { - return rf(ctx, sql, args...) - } - if rf, ok := ret.Get(0).(func(context.Context, string, ...interface{}) pgx.Rows); ok { - r0 = rf(ctx, sql, args...) - } else { - if ret.Get(0) != nil { - r0 = ret.Get(0).(pgx.Rows) - } - } - - if rf, ok := ret.Get(1).(func(context.Context, string, ...interface{}) error); ok { - r1 = rf(ctx, sql, args...) - } else { - r1 = ret.Error(1) - } - - return r0, r1 -} - -// QueryFunc provides a mock function with given fields: ctx, sql, args, scans, f -func (_m *DbTxMock) QueryFunc(ctx context.Context, sql string, args []interface{}, scans []interface{}, f func(pgx.QueryFuncRow) error) (pgconn.CommandTag, error) { - ret := _m.Called(ctx, sql, args, scans, f) - - if len(ret) == 0 { - panic("no return value specified for QueryFunc") - } - - var r0 pgconn.CommandTag - var r1 error - if rf, ok := ret.Get(0).(func(context.Context, string, []interface{}, []interface{}, func(pgx.QueryFuncRow) error) (pgconn.CommandTag, error)); ok { - return rf(ctx, sql, args, scans, f) - } - if rf, ok := ret.Get(0).(func(context.Context, string, []interface{}, []interface{}, func(pgx.QueryFuncRow) error) pgconn.CommandTag); ok { - r0 = rf(ctx, sql, args, scans, f) - } else { - if ret.Get(0) != nil { - r0 = ret.Get(0).(pgconn.CommandTag) - } - } - - if rf, ok := ret.Get(1).(func(context.Context, string, []interface{}, []interface{}, func(pgx.QueryFuncRow) error) error); ok { - r1 = rf(ctx, sql, args, scans, f) - } else { - r1 = ret.Error(1) - } - - return r0, r1 -} - -// QueryRow provides a mock function with given fields: ctx, sql, args -func (_m *DbTxMock) QueryRow(ctx context.Context, sql string, args ...interface{}) pgx.Row { - var _ca []interface{} - _ca = append(_ca, ctx, sql) - _ca = append(_ca, args...) - ret := _m.Called(_ca...) - - if len(ret) == 0 { - panic("no return value specified for QueryRow") - } - - var r0 pgx.Row - if rf, ok := ret.Get(0).(func(context.Context, string, ...interface{}) pgx.Row); ok { - r0 = rf(ctx, sql, args...) - } else { - if ret.Get(0) != nil { - r0 = ret.Get(0).(pgx.Row) - } - } - - return r0 -} - -// Rollback provides a mock function with given fields: ctx -func (_m *DbTxMock) Rollback(ctx context.Context) error { - ret := _m.Called(ctx) - - if len(ret) == 0 { - panic("no return value specified for Rollback") - } - - var r0 error - if rf, ok := ret.Get(0).(func(context.Context) error); ok { - r0 = rf(ctx) - } else { - r0 = ret.Error(0) - } - - return r0 -} - -// SendBatch provides a mock function with given fields: ctx, b -func (_m *DbTxMock) SendBatch(ctx context.Context, b *pgx.Batch) pgx.BatchResults { - ret := _m.Called(ctx, b) - - if len(ret) == 0 { - panic("no return value specified for SendBatch") - } - - var r0 pgx.BatchResults - if rf, ok := ret.Get(0).(func(context.Context, *pgx.Batch) pgx.BatchResults); ok { - r0 = rf(ctx, b) - } else { - if ret.Get(0) != nil { - r0 = ret.Get(0).(pgx.BatchResults) - } - } - - return r0 -} - -// NewDbTxMock creates a new instance of DbTxMock. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. -// The first argument is typically a *testing.T value. -func NewDbTxMock(t interface { - mock.TestingT - Cleanup(func()) -}) *DbTxMock { - mock := &DbTxMock{} - mock.Mock.Test(t) - - t.Cleanup(func() { mock.AssertExpectations(t) }) - - return mock -} diff --git a/aggregator/mocks/mock_state.go b/aggregator/mocks/mock_storage.go similarity index 54% rename from aggregator/mocks/mock_state.go rename to aggregator/mocks/mock_storage.go index 74c9021b..405cba46 100644 --- a/aggregator/mocks/mock_state.go +++ b/aggregator/mocks/mock_storage.go @@ -5,19 +5,21 @@ package mocks import ( context "context" - pgx "github.com/jackc/pgx/v4" + db "github.com/0xPolygon/cdk/db" mock "github.com/stretchr/testify/mock" + sql "database/sql" + state "github.com/0xPolygon/cdk/state" ) -// StateInterfaceMock is an autogenerated mock type for the StateInterface type -type StateInterfaceMock struct { +// StorageInterfaceMock is an autogenerated mock type for the StorageInterface type +type StorageInterfaceMock struct { mock.Mock } // AddGeneratedProof provides a mock function with given fields: ctx, proof, dbTx -func (_m *StateInterfaceMock) AddGeneratedProof(ctx context.Context, proof *state.Proof, dbTx pgx.Tx) error { +func (_m *StorageInterfaceMock) AddGeneratedProof(ctx context.Context, proof *state.Proof, dbTx db.Txer) error { ret := _m.Called(ctx, proof, dbTx) if len(ret) == 0 { @@ -25,7 +27,7 @@ func (_m *StateInterfaceMock) AddGeneratedProof(ctx context.Context, proof *stat } var r0 error - if rf, ok := ret.Get(0).(func(context.Context, *state.Proof, pgx.Tx) error); ok { + if rf, ok := ret.Get(0).(func(context.Context, *state.Proof, db.Txer) error); ok { r0 = rf(ctx, proof, dbTx) } else { r0 = ret.Error(0) @@ -35,7 +37,7 @@ func (_m *StateInterfaceMock) AddGeneratedProof(ctx context.Context, proof *stat } // AddSequence provides a mock function with given fields: ctx, sequence, dbTx -func (_m *StateInterfaceMock) AddSequence(ctx context.Context, sequence state.Sequence, dbTx pgx.Tx) error { +func (_m *StorageInterfaceMock) AddSequence(ctx context.Context, sequence state.Sequence, dbTx db.Txer) error { ret := _m.Called(ctx, sequence, dbTx) if len(ret) == 0 { @@ -43,7 +45,7 @@ func (_m *StateInterfaceMock) AddSequence(ctx context.Context, sequence state.Se } var r0 error - if rf, ok := ret.Get(0).(func(context.Context, state.Sequence, pgx.Tx) error); ok { + if rf, ok := ret.Get(0).(func(context.Context, state.Sequence, db.Txer) error); ok { r0 = rf(ctx, sequence, dbTx) } else { r0 = ret.Error(0) @@ -52,29 +54,29 @@ func (_m *StateInterfaceMock) AddSequence(ctx context.Context, sequence state.Se return r0 } -// BeginStateTransaction provides a mock function with given fields: ctx -func (_m *StateInterfaceMock) BeginStateTransaction(ctx context.Context) (pgx.Tx, error) { - ret := _m.Called(ctx) +// BeginTx provides a mock function with given fields: ctx, options +func (_m *StorageInterfaceMock) BeginTx(ctx context.Context, options *sql.TxOptions) (db.Txer, error) { + ret := _m.Called(ctx, options) if len(ret) == 0 { - panic("no return value specified for BeginStateTransaction") + panic("no return value specified for BeginTx") } - var r0 pgx.Tx + var r0 db.Txer var r1 error - if rf, ok := ret.Get(0).(func(context.Context) (pgx.Tx, error)); ok { - return rf(ctx) + if rf, ok := ret.Get(0).(func(context.Context, *sql.TxOptions) (db.Txer, error)); ok { + return rf(ctx, options) } - if rf, ok := ret.Get(0).(func(context.Context) pgx.Tx); ok { - r0 = rf(ctx) + if rf, ok := ret.Get(0).(func(context.Context, *sql.TxOptions) db.Txer); ok { + r0 = rf(ctx, options) } else { if ret.Get(0) != nil { - r0 = ret.Get(0).(pgx.Tx) + r0 = ret.Get(0).(db.Txer) } } - if rf, ok := ret.Get(1).(func(context.Context) error); ok { - r1 = rf(ctx) + if rf, ok := ret.Get(1).(func(context.Context, *sql.TxOptions) error); ok { + r1 = rf(ctx, options) } else { r1 = ret.Error(1) } @@ -83,7 +85,7 @@ func (_m *StateInterfaceMock) BeginStateTransaction(ctx context.Context) (pgx.Tx } // CheckProofContainsCompleteSequences provides a mock function with given fields: ctx, proof, dbTx -func (_m *StateInterfaceMock) CheckProofContainsCompleteSequences(ctx context.Context, proof *state.Proof, dbTx pgx.Tx) (bool, error) { +func (_m *StorageInterfaceMock) CheckProofContainsCompleteSequences(ctx context.Context, proof *state.Proof, dbTx db.Txer) (bool, error) { ret := _m.Called(ctx, proof, dbTx) if len(ret) == 0 { @@ -92,16 +94,16 @@ func (_m *StateInterfaceMock) CheckProofContainsCompleteSequences(ctx context.Co var r0 bool var r1 error - if rf, ok := ret.Get(0).(func(context.Context, *state.Proof, pgx.Tx) (bool, error)); ok { + if rf, ok := ret.Get(0).(func(context.Context, *state.Proof, db.Txer) (bool, error)); ok { return rf(ctx, proof, dbTx) } - if rf, ok := ret.Get(0).(func(context.Context, *state.Proof, pgx.Tx) bool); ok { + if rf, ok := ret.Get(0).(func(context.Context, *state.Proof, db.Txer) bool); ok { r0 = rf(ctx, proof, dbTx) } else { r0 = ret.Get(0).(bool) } - if rf, ok := ret.Get(1).(func(context.Context, *state.Proof, pgx.Tx) error); ok { + if rf, ok := ret.Get(1).(func(context.Context, *state.Proof, db.Txer) error); ok { r1 = rf(ctx, proof, dbTx) } else { r1 = ret.Error(1) @@ -111,7 +113,7 @@ func (_m *StateInterfaceMock) CheckProofContainsCompleteSequences(ctx context.Co } // CheckProofExistsForBatch provides a mock function with given fields: ctx, batchNumber, dbTx -func (_m *StateInterfaceMock) CheckProofExistsForBatch(ctx context.Context, batchNumber uint64, dbTx pgx.Tx) (bool, error) { +func (_m *StorageInterfaceMock) CheckProofExistsForBatch(ctx context.Context, batchNumber uint64, dbTx db.Txer) (bool, error) { ret := _m.Called(ctx, batchNumber, dbTx) if len(ret) == 0 { @@ -120,16 +122,16 @@ func (_m *StateInterfaceMock) CheckProofExistsForBatch(ctx context.Context, batc var r0 bool var r1 error - if rf, ok := ret.Get(0).(func(context.Context, uint64, pgx.Tx) (bool, error)); ok { + if rf, ok := ret.Get(0).(func(context.Context, uint64, db.Txer) (bool, error)); ok { return rf(ctx, batchNumber, dbTx) } - if rf, ok := ret.Get(0).(func(context.Context, uint64, pgx.Tx) bool); ok { + if rf, ok := ret.Get(0).(func(context.Context, uint64, db.Txer) bool); ok { r0 = rf(ctx, batchNumber, dbTx) } else { r0 = ret.Get(0).(bool) } - if rf, ok := ret.Get(1).(func(context.Context, uint64, pgx.Tx) error); ok { + if rf, ok := ret.Get(1).(func(context.Context, uint64, db.Txer) error); ok { r1 = rf(ctx, batchNumber, dbTx) } else { r1 = ret.Error(1) @@ -139,7 +141,7 @@ func (_m *StateInterfaceMock) CheckProofExistsForBatch(ctx context.Context, batc } // CleanupGeneratedProofs provides a mock function with given fields: ctx, batchNumber, dbTx -func (_m *StateInterfaceMock) CleanupGeneratedProofs(ctx context.Context, batchNumber uint64, dbTx pgx.Tx) error { +func (_m *StorageInterfaceMock) CleanupGeneratedProofs(ctx context.Context, batchNumber uint64, dbTx db.Txer) error { ret := _m.Called(ctx, batchNumber, dbTx) if len(ret) == 0 { @@ -147,7 +149,7 @@ func (_m *StateInterfaceMock) CleanupGeneratedProofs(ctx context.Context, batchN } var r0 error - if rf, ok := ret.Get(0).(func(context.Context, uint64, pgx.Tx) error); ok { + if rf, ok := ret.Get(0).(func(context.Context, uint64, db.Txer) error); ok { r0 = rf(ctx, batchNumber, dbTx) } else { r0 = ret.Error(0) @@ -157,7 +159,7 @@ func (_m *StateInterfaceMock) CleanupGeneratedProofs(ctx context.Context, batchN } // CleanupLockedProofs provides a mock function with given fields: ctx, duration, dbTx -func (_m *StateInterfaceMock) CleanupLockedProofs(ctx context.Context, duration string, dbTx pgx.Tx) (int64, error) { +func (_m *StorageInterfaceMock) CleanupLockedProofs(ctx context.Context, duration string, dbTx db.Txer) (int64, error) { ret := _m.Called(ctx, duration, dbTx) if len(ret) == 0 { @@ -166,16 +168,16 @@ func (_m *StateInterfaceMock) CleanupLockedProofs(ctx context.Context, duration var r0 int64 var r1 error - if rf, ok := ret.Get(0).(func(context.Context, string, pgx.Tx) (int64, error)); ok { + if rf, ok := ret.Get(0).(func(context.Context, string, db.Txer) (int64, error)); ok { return rf(ctx, duration, dbTx) } - if rf, ok := ret.Get(0).(func(context.Context, string, pgx.Tx) int64); ok { + if rf, ok := ret.Get(0).(func(context.Context, string, db.Txer) int64); ok { r0 = rf(ctx, duration, dbTx) } else { r0 = ret.Get(0).(int64) } - if rf, ok := ret.Get(1).(func(context.Context, string, pgx.Tx) error); ok { + if rf, ok := ret.Get(1).(func(context.Context, string, db.Txer) error); ok { r1 = rf(ctx, duration, dbTx) } else { r1 = ret.Error(1) @@ -185,7 +187,7 @@ func (_m *StateInterfaceMock) CleanupLockedProofs(ctx context.Context, duration } // DeleteGeneratedProofs provides a mock function with given fields: ctx, batchNumber, batchNumberFinal, dbTx -func (_m *StateInterfaceMock) DeleteGeneratedProofs(ctx context.Context, batchNumber uint64, batchNumberFinal uint64, dbTx pgx.Tx) error { +func (_m *StorageInterfaceMock) DeleteGeneratedProofs(ctx context.Context, batchNumber uint64, batchNumberFinal uint64, dbTx db.Txer) error { ret := _m.Called(ctx, batchNumber, batchNumberFinal, dbTx) if len(ret) == 0 { @@ -193,7 +195,7 @@ func (_m *StateInterfaceMock) DeleteGeneratedProofs(ctx context.Context, batchNu } var r0 error - if rf, ok := ret.Get(0).(func(context.Context, uint64, uint64, pgx.Tx) error); ok { + if rf, ok := ret.Get(0).(func(context.Context, uint64, uint64, db.Txer) error); ok { r0 = rf(ctx, batchNumber, batchNumberFinal, dbTx) } else { r0 = ret.Error(0) @@ -203,7 +205,7 @@ func (_m *StateInterfaceMock) DeleteGeneratedProofs(ctx context.Context, batchNu } // DeleteUngeneratedProofs provides a mock function with given fields: ctx, dbTx -func (_m *StateInterfaceMock) DeleteUngeneratedProofs(ctx context.Context, dbTx pgx.Tx) error { +func (_m *StorageInterfaceMock) DeleteUngeneratedProofs(ctx context.Context, dbTx db.Txer) error { ret := _m.Called(ctx, dbTx) if len(ret) == 0 { @@ -211,7 +213,7 @@ func (_m *StateInterfaceMock) DeleteUngeneratedProofs(ctx context.Context, dbTx } var r0 error - if rf, ok := ret.Get(0).(func(context.Context, pgx.Tx) error); ok { + if rf, ok := ret.Get(0).(func(context.Context, db.Txer) error); ok { r0 = rf(ctx, dbTx) } else { r0 = ret.Error(0) @@ -221,7 +223,7 @@ func (_m *StateInterfaceMock) DeleteUngeneratedProofs(ctx context.Context, dbTx } // GetProofReadyToVerify provides a mock function with given fields: ctx, lastVerfiedBatchNumber, dbTx -func (_m *StateInterfaceMock) GetProofReadyToVerify(ctx context.Context, lastVerfiedBatchNumber uint64, dbTx pgx.Tx) (*state.Proof, error) { +func (_m *StorageInterfaceMock) GetProofReadyToVerify(ctx context.Context, lastVerfiedBatchNumber uint64, dbTx db.Txer) (*state.Proof, error) { ret := _m.Called(ctx, lastVerfiedBatchNumber, dbTx) if len(ret) == 0 { @@ -230,10 +232,10 @@ func (_m *StateInterfaceMock) GetProofReadyToVerify(ctx context.Context, lastVer var r0 *state.Proof var r1 error - if rf, ok := ret.Get(0).(func(context.Context, uint64, pgx.Tx) (*state.Proof, error)); ok { + if rf, ok := ret.Get(0).(func(context.Context, uint64, db.Txer) (*state.Proof, error)); ok { return rf(ctx, lastVerfiedBatchNumber, dbTx) } - if rf, ok := ret.Get(0).(func(context.Context, uint64, pgx.Tx) *state.Proof); ok { + if rf, ok := ret.Get(0).(func(context.Context, uint64, db.Txer) *state.Proof); ok { r0 = rf(ctx, lastVerfiedBatchNumber, dbTx) } else { if ret.Get(0) != nil { @@ -241,7 +243,7 @@ func (_m *StateInterfaceMock) GetProofReadyToVerify(ctx context.Context, lastVer } } - if rf, ok := ret.Get(1).(func(context.Context, uint64, pgx.Tx) error); ok { + if rf, ok := ret.Get(1).(func(context.Context, uint64, db.Txer) error); ok { r1 = rf(ctx, lastVerfiedBatchNumber, dbTx) } else { r1 = ret.Error(1) @@ -251,7 +253,7 @@ func (_m *StateInterfaceMock) GetProofReadyToVerify(ctx context.Context, lastVer } // GetProofsToAggregate provides a mock function with given fields: ctx, dbTx -func (_m *StateInterfaceMock) GetProofsToAggregate(ctx context.Context, dbTx pgx.Tx) (*state.Proof, *state.Proof, error) { +func (_m *StorageInterfaceMock) GetProofsToAggregate(ctx context.Context, dbTx db.Txer) (*state.Proof, *state.Proof, error) { ret := _m.Called(ctx, dbTx) if len(ret) == 0 { @@ -261,10 +263,10 @@ func (_m *StateInterfaceMock) GetProofsToAggregate(ctx context.Context, dbTx pgx var r0 *state.Proof var r1 *state.Proof var r2 error - if rf, ok := ret.Get(0).(func(context.Context, pgx.Tx) (*state.Proof, *state.Proof, error)); ok { + if rf, ok := ret.Get(0).(func(context.Context, db.Txer) (*state.Proof, *state.Proof, error)); ok { return rf(ctx, dbTx) } - if rf, ok := ret.Get(0).(func(context.Context, pgx.Tx) *state.Proof); ok { + if rf, ok := ret.Get(0).(func(context.Context, db.Txer) *state.Proof); ok { r0 = rf(ctx, dbTx) } else { if ret.Get(0) != nil { @@ -272,7 +274,7 @@ func (_m *StateInterfaceMock) GetProofsToAggregate(ctx context.Context, dbTx pgx } } - if rf, ok := ret.Get(1).(func(context.Context, pgx.Tx) *state.Proof); ok { + if rf, ok := ret.Get(1).(func(context.Context, db.Txer) *state.Proof); ok { r1 = rf(ctx, dbTx) } else { if ret.Get(1) != nil { @@ -280,7 +282,7 @@ func (_m *StateInterfaceMock) GetProofsToAggregate(ctx context.Context, dbTx pgx } } - if rf, ok := ret.Get(2).(func(context.Context, pgx.Tx) error); ok { + if rf, ok := ret.Get(2).(func(context.Context, db.Txer) error); ok { r2 = rf(ctx, dbTx) } else { r2 = ret.Error(2) @@ -290,7 +292,7 @@ func (_m *StateInterfaceMock) GetProofsToAggregate(ctx context.Context, dbTx pgx } // UpdateGeneratedProof provides a mock function with given fields: ctx, proof, dbTx -func (_m *StateInterfaceMock) UpdateGeneratedProof(ctx context.Context, proof *state.Proof, dbTx pgx.Tx) error { +func (_m *StorageInterfaceMock) UpdateGeneratedProof(ctx context.Context, proof *state.Proof, dbTx db.Txer) error { ret := _m.Called(ctx, proof, dbTx) if len(ret) == 0 { @@ -298,7 +300,7 @@ func (_m *StateInterfaceMock) UpdateGeneratedProof(ctx context.Context, proof *s } var r0 error - if rf, ok := ret.Get(0).(func(context.Context, *state.Proof, pgx.Tx) error); ok { + if rf, ok := ret.Get(0).(func(context.Context, *state.Proof, db.Txer) error); ok { r0 = rf(ctx, proof, dbTx) } else { r0 = ret.Error(0) @@ -307,13 +309,13 @@ func (_m *StateInterfaceMock) UpdateGeneratedProof(ctx context.Context, proof *s return r0 } -// NewStateInterfaceMock creates a new instance of StateInterfaceMock. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. +// NewStorageInterfaceMock creates a new instance of StorageInterfaceMock. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. // The first argument is typically a *testing.T value. -func NewStateInterfaceMock(t interface { +func NewStorageInterfaceMock(t interface { mock.TestingT Cleanup(func()) -}) *StateInterfaceMock { - mock := &StateInterfaceMock{} +}) *StorageInterfaceMock { + mock := &StorageInterfaceMock{} mock.Mock.Test(t) t.Cleanup(func() { mock.AssertExpectations(t) }) diff --git a/aggregator/mocks/mock_txer.go b/aggregator/mocks/mock_txer.go new file mode 100644 index 00000000..1de07124 --- /dev/null +++ b/aggregator/mocks/mock_txer.go @@ -0,0 +1,163 @@ +// Code generated by mockery v2.39.0. DO NOT EDIT. + +package mocks + +import ( + sql "database/sql" + + mock "github.com/stretchr/testify/mock" +) + +// TxerMock is an autogenerated mock type for the Txer type +type TxerMock struct { + mock.Mock +} + +// AddCommitCallback provides a mock function with given fields: cb +func (_m *TxerMock) AddCommitCallback(cb func()) { + _m.Called(cb) +} + +// AddRollbackCallback provides a mock function with given fields: cb +func (_m *TxerMock) AddRollbackCallback(cb func()) { + _m.Called(cb) +} + +// Commit provides a mock function with given fields: +func (_m *TxerMock) Commit() error { + ret := _m.Called() + + if len(ret) == 0 { + panic("no return value specified for Commit") + } + + var r0 error + if rf, ok := ret.Get(0).(func() error); ok { + r0 = rf() + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// Exec provides a mock function with given fields: query, args +func (_m *TxerMock) Exec(query string, args ...interface{}) (sql.Result, error) { + var _ca []interface{} + _ca = append(_ca, query) + _ca = append(_ca, args...) + ret := _m.Called(_ca...) + + if len(ret) == 0 { + panic("no return value specified for Exec") + } + + var r0 sql.Result + var r1 error + if rf, ok := ret.Get(0).(func(string, ...interface{}) (sql.Result, error)); ok { + return rf(query, args...) + } + if rf, ok := ret.Get(0).(func(string, ...interface{}) sql.Result); ok { + r0 = rf(query, args...) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(sql.Result) + } + } + + if rf, ok := ret.Get(1).(func(string, ...interface{}) error); ok { + r1 = rf(query, args...) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// Query provides a mock function with given fields: query, args +func (_m *TxerMock) Query(query string, args ...interface{}) (*sql.Rows, error) { + var _ca []interface{} + _ca = append(_ca, query) + _ca = append(_ca, args...) + ret := _m.Called(_ca...) + + if len(ret) == 0 { + panic("no return value specified for Query") + } + + var r0 *sql.Rows + var r1 error + if rf, ok := ret.Get(0).(func(string, ...interface{}) (*sql.Rows, error)); ok { + return rf(query, args...) + } + if rf, ok := ret.Get(0).(func(string, ...interface{}) *sql.Rows); ok { + r0 = rf(query, args...) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*sql.Rows) + } + } + + if rf, ok := ret.Get(1).(func(string, ...interface{}) error); ok { + r1 = rf(query, args...) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// QueryRow provides a mock function with given fields: query, args +func (_m *TxerMock) QueryRow(query string, args ...interface{}) *sql.Row { + var _ca []interface{} + _ca = append(_ca, query) + _ca = append(_ca, args...) + ret := _m.Called(_ca...) + + if len(ret) == 0 { + panic("no return value specified for QueryRow") + } + + var r0 *sql.Row + if rf, ok := ret.Get(0).(func(string, ...interface{}) *sql.Row); ok { + r0 = rf(query, args...) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*sql.Row) + } + } + + return r0 +} + +// Rollback provides a mock function with given fields: +func (_m *TxerMock) Rollback() error { + ret := _m.Called() + + if len(ret) == 0 { + panic("no return value specified for Rollback") + } + + var r0 error + if rf, ok := ret.Get(0).(func() error); ok { + r0 = rf() + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// NewTxerMock creates a new instance of TxerMock. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. +// The first argument is typically a *testing.T value. +func NewTxerMock(t interface { + mock.TestingT + Cleanup(func()) +}) *TxerMock { + mock := &TxerMock{} + mock.Mock.Test(t) + + t.Cleanup(func() { mock.AssertExpectations(t) }) + + return mock +} diff --git a/aggregator/profitabilitychecker.go b/aggregator/profitabilitychecker.go deleted file mode 100644 index dc91a21e..00000000 --- a/aggregator/profitabilitychecker.go +++ /dev/null @@ -1,92 +0,0 @@ -package aggregator - -import ( - "context" - "math/big" - "time" -) - -// TxProfitabilityCheckerType checks profitability of batch validation -type TxProfitabilityCheckerType string - -const ( - // ProfitabilityBase checks pol collateral with min reward - ProfitabilityBase = "base" - // ProfitabilityAcceptAll validate batch anyway and don't check anything - ProfitabilityAcceptAll = "acceptall" -) - -// TxProfitabilityCheckerBase checks pol collateral with min reward -type TxProfitabilityCheckerBase struct { - State StateInterface - IntervalAfterWhichBatchSentAnyway time.Duration - MinReward *big.Int -} - -// NewTxProfitabilityCheckerBase init base tx profitability checker -func NewTxProfitabilityCheckerBase( - state StateInterface, interval time.Duration, minReward *big.Int, -) *TxProfitabilityCheckerBase { - return &TxProfitabilityCheckerBase{ - State: state, - IntervalAfterWhichBatchSentAnyway: interval, - MinReward: minReward, - } -} - -// IsProfitable checks pol collateral with min reward -func (pc *TxProfitabilityCheckerBase) IsProfitable(ctx context.Context, polCollateral *big.Int) (bool, error) { - // if pc.IntervalAfterWhichBatchSentAnyway != 0 { - // ok, err := isConsolidatedBatchAppeared(ctx, pc.State, pc.IntervalAfterWhichBatchSentAnyway) - // if err != nil { - // return false, err - // } - // if ok { - // return true, nil - // } - // } - return polCollateral.Cmp(pc.MinReward) >= 0, nil -} - -// TxProfitabilityCheckerAcceptAll validate batch anyway and don't check anything -type TxProfitabilityCheckerAcceptAll struct { - State StateInterface - IntervalAfterWhichBatchSentAnyway time.Duration -} - -// NewTxProfitabilityCheckerAcceptAll init tx profitability checker that accept all txs -func NewTxProfitabilityCheckerAcceptAll(state StateInterface, interval time.Duration) *TxProfitabilityCheckerAcceptAll { - return &TxProfitabilityCheckerAcceptAll{ - State: state, - IntervalAfterWhichBatchSentAnyway: interval, - } -} - -// IsProfitable validate batch anyway and don't check anything -func (pc *TxProfitabilityCheckerAcceptAll) IsProfitable(ctx context.Context, polCollateral *big.Int) (bool, error) { - // if pc.IntervalAfterWhichBatchSentAnyway != 0 { - // ok, err := isConsolidatedBatchAppeared(ctx, pc.State, pc.IntervalAfterWhichBatchSentAnyway) - // if err != nil { - // return false, err - // } - // if ok { - // return true, nil - // } - // } - return true, nil -} - -// TODO: now it's impossible to check, when batch got consolidated, bcs it's not saved -// func isConsolidatedBatchAppeared(ctx context.Context, state StateInterface, -// intervalAfterWhichBatchConsolidatedAnyway time.Duration) (bool, error) { -// batch, err := state.GetLastVerifiedBatch(ctx, nil) -// if err != nil { -// return false, fmt.Errorf("failed to get last verified batch, err: %v", err) -// } -// interval := intervalAfterWhichBatchConsolidatedAnyway * time.Minute -// if batch..Before(time.Now().Add(-interval)) { -// return true, nil -// } -// -// return false, err -// } diff --git a/cmd/run.go b/cmd/run.go index 727533e8..cff1188b 100644 --- a/cmd/run.go +++ b/cmd/run.go @@ -34,15 +34,12 @@ import ( "github.com/0xPolygon/cdk/rpc" "github.com/0xPolygon/cdk/sequencesender" "github.com/0xPolygon/cdk/sequencesender/txbuilder" - "github.com/0xPolygon/cdk/state" - "github.com/0xPolygon/cdk/state/pgstatestorage" "github.com/0xPolygon/cdk/translator" ethtxman "github.com/0xPolygon/zkevm-ethtx-manager/etherman" "github.com/0xPolygon/zkevm-ethtx-manager/etherman/etherscan" "github.com/0xPolygon/zkevm-ethtx-manager/ethtxmanager" ethtxlog "github.com/0xPolygon/zkevm-ethtx-manager/log" "github.com/ethereum/go-ethereum/ethclient" - "github.com/jackc/pgx/v4/pgxpool" "github.com/urfave/cli/v2" ) @@ -180,17 +177,8 @@ func createAggregator(ctx context.Context, c config.Config, runMigrations bool) logger := log.WithFields("module", cdkcommon.AGGREGATOR) // Migrations if runMigrations { - logger.Infof( - "Running DB migrations host: %s:%s db:%s user:%s", - c.Aggregator.DB.Host, c.Aggregator.DB.Port, c.Aggregator.DB.Name, c.Aggregator.DB.User, - ) - runAggregatorMigrations(c.Aggregator.DB) - } - - // DB - stateSQLDB, err := db.NewSQLDB(logger, c.Aggregator.DB) - if err != nil { - logger.Fatal(err) + logger.Infof("Running DB migrations. File %s", c.Aggregator.DBPath) + runAggregatorMigrations(c.Aggregator.DBPath) } etherman, err := newEtherman(c) @@ -209,9 +197,7 @@ func createAggregator(ctx context.Context, c config.Config, runMigrations bool) c.Aggregator.ChainID = l2ChainID } - st := newState(&c, c.Aggregator.ChainID, stateSQLDB) - - aggregator, err := aggregator.New(ctx, c.Aggregator, logger, st, etherman) + aggregator, err := aggregator.New(ctx, c.Aggregator, logger, etherman) if err != nil { logger.Fatal(err) } @@ -432,13 +418,13 @@ func newDataAvailability(c config.Config, etherman *etherman.Client) (*dataavail return dataavailability.New(daBackend) } -func runAggregatorMigrations(c db.Config) { - runMigrations(c, db.AggregatorMigrationName) +func runAggregatorMigrations(dbPath string) { + runMigrations(dbPath, db.AggregatorMigrationName) } -func runMigrations(c db.Config, name string) { +func runMigrations(dbPath string, name string) { log.Infof("running migrations for %v", name) - err := db.RunMigrationsUp(c, name) + err := db.RunMigrationsUp(dbPath, name) if err != nil { log.Fatal(err) } @@ -484,19 +470,6 @@ func waitSignal(cancelFuncs []context.CancelFunc) { } } -func newState(c *config.Config, l2ChainID uint64, sqlDB *pgxpool.Pool) *state.State { - stateCfg := state.Config{ - DB: c.Aggregator.DB, - ChainID: l2ChainID, - } - - stateDB := pgstatestorage.NewPostgresStorage(stateCfg, sqlDB) - - st := state.NewState(stateCfg, stateDB) - - return st -} - func newReorgDetector( cfg *reorgdetector.Config, client *ethclient.Client, diff --git a/config/default.go b/config/default.go index 61b099c8..316cfb76 100644 --- a/config/default.go +++ b/config/default.go @@ -137,17 +137,10 @@ SettlementBackend = "l1" AggLayerTxTimeout = "5m" AggLayerURL = "{{AggLayerURL}}" SyncModeOnlyEnabled = false +DBPath = "{{PathRWData}}/aggregator_db.sqlite" [Aggregator.SequencerPrivateKey] Path = "{{SequencerPrivateKeyPath}}" Password = "{{SequencerPrivateKeyPassword}}" - [Aggregator.DB] - Name = "aggregator_db" - User = "aggregator_user" - Password = "aggregator_password" - Host = "cdk-aggregator-db" - Port = "5432" - EnableLog = false - MaxConns = 200 [Aggregator.Log] Environment ="{{Log.Environment}}" # "production" or "development" Level = "{{Log.Level}}" diff --git a/scripts/local_config b/scripts/local_config index 5830b6e6..ca25dbbb 100755 --- a/scripts/local_config +++ b/scripts/local_config @@ -194,9 +194,6 @@ function export_values_of_cdk_node_config(){ if [ $? -ne 0 ]; then export_key_from_toml_file zkevm_l2_agglayer_address $_CDK_CONFIG_FILE "." SenderProofToL1Addr fi - export_key_from_toml_file_or_fatal aggregator_db_name $_CDK_CONFIG_FILE Aggregator.DB Name - export_key_from_toml_file_or_fatal aggregator_db_user $_CDK_CONFIG_FILE Aggregator.DB User - export_key_from_toml_file_or_fatal aggregator_db_password $_CDK_CONFIG_FILE Aggregator.DB Password export_obj_key_from_toml_file zkevm_l2_aggregator_keystore_password $_CDK_CONFIG_FILE Aggregator.EthTxManager PrivateKeys Password if [ $? -ne 0 ]; then export_key_from_toml_file zkevm_l2_aggregator_keystore_password $_CDK_CONFIG_FILE "." AggregatorPrivateKeyPassword diff --git a/state/config.go b/state/config.go deleted file mode 100644 index e5a65e8b..00000000 --- a/state/config.go +++ /dev/null @@ -1,13 +0,0 @@ -package state - -import ( - "github.com/0xPolygon/cdk/aggregator/db" -) - -// Config is state config -type Config struct { - // ChainID is the L2 ChainID provided by the Network Config - ChainID uint64 - // DB is the database configuration - DB db.Config `mapstructure:"DB"` -} diff --git a/state/interfaces.go b/state/interfaces.go deleted file mode 100644 index fc4eb495..00000000 --- a/state/interfaces.go +++ /dev/null @@ -1,26 +0,0 @@ -package state - -import ( - "context" - - "github.com/jackc/pgconn" - "github.com/jackc/pgx/v4" -) - -type storage interface { - Exec(ctx context.Context, sql string, arguments ...interface{}) (commandTag pgconn.CommandTag, err error) - Query(ctx context.Context, sql string, args ...interface{}) (pgx.Rows, error) - QueryRow(ctx context.Context, sql string, args ...interface{}) pgx.Row - Begin(ctx context.Context) (pgx.Tx, error) - AddSequence(ctx context.Context, sequence Sequence, dbTx pgx.Tx) error - CheckProofContainsCompleteSequences(ctx context.Context, proof *Proof, dbTx pgx.Tx) (bool, error) - GetProofReadyToVerify(ctx context.Context, lastVerfiedBatchNumber uint64, dbTx pgx.Tx) (*Proof, error) - GetProofsToAggregate(ctx context.Context, dbTx pgx.Tx) (*Proof, *Proof, error) - AddGeneratedProof(ctx context.Context, proof *Proof, dbTx pgx.Tx) error - UpdateGeneratedProof(ctx context.Context, proof *Proof, dbTx pgx.Tx) error - DeleteGeneratedProofs(ctx context.Context, batchNumber uint64, batchNumberFinal uint64, dbTx pgx.Tx) error - DeleteUngeneratedProofs(ctx context.Context, dbTx pgx.Tx) error - CleanupGeneratedProofs(ctx context.Context, batchNumber uint64, dbTx pgx.Tx) error - CleanupLockedProofs(ctx context.Context, duration string, dbTx pgx.Tx) (int64, error) - CheckProofExistsForBatch(ctx context.Context, batchNumber uint64, dbTx pgx.Tx) (bool, error) -} diff --git a/state/pgstatestorage/interfaces.go b/state/pgstatestorage/interfaces.go deleted file mode 100644 index e5f7402b..00000000 --- a/state/pgstatestorage/interfaces.go +++ /dev/null @@ -1,14 +0,0 @@ -package pgstatestorage - -import ( - "context" - - "github.com/jackc/pgconn" - "github.com/jackc/pgx/v4" -) - -type ExecQuerier interface { - Exec(ctx context.Context, sql string, arguments ...interface{}) (commandTag pgconn.CommandTag, err error) - Query(ctx context.Context, sql string, args ...interface{}) (pgx.Rows, error) - QueryRow(ctx context.Context, sql string, args ...interface{}) pgx.Row -} diff --git a/state/pgstatestorage/pgstatestorage.go b/state/pgstatestorage/pgstatestorage.go deleted file mode 100644 index 7e294c6b..00000000 --- a/state/pgstatestorage/pgstatestorage.go +++ /dev/null @@ -1,29 +0,0 @@ -package pgstatestorage - -import ( - "github.com/0xPolygon/cdk/state" - "github.com/jackc/pgx/v4" - "github.com/jackc/pgx/v4/pgxpool" -) - -// PostgresStorage implements the Storage interface -type PostgresStorage struct { - cfg state.Config - *pgxpool.Pool -} - -// NewPostgresStorage creates a new StateDB -func NewPostgresStorage(cfg state.Config, db *pgxpool.Pool) *PostgresStorage { - return &PostgresStorage{ - cfg, - db, - } -} - -// getExecQuerier determines which execQuerier to use, dbTx or the main pgxpool -func (p *PostgresStorage) getExecQuerier(dbTx pgx.Tx) ExecQuerier { - if dbTx != nil { - return dbTx - } - return p -} diff --git a/state/pgstatestorage/proof.go b/state/pgstatestorage/proof.go deleted file mode 100644 index fa32fc99..00000000 --- a/state/pgstatestorage/proof.go +++ /dev/null @@ -1,266 +0,0 @@ -package pgstatestorage - -import ( - "context" - "errors" - "fmt" - "time" - - "github.com/0xPolygon/cdk/state" - "github.com/jackc/pgx/v4" -) - -// CheckProofExistsForBatch checks if the batch is already included in any proof -func (p *PostgresStorage) CheckProofExistsForBatch(ctx context.Context, batchNumber uint64, dbTx pgx.Tx) (bool, error) { - const checkProofExistsForBatchSQL = ` - SELECT EXISTS (SELECT 1 FROM aggregator.proof p WHERE $1 >= p.batch_num AND $1 <= p.batch_num_final) - ` - e := p.getExecQuerier(dbTx) - var exists bool - err := e.QueryRow(ctx, checkProofExistsForBatchSQL, batchNumber).Scan(&exists) - if err != nil && !errors.Is(err, pgx.ErrNoRows) { - return exists, err - } - return exists, nil -} - -// CheckProofContainsCompleteSequences checks if a recursive proof contains complete sequences -func (p *PostgresStorage) CheckProofContainsCompleteSequences( - ctx context.Context, proof *state.Proof, dbTx pgx.Tx, -) (bool, error) { - const getProofContainsCompleteSequencesSQL = ` - SELECT EXISTS (SELECT 1 FROM aggregator.sequence s1 WHERE s1.from_batch_num = $1) AND - EXISTS (SELECT 1 FROM aggregator.sequence s2 WHERE s2.to_batch_num = $2) - ` - e := p.getExecQuerier(dbTx) - var exists bool - err := e.QueryRow(ctx, getProofContainsCompleteSequencesSQL, proof.BatchNumber, proof.BatchNumberFinal).Scan(&exists) - if err != nil && !errors.Is(err, pgx.ErrNoRows) { - return exists, err - } - return exists, nil -} - -// GetProofReadyToVerify return the proof that is ready to verify -func (p *PostgresStorage) GetProofReadyToVerify( - ctx context.Context, lastVerfiedBatchNumber uint64, dbTx pgx.Tx, -) (*state.Proof, error) { - const getProofReadyToVerifySQL = ` - SELECT - p.batch_num, - p.batch_num_final, - p.proof, - p.proof_id, - p.input_prover, - p.prover, - p.prover_id, - p.generating_since, - p.created_at, - p.updated_at - FROM aggregator.proof p - WHERE batch_num = $1 AND generating_since IS NULL AND - EXISTS (SELECT 1 FROM aggregator.sequence s1 WHERE s1.from_batch_num = p.batch_num) AND - EXISTS (SELECT 1 FROM aggregator.sequence s2 WHERE s2.to_batch_num = p.batch_num_final) - ` - - var proof = &state.Proof{} - - e := p.getExecQuerier(dbTx) - row := e.QueryRow(ctx, getProofReadyToVerifySQL, lastVerfiedBatchNumber+1) - err := row.Scan( - &proof.BatchNumber, &proof.BatchNumberFinal, &proof.Proof, &proof.ProofID, - &proof.InputProver, &proof.Prover, &proof.ProverID, &proof.GeneratingSince, - &proof.CreatedAt, &proof.UpdatedAt, - ) - - if errors.Is(err, pgx.ErrNoRows) { - return nil, state.ErrNotFound - } else if err != nil { - return nil, err - } - - return proof, err -} - -// GetProofsToAggregate return the next to proof that it is possible to aggregate -func (p *PostgresStorage) GetProofsToAggregate(ctx context.Context, dbTx pgx.Tx) (*state.Proof, *state.Proof, error) { - var ( - proof1 = &state.Proof{} - proof2 = &state.Proof{} - ) - - // TODO: add comments to explain the query - const getProofsToAggregateSQL = ` - SELECT - p1.batch_num as p1_batch_num, - p1.batch_num_final as p1_batch_num_final, - p1.proof as p1_proof, - p1.proof_id as p1_proof_id, - p1.input_prover as p1_input_prover, - p1.prover as p1_prover, - p1.prover_id as p1_prover_id, - p1.generating_since as p1_generating_since, - p1.created_at as p1_created_at, - p1.updated_at as p1_updated_at, - p2.batch_num as p2_batch_num, - p2.batch_num_final as p2_batch_num_final, - p2.proof as p2_proof, - p2.proof_id as p2_proof_id, - p2.input_prover as p2_input_prover, - p2.prover as p2_prover, - p2.prover_id as p2_prover_id, - p2.generating_since as p2_generating_since, - p2.created_at as p2_created_at, - p2.updated_at as p2_updated_at - FROM aggregator.proof p1 INNER JOIN aggregator.proof p2 ON p1.batch_num_final = p2.batch_num - 1 - WHERE p1.generating_since IS NULL AND p2.generating_since IS NULL AND - p1.proof IS NOT NULL AND p2.proof IS NOT NULL AND - ( - EXISTS ( - SELECT 1 FROM aggregator.sequence s - WHERE p1.batch_num >= s.from_batch_num AND p1.batch_num <= s.to_batch_num AND - p1.batch_num_final >= s.from_batch_num AND p1.batch_num_final <= s.to_batch_num AND - p2.batch_num >= s.from_batch_num AND p2.batch_num <= s.to_batch_num AND - p2.batch_num_final >= s.from_batch_num AND p2.batch_num_final <= s.to_batch_num - ) - OR - ( - EXISTS ( SELECT 1 FROM aggregator.sequence s WHERE p1.batch_num = s.from_batch_num) AND - EXISTS ( SELECT 1 FROM aggregator.sequence s WHERE p1.batch_num_final = s.to_batch_num) AND - EXISTS ( SELECT 1 FROM aggregator.sequence s WHERE p2.batch_num = s.from_batch_num) AND - EXISTS ( SELECT 1 FROM aggregator.sequence s WHERE p2.batch_num_final = s.to_batch_num) - ) - ) - ORDER BY p1.batch_num ASC - LIMIT 1 - ` - - e := p.getExecQuerier(dbTx) - row := e.QueryRow(ctx, getProofsToAggregateSQL) - err := row.Scan( - &proof1.BatchNumber, &proof1.BatchNumberFinal, &proof1.Proof, &proof1.ProofID, - &proof1.InputProver, &proof1.Prover, &proof1.ProverID, &proof1.GeneratingSince, - &proof1.CreatedAt, &proof1.UpdatedAt, - &proof2.BatchNumber, &proof2.BatchNumberFinal, &proof2.Proof, &proof2.ProofID, - &proof2.InputProver, &proof2.Prover, &proof2.ProverID, &proof2.GeneratingSince, - &proof2.CreatedAt, &proof2.UpdatedAt, - ) - - if errors.Is(err, pgx.ErrNoRows) { - return nil, nil, state.ErrNotFound - } else if err != nil { - return nil, nil, err - } - - return proof1, proof2, err -} - -// AddGeneratedProof adds a generated proof to the storage -func (p *PostgresStorage) AddGeneratedProof(ctx context.Context, proof *state.Proof, dbTx pgx.Tx) error { - const addGeneratedProofSQL = ` - INSERT INTO aggregator.proof ( - batch_num, batch_num_final, proof, proof_id, input_prover, prover, - prover_id, generating_since, created_at, updated_at - ) VALUES ( - $1, $2, $3, $4, $5, $6, $7, $8, $9, $10 - ) - ` - e := p.getExecQuerier(dbTx) - now := time.Now().UTC().Round(time.Microsecond) - _, err := e.Exec( - ctx, addGeneratedProofSQL, proof.BatchNumber, proof.BatchNumberFinal, proof.Proof, proof.ProofID, - proof.InputProver, proof.Prover, proof.ProverID, proof.GeneratingSince, now, now, - ) - return err -} - -// UpdateGeneratedProof updates a generated proof in the storage -func (p *PostgresStorage) UpdateGeneratedProof(ctx context.Context, proof *state.Proof, dbTx pgx.Tx) error { - const addGeneratedProofSQL = ` - UPDATE aggregator.proof - SET proof = $3, - proof_id = $4, - input_prover = $5, - prover = $6, - prover_id = $7, - generating_since = $8, - updated_at = $9 - WHERE batch_num = $1 - AND batch_num_final = $2 - ` - e := p.getExecQuerier(dbTx) - now := time.Now().UTC().Round(time.Microsecond) - _, err := e.Exec( - ctx, addGeneratedProofSQL, proof.BatchNumber, proof.BatchNumberFinal, proof.Proof, proof.ProofID, - proof.InputProver, proof.Prover, proof.ProverID, proof.GeneratingSince, now, - ) - return err -} - -// DeleteGeneratedProofs deletes from the storage the generated proofs falling -// inside the batch numbers range. -func (p *PostgresStorage) DeleteGeneratedProofs( - ctx context.Context, batchNumber uint64, batchNumberFinal uint64, dbTx pgx.Tx, -) error { - const deleteGeneratedProofSQL = "DELETE FROM aggregator.proof WHERE batch_num >= $1 AND batch_num_final <= $2" - e := p.getExecQuerier(dbTx) - _, err := e.Exec(ctx, deleteGeneratedProofSQL, batchNumber, batchNumberFinal) - return err -} - -// CleanupGeneratedProofs deletes from the storage the generated proofs up to -// the specified batch number included. -func (p *PostgresStorage) CleanupGeneratedProofs(ctx context.Context, batchNumber uint64, dbTx pgx.Tx) error { - const deleteGeneratedProofSQL = "DELETE FROM aggregator.proof WHERE batch_num_final <= $1" - e := p.getExecQuerier(dbTx) - _, err := e.Exec(ctx, deleteGeneratedProofSQL, batchNumber) - return err -} - -// CleanupLockedProofs deletes from the storage the proofs locked in generating -// state for more than the provided threshold. -func (p *PostgresStorage) CleanupLockedProofs(ctx context.Context, duration string, dbTx pgx.Tx) (int64, error) { - interval, err := toPostgresInterval(duration) - if err != nil { - return 0, err - } - sql := fmt.Sprintf("DELETE FROM aggregator.proof WHERE generating_since < (NOW() - interval '%s')", interval) - e := p.getExecQuerier(dbTx) - ct, err := e.Exec(ctx, sql) - if err != nil { - return 0, err - } - return ct.RowsAffected(), nil -} - -// DeleteUngeneratedProofs deletes ungenerated proofs. -// This method is meant to be use during aggregator boot-up sequence -func (p *PostgresStorage) DeleteUngeneratedProofs(ctx context.Context, dbTx pgx.Tx) error { - const deleteUngeneratedProofsSQL = "DELETE FROM aggregator.proof WHERE generating_since IS NOT NULL" - e := p.getExecQuerier(dbTx) - _, err := e.Exec(ctx, deleteUngeneratedProofsSQL) - return err -} - -func toPostgresInterval(duration string) (string, error) { - unit := duration[len(duration)-1] - var pgUnit string - - switch unit { - case 's': - pgUnit = "second" - case 'm': - pgUnit = "minute" - case 'h': - pgUnit = "hour" - default: - return "", state.ErrUnsupportedDuration - } - - isMoreThanOne := duration[0] != '1' || len(duration) > 2 //nolint:mnd - if isMoreThanOne { - pgUnit += "s" - } - - return fmt.Sprintf("%s %s", duration[:len(duration)-1], pgUnit), nil -} diff --git a/state/pgstatestorage/sequence.go b/state/pgstatestorage/sequence.go deleted file mode 100644 index 7d5be9fb..00000000 --- a/state/pgstatestorage/sequence.go +++ /dev/null @@ -1,21 +0,0 @@ -package pgstatestorage - -import ( - "context" - - "github.com/0xPolygon/cdk/state" - "github.com/jackc/pgx/v4" -) - -// AddSequence stores the sequence information to allow the aggregator verify sequences. -func (p *PostgresStorage) AddSequence(ctx context.Context, sequence state.Sequence, dbTx pgx.Tx) error { - const addSequenceSQL = ` - INSERT INTO aggregator.sequence (from_batch_num, to_batch_num) - VALUES($1, $2) - ON CONFLICT (from_batch_num) DO UPDATE SET to_batch_num = $2 - ` - - e := p.getExecQuerier(dbTx) - _, err := e.Exec(ctx, addSequenceSQL, sequence.FromBatchNumber, sequence.ToBatchNumber) - return err -} diff --git a/state/state.go b/state/state.go deleted file mode 100644 index c9235ce4..00000000 --- a/state/state.go +++ /dev/null @@ -1,40 +0,0 @@ -package state - -import ( - "context" - - "github.com/ethereum/go-ethereum/common" - "github.com/jackc/pgx/v4" -) - -var ( - // ZeroHash is the hash 0x0000000000000000000000000000000000000000000000000000000000000000 - ZeroHash = common.Hash{} - // ZeroAddress is the address 0x0000000000000000000000000000000000000000 - ZeroAddress = common.Address{} -) - -// State is an implementation of the state -type State struct { - cfg Config - storage -} - -// NewState creates a new State -func NewState(cfg Config, storage storage) *State { - state := &State{ - cfg: cfg, - storage: storage, - } - - return state -} - -// BeginStateTransaction starts a state transaction -func (s *State) BeginStateTransaction(ctx context.Context) (pgx.Tx, error) { - tx, err := s.Begin(ctx) - if err != nil { - return nil, err - } - return tx, nil -} diff --git a/test/Makefile b/test/Makefile index 49c22f95..d4e3c274 100644 --- a/test/Makefile +++ b/test/Makefile @@ -54,10 +54,10 @@ generate-mocks-sync: ## Generates mocks for sync, using mockery tool generate-mocks-aggregator: ## Generates mocks for aggregator, using mockery tool export "GOROOT=$$(go env GOROOT)" && $$(go env GOPATH)/bin/mockery --name=ProverInterface --dir=../aggregator --output=../aggregator/mocks --outpkg=mocks --structname=ProverInterfaceMock --filename=mock_prover.go export "GOROOT=$$(go env GOROOT)" && $$(go env GOPATH)/bin/mockery --name=Etherman --dir=../aggregator --output=../aggregator/mocks --outpkg=mocks --structname=EthermanMock --filename=mock_etherman.go - export "GOROOT=$$(go env GOROOT)" && $$(go env GOPATH)/bin/mockery --name=StateInterface --dir=../aggregator --output=../aggregator/mocks --outpkg=mocks --structname=StateInterfaceMock --filename=mock_state.go + export "GOROOT=$$(go env GOROOT)" && $$(go env GOPATH)/bin/mockery --name=StorageInterface --dir=../aggregator --output=../aggregator/mocks --outpkg=mocks --structname=StorageInterfaceMock --filename=mock_storage.go export "GOROOT=$$(go env GOROOT)" && $$(go env GOPATH)/bin/mockery --name=Synchronizer --srcpkg=github.com/0xPolygonHermez/zkevm-synchronizer-l1/synchronizer --output=../aggregator/mocks --outpkg=mocks --structname=SynchronizerInterfaceMock --filename=mock_synchronizer.go export "GOROOT=$$(go env GOROOT)" && $$(go env GOPATH)/bin/mockery --name=EthTxManagerClient --dir=../aggregator --output=../aggregator/mocks --outpkg=mocks --structname=EthTxManagerClientMock --filename=mock_eth_tx_manager.go - export "GOROOT=$$(go env GOROOT)" && $$(go env GOPATH)/bin/mockery --name=Tx --srcpkg=github.com/jackc/pgx/v4 --output=../aggregator/mocks --outpkg=mocks --structname=DbTxMock --filename=mock_dbtx.go + export "GOROOT=$$(go env GOROOT)" && $$(go env GOPATH)/bin/mockery --name=Txer --dir=../db --output=../aggregator/mocks --outpkg=mocks --structname=TxerMock --filename=mock_txer.go export "GOROOT=$$(go env GOROOT)" && $$(go env GOPATH)/bin/mockery --name=RPCInterface --dir=../aggregator --output=../aggregator/mocks --outpkg=mocks --structname=RPCInterfaceMock --filename=mock_rpc.go export "GOROOT=$$(go env GOROOT)" && $$(go env GOPATH)/bin/mockery --name=AggregatorService_ChannelServer --dir=../aggregator/prover --output=../aggregator/prover/mocks --outpkg=mocks --structname=ChannelMock --filename=mock_channel.go diff --git a/test/config/kurtosis-cdk-node-config.toml.template b/test/config/kurtosis-cdk-node-config.toml.template index 4069b350..45ef7464 100644 --- a/test/config/kurtosis-cdk-node-config.toml.template +++ b/test/config/kurtosis-cdk-node-config.toml.template @@ -53,14 +53,6 @@ Outputs = ["stderr"] VerifyProofInterval = "10s" GasOffset = 150000 SettlementBackend = "agglayer" - [Aggregator.DB] - Name = "{{.aggregator_db.name}}" - User = "{{.aggregator_db.user}}" - Password = "{{.aggregator_db.password}}" - Host = "{{.aggregator_db.hostname}}" - Port = "{{.aggregator_db.port}}" - EnableLog = false - MaxConns = 200 [AggSender] CertificateSendInterval = "1m" diff --git a/test/config/test.config.toml b/test/config/test.config.toml index 94940469..9da00c79 100644 --- a/test/config/test.config.toml +++ b/test/config/test.config.toml @@ -58,14 +58,6 @@ AggLayerURL = "" SyncModeOnlyEnabled = false UseFullWitness = false SequencerPrivateKey = {} - [Aggregator.DB] - Name = "aggregator_db" - User = "aggregator_user" - Password = "aggregator_password" - Host = "cdk-aggregator-db" - Port = "5432" - EnableLog = false - MaxConns = 200 [Aggregator.Log] Environment = "development" # "production" or "development" Level = "info" From e93e1b234e0ee14ea6bd1429099992dcf47bd33e Mon Sep 17 00:00:00 2001 From: Joan Esteban <129153821+joanestebanr@users.noreply.github.com> Date: Mon, 25 Nov 2024 10:55:59 +0100 Subject: [PATCH 13/23] feat: check agglayer certificate and use as initial if db is empty (#192) - integration `interop_getLatestKnownCertificateHeader` end-point (aggsender and e2e tests) - Check agglayer and aggsender are on the same page - Fix wrong DBPath on default config - Fix colors on script `local_config` - Changes on config file: new fields `aggsender.MaxRetriesStoreCertificate` and `aggsender.DelayBeetweenRetries` - Partial solution to bug CDK-603 on PreviousLER (just fix initial case that first cert fails) --------- Co-authored-by: Goran Rojovic --- .github/workflows/test-e2e.yml | 1 + agglayer/client.go | 22 ++ agglayer/client_test.go | 71 +++- agglayer/mock_agglayer_client.go | 58 +++ agglayer/types.go | 39 +- aggsender/aggsender.go | 221 ++++++++++-- aggsender/aggsender_test.go | 340 ++++++++++++++++-- aggsender/block_notifier_polling_test.go | 7 +- aggsender/config.go | 8 +- aggsender/db/aggsender_db_storage.go | 28 +- aggsender/db/aggsender_db_storage_test.go | 28 +- aggsender/mocks/agg_sender_storage.go | 54 +-- aggsender/types/types.go | 50 ++- config/default.go | 10 +- scripts/local_config | 2 +- test/bats/pp/e2e-pp.bats | 21 +- test/combinations/fork12-pessimistic.yml | 5 +- test/run-e2e.sh | 9 +- test/scripts/agglayer_certificates_monitor.sh | 73 ++-- 19 files changed, 864 insertions(+), 183 deletions(-) diff --git a/.github/workflows/test-e2e.yml b/.github/workflows/test-e2e.yml index abde0c6b..9f024498 100644 --- a/.github/workflows/test-e2e.yml +++ b/.github/workflows/test-e2e.yml @@ -82,6 +82,7 @@ jobs: env: KURTOSIS_FOLDER: ${{ github.workspace }}/kurtosis-cdk BATS_LIB_PATH: /usr/lib/ + agglayer_prover_sp1_key: ${{ secrets.SP1_PRIVATE_KEY }} - name: Dump enclave logs if: failure() diff --git a/agglayer/client.go b/agglayer/client.go index 8396fc9e..8a186be4 100644 --- a/agglayer/client.go +++ b/agglayer/client.go @@ -30,6 +30,7 @@ type AgglayerClientInterface interface { WaitTxToBeMined(hash common.Hash, ctx context.Context) error SendCertificate(certificate *SignedCertificate) (common.Hash, error) GetCertificateHeader(certificateHash common.Hash) (*CertificateHeader, error) + GetLatestKnownCertificateHeader(networkID uint32) (*CertificateHeader, error) AggLayerClientGetEpochConfiguration } @@ -158,3 +159,24 @@ func (c *AggLayerClient) GetEpochConfiguration() (*ClockConfiguration, error) { return result, nil } + +// GetLatestKnownCertificateHeader returns the last certificate header submitted by networkID +func (c *AggLayerClient) GetLatestKnownCertificateHeader(networkID uint32) (*CertificateHeader, error) { + response, err := jSONRPCCall(c.url, "interop_getLatestKnownCertificateHeader", networkID) + if err != nil { + return nil, fmt.Errorf("GetLatestKnownCertificateHeader error jSONRPCCall. Err: %w", err) + } + + if response.Error != nil { + return nil, fmt.Errorf("GetLatestKnownCertificateHeader rpc returns an error: code=%d msg=%s", + response.Error.Code, response.Error.Message) + } + + var result *CertificateHeader + err = json.Unmarshal(response.Result, &result) + if err != nil { + return nil, fmt.Errorf("GetLatestKnownCertificateHeader error Unmashal. Err: %w", err) + } + + return result, nil +} diff --git a/agglayer/client_test.go b/agglayer/client_test.go index 82baea85..09bea14e 100644 --- a/agglayer/client_test.go +++ b/agglayer/client_test.go @@ -5,6 +5,7 @@ import ( "testing" "github.com/0xPolygon/cdk-rpc/rpc" + "github.com/ethereum/go-ethereum/common" "github.com/stretchr/testify/require" ) @@ -14,11 +15,40 @@ const ( func TestExploratoryClient(t *testing.T) { t.Skip("This test is for exploratory purposes only") - sut := NewAggLayerClient("http://127.0.0.1:32853") + sut := NewAggLayerClient("http://127.0.0.1:32781") config, err := sut.GetEpochConfiguration() require.NoError(t, err) require.NotNil(t, config) fmt.Printf("Config: %s", config.String()) + + lastCert, err := sut.GetLatestKnownCertificateHeader(1) + require.NoError(t, err) + require.NotNil(t, lastCert) + fmt.Printf("LastCert: %s", lastCert.String()) +} + +func TestExploratoryGetCertificateHeader(t *testing.T) { + t.Skip("This test is exploratory and should be skipped") + aggLayerClient := NewAggLayerClient("http://localhost:32796") + certificateID := common.HexToHash("0xf153e75e24591432ac5deafaeaafba3fec0fd851261c86051b9c0d540b38c369") + certificateHeader, err := aggLayerClient.GetCertificateHeader(certificateID) + require.NoError(t, err) + fmt.Print(certificateHeader) +} +func TestExploratoryGetEpochConfiguration(t *testing.T) { + t.Skip("This test is exploratory and should be skipped") + aggLayerClient := NewAggLayerClient("http://localhost:32796") + clockConfig, err := aggLayerClient.GetEpochConfiguration() + require.NoError(t, err) + fmt.Print(clockConfig) +} + +func TestExploratoryGetLatestKnownCertificateHeader(t *testing.T) { + t.Skip("This test is exploratory and should be skipped") + aggLayerClient := NewAggLayerClient("http://localhost:32781") + cert, err := aggLayerClient.GetLatestKnownCertificateHeader(1) + require.NoError(t, err) + fmt.Print(cert) } func TestGetEpochConfigurationResponseWithError(t *testing.T) { @@ -74,3 +104,42 @@ func TestGetEpochConfigurationOkResponse(t *testing.T) { GenesisBlock: 1, }, *clockConfig) } + +func TestGetLatestKnownCertificateHeaderOkResponse(t *testing.T) { + sut := NewAggLayerClient(testURL) + response := rpc.Response{ + Result: []byte(`{"network_id":1,"height":0,"epoch_number":223,"certificate_index":0,"certificate_id":"0xf9179d2fbe535814b5a14496e2eed474f49c6131227a9dfc5d2d8caf9e212054","new_local_exit_root":"0x7ae06f4a5d0b6da7dd4973fb6ef40d82c9f2680899b3baaf9e564413b59cc160","metadata":"0x00000000000000000000000000000000000000000000000000000000000001a7","status":"Settled"}`), + } + jSONRPCCall = func(url, method string, params ...interface{}) (rpc.Response, error) { + return response, nil + } + cert, err := sut.GetLatestKnownCertificateHeader(1) + require.NotNil(t, cert) + require.NoError(t, err) +} +func TestGetLatestKnownCertificateHeaderErrorResponse(t *testing.T) { + sut := NewAggLayerClient(testURL) + jSONRPCCall = func(url, method string, params ...interface{}) (rpc.Response, error) { + return rpc.Response{}, fmt.Errorf("unittest error") + } + + cert, err := sut.GetLatestKnownCertificateHeader(1) + + require.Nil(t, cert) + require.Error(t, err) +} + +func TestGetLatestKnownCertificateHeaderResponseBadJson(t *testing.T) { + sut := NewAggLayerClient(testURL) + response := rpc.Response{ + Result: []byte(`{`), + } + jSONRPCCall = func(url, method string, params ...interface{}) (rpc.Response, error) { + return response, nil + } + + cert, err := sut.GetLatestKnownCertificateHeader(1) + + require.Nil(t, cert) + require.Error(t, err) +} diff --git a/agglayer/mock_agglayer_client.go b/agglayer/mock_agglayer_client.go index b7f70ee8..6c5a3fbf 100644 --- a/agglayer/mock_agglayer_client.go +++ b/agglayer/mock_agglayer_client.go @@ -138,6 +138,64 @@ func (_c *AgglayerClientMock_GetEpochConfiguration_Call) RunAndReturn(run func() return _c } +// GetLatestKnownCertificateHeader provides a mock function with given fields: networkID +func (_m *AgglayerClientMock) GetLatestKnownCertificateHeader(networkID uint32) (*CertificateHeader, error) { + ret := _m.Called(networkID) + + if len(ret) == 0 { + panic("no return value specified for GetLatestKnownCertificateHeader") + } + + var r0 *CertificateHeader + var r1 error + if rf, ok := ret.Get(0).(func(uint32) (*CertificateHeader, error)); ok { + return rf(networkID) + } + if rf, ok := ret.Get(0).(func(uint32) *CertificateHeader); ok { + r0 = rf(networkID) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*CertificateHeader) + } + } + + if rf, ok := ret.Get(1).(func(uint32) error); ok { + r1 = rf(networkID) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// AgglayerClientMock_GetLatestKnownCertificateHeader_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GetLatestKnownCertificateHeader' +type AgglayerClientMock_GetLatestKnownCertificateHeader_Call struct { + *mock.Call +} + +// GetLatestKnownCertificateHeader is a helper method to define mock.On call +// - networkID uint32 +func (_e *AgglayerClientMock_Expecter) GetLatestKnownCertificateHeader(networkID interface{}) *AgglayerClientMock_GetLatestKnownCertificateHeader_Call { + return &AgglayerClientMock_GetLatestKnownCertificateHeader_Call{Call: _e.mock.On("GetLatestKnownCertificateHeader", networkID)} +} + +func (_c *AgglayerClientMock_GetLatestKnownCertificateHeader_Call) Run(run func(networkID uint32)) *AgglayerClientMock_GetLatestKnownCertificateHeader_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(uint32)) + }) + return _c +} + +func (_c *AgglayerClientMock_GetLatestKnownCertificateHeader_Call) Return(_a0 *CertificateHeader, _a1 error) *AgglayerClientMock_GetLatestKnownCertificateHeader_Call { + _c.Call.Return(_a0, _a1) + return _c +} + +func (_c *AgglayerClientMock_GetLatestKnownCertificateHeader_Call) RunAndReturn(run func(uint32) (*CertificateHeader, error)) *AgglayerClientMock_GetLatestKnownCertificateHeader_Call { + _c.Call.Return(run) + return _c +} + // SendCertificate provides a mock function with given fields: certificate func (_m *AgglayerClientMock) SendCertificate(certificate *SignedCertificate) (common.Hash, error) { ret := _m.Called(certificate) diff --git a/agglayer/types.go b/agglayer/types.go index aece93f0..9a56d878 100644 --- a/agglayer/types.go +++ b/agglayer/types.go @@ -5,6 +5,7 @@ import ( "errors" "fmt" "math/big" + "slices" "strings" "github.com/0xPolygon/cdk/bridgesync" @@ -24,11 +25,36 @@ const ( Settled ) +var ( + NonSettledStatuses = []CertificateStatus{Pending, Candidate, Proven} + ClosedStatuses = []CertificateStatus{Settled, InError} +) + // String representation of the enum func (c CertificateStatus) String() string { return [...]string{"Pending", "Proven", "Candidate", "InError", "Settled"}[c] } +// IsClosed returns true if the certificate is closed (settled or inError) +func (c CertificateStatus) IsClosed() bool { + return !c.IsOpen() +} + +// IsSettled returns true if the certificate is settled +func (c CertificateStatus) IsSettled() bool { + return c == Settled +} + +// IsInError returns true if the certificate is in error +func (c CertificateStatus) IsInError() bool { + return c == InError +} + +// IsOpen returns true if the certificate is open (pending, candidate or proven) +func (c CertificateStatus) IsOpen() bool { + return slices.Contains(NonSettledStatuses, c) +} + // UnmarshalJSON is the implementation of the json.Unmarshaler interface func (c *CertificateStatus) UnmarshalJSON(data []byte) error { dataStr := string(data) @@ -550,7 +576,18 @@ type CertificateHeader struct { Error PPError `json:"-"` } -func (c CertificateHeader) String() string { +// ID returns a string with the ident of this cert (height/certID) +func (c *CertificateHeader) ID() string { + if c == nil { + return "nil" + } + return fmt.Sprintf("%d/%s", c.Height, c.CertificateID.String()) +} + +func (c *CertificateHeader) String() string { + if c == nil { + return "nil" + } errors := "" if c.Error != nil { errors = c.Error.String() diff --git a/aggsender/aggsender.go b/aggsender/aggsender.go index 08730572..39cd6867 100644 --- a/aggsender/aggsender.go +++ b/aggsender/aggsender.go @@ -8,7 +8,6 @@ import ( "fmt" "math/big" "os" - "slices" "time" "github.com/0xPolygon/cdk/agglayer" @@ -28,8 +27,7 @@ var ( errNoBridgesAndClaims = errors.New("no bridges and claims to build certificate") errInvalidSignatureSize = errors.New("invalid signature size") - zeroLER = common.HexToHash("0x27ae5ba08d7291c96c8cbddcc148bf48a6d68c7974b94356f53754ef6171d757") - nonSettledStatuses = []agglayer.CertificateStatus{agglayer.Pending, agglayer.Candidate, agglayer.Proven} + zeroLER = common.HexToHash("0x27ae5ba08d7291c96c8cbddcc148bf48a6d68c7974b94356f53754ef6171d757") ) // AggSender is a component that will send certificates to the aggLayer @@ -83,9 +81,31 @@ func New( // Start starts the AggSender func (a *AggSender) Start(ctx context.Context) { + a.log.Info("AggSender started") + a.checkInitialStatus(ctx) a.sendCertificates(ctx) } +// checkInitialStatus check local status vs agglayer status +func (a *AggSender) checkInitialStatus(ctx context.Context) { + ticker := time.NewTicker(a.cfg.DelayBeetweenRetries.Duration) + defer ticker.Stop() + + for { + if err := a.checkLastCertificateFromAgglayer(ctx); err != nil { + log.Errorf("error checking initial status: %w, retrying in %s", err, a.cfg.DelayBeetweenRetries.String()) + } else { + log.Info("Initial status checked successfully") + return + } + select { + case <-ctx.Done(): + return + case <-ticker.C: + } + } +} + // sendCertificates sends certificates to the aggLayer func (a *AggSender) sendCertificates(ctx context.Context) { chEpoch := a.epochNotifier.Subscribe("aggsender") @@ -132,6 +152,10 @@ func (a *AggSender) sendCertificate(ctx context.Context) (*agglayer.SignedCertif if err != nil { return nil, err } + if lastSentCertificateInfo == nil { + // There are no certificates, so we set that to a empty one + lastSentCertificateInfo = &types.CertificateInfo{} + } previousToBlock := lastSentCertificateInfo.ToBlock if lastSentCertificateInfo.Status == agglayer.InError { @@ -166,7 +190,7 @@ func (a *AggSender) sendCertificate(ctx context.Context) (*agglayer.SignedCertif a.log.Infof("building certificate for block: %d to block: %d", fromBlock, toBlock) - certificate, err := a.buildCertificate(ctx, bridges, claims, lastSentCertificateInfo, toBlock) + certificate, err := a.buildCertificate(ctx, bridges, claims, *lastSentCertificateInfo, toBlock) if err != nil { return nil, fmt.Errorf("error building certificate: %w", err) } @@ -202,8 +226,10 @@ func (a *AggSender) sendCertificate(ctx context.Context) (*agglayer.SignedCertif UpdatedAt: createdTime, SignedCertificate: string(raw), } - - if err := a.storage.SaveLastSentCertificate(ctx, certInfo); err != nil { + // TODO: Improve this case, if a cert is not save in the storage, we are going to settle a unknown certificate + err = a.saveCertificateToStorage(ctx, certInfo, a.cfg.MaxRetriesStoreCertificate) + if err != nil { + a.log.Errorf("error saving certificate to storage: %w", err) return nil, fmt.Errorf("error saving last sent certificate %s in db: %w", certInfo.String(), err) } @@ -213,6 +239,26 @@ func (a *AggSender) sendCertificate(ctx context.Context) (*agglayer.SignedCertif return signedCertificate, nil } +// saveCertificateToStorage saves the certificate to the storage +// it retries if it fails. if param retries == 0 it retries indefinitely +func (a *AggSender) saveCertificateToStorage(ctx context.Context, cert types.CertificateInfo, maxRetries int) error { + retries := 1 + err := fmt.Errorf("initial_error") + for err != nil { + if err = a.storage.SaveLastSentCertificate(ctx, cert); err != nil { + // If this happens we can't work as normal, because local DB is outdated, we have to retry + a.log.Errorf("error saving last sent certificate %s in db: %w", cert.String(), err) + if retries == maxRetries { + return fmt.Errorf("error saving last sent certificate %s in db: %w", cert.String(), err) + } else { + retries++ + time.Sleep(a.cfg.DelayBeetweenRetries.Duration) + } + } + } + return nil +} + // saveCertificate saves the certificate to a tmp file func (a *AggSender) saveCertificateToFile(signedCertificate *agglayer.SignedCertificate) { if signedCertificate == nil || a.cfg.SaveCertificatesToFilesPath == "" { @@ -235,7 +281,7 @@ func (a *AggSender) saveCertificateToFile(signedCertificate *agglayer.SignedCert func (a *AggSender) getNextHeightAndPreviousLER( lastSentCertificateInfo *types.CertificateInfo) (uint64, common.Hash) { height := lastSentCertificateInfo.Height + 1 - if lastSentCertificateInfo.Status == agglayer.InError { + if lastSentCertificateInfo.Status.IsInError() { // previous certificate was in error, so we need to resend it a.log.Debugf("Last certificate %s failed so reusing height %d", lastSentCertificateInfo.CertificateID, lastSentCertificateInfo.Height) @@ -243,7 +289,8 @@ func (a *AggSender) getNextHeightAndPreviousLER( } previousLER := lastSentCertificateInfo.NewLocalExitRoot - if lastSentCertificateInfo.NewLocalExitRoot == (common.Hash{}) { + if lastSentCertificateInfo.NewLocalExitRoot == (common.Hash{}) || + lastSentCertificateInfo.Height == 0 && lastSentCertificateInfo.Status.IsInError() { // meaning this is the first certificate height = 0 previousLER = zeroLER @@ -489,54 +536,72 @@ func (a *AggSender) signCertificate(certificate *agglayer.Certificate) (*agglaye // It returns: // bool -> if there are pending certificates func (a *AggSender) checkPendingCertificatesStatus(ctx context.Context) bool { - pendingCertificates, err := a.storage.GetCertificatesByStatus(nonSettledStatuses) + pendingCertificates, err := a.storage.GetCertificatesByStatus(agglayer.NonSettledStatuses) if err != nil { - err = fmt.Errorf("error getting pending certificates: %w", err) - a.log.Error(err) + a.log.Errorf("error getting pending certificates: %w", err) return true } - thereArePendingCerts := false + a.log.Debugf("checkPendingCertificatesStatus num of pendingCertificates: %d", len(pendingCertificates)) + thereArePendingCerts := false + for _, certificate := range pendingCertificates { certificateHeader, err := a.aggLayerClient.GetCertificateHeader(certificate.CertificateID) if err != nil { - err = fmt.Errorf("error getting certificate header of %d/%s from agglayer: %w", - certificate.Height, certificate.String(), err) - a.log.Error(err) + a.log.Errorf("error getting certificate header of %s from agglayer: %w", + certificate.ID(), err) return true } - elapsedTime := time.Now().UTC().Sub(time.UnixMilli(certificate.CreatedAt)) + a.log.Debugf("aggLayerClient.GetCertificateHeader status [%s] of certificate %s elapsed time:%s", certificateHeader.Status, - certificateHeader.String(), - elapsedTime) - - if certificateHeader.Status != certificate.Status { - a.log.Infof("certificate %s changed status from [%s] to [%s] elapsed time: %s", - certificateHeader.String(), certificate.Status, certificateHeader.Status, elapsedTime) + certificateHeader.ID(), + certificate.ElapsedTimeSinceCreation()) - certificate.Status = certificateHeader.Status - certificate.UpdatedAt = time.Now().UTC().UnixMilli() - - if err := a.storage.UpdateCertificateStatus(ctx, *certificate); err != nil { - err = fmt.Errorf("error updating certificate %s status in storage: %w", certificateHeader.String(), err) - a.log.Error(err) - return true - } + if err := a.updateCertificateStatus(ctx, certificate, certificateHeader); err != nil { + a.log.Errorf("error updating certificate %s status in storage: %w", certificateHeader.String(), err) + return true } - if slices.Contains(nonSettledStatuses, certificateHeader.Status) { + + if !certificate.IsClosed() { a.log.Infof("certificate %s is still pending, elapsed time:%s ", - certificateHeader.String(), elapsedTime) + certificateHeader.ID(), certificate.ElapsedTimeSinceCreation()) thereArePendingCerts = true } } return thereArePendingCerts } +// updateCertificate updates the certificate status in the storage +func (a *AggSender) updateCertificateStatus(ctx context.Context, + localCert *types.CertificateInfo, + agglayerCert *agglayer.CertificateHeader) error { + if localCert.Status == agglayerCert.Status { + return nil + } + a.log.Infof("certificate %s changed status from [%s] to [%s] elapsed time: %s full_cert: %s", + localCert.ID(), localCert.Status, agglayerCert.Status, localCert.ElapsedTimeSinceCreation(), + localCert.String()) + + // That is a strange situation + if agglayerCert.Status.IsOpen() == localCert.Status.IsClosed() { + a.log.Warnf("certificate %s is reopen! from [%s] to [%s]", + localCert.ID(), localCert.Status, agglayerCert.Status) + } + + localCert.Status = agglayerCert.Status + localCert.UpdatedAt = time.Now().UTC().UnixMilli() + if err := a.storage.UpdateCertificate(ctx, *localCert); err != nil { + a.log.Errorf("error updating certificate %s status in storage: %w", agglayerCert.ID(), err) + return fmt.Errorf("error updating certificate. Err: %w", err) + } + return nil +} + // shouldSendCertificate checks if a certificate should be sent at given time // if we have pending certificates, then we wait until they are settled func (a *AggSender) shouldSendCertificate() (bool, error) { - pendingCertificates, err := a.storage.GetCertificatesByStatus(nonSettledStatuses) + pendingCertificates, err := a.storage.GetCertificatesByStatus(agglayer.NonSettledStatuses) if err != nil { return false, fmt.Errorf("error getting pending certificates: %w", err) } @@ -544,6 +609,74 @@ func (a *AggSender) shouldSendCertificate() (bool, error) { return len(pendingCertificates) == 0, nil } +// checkLastCertificateFromAgglayer checks the last certificate from agglayer +func (a *AggSender) checkLastCertificateFromAgglayer(ctx context.Context) error { + networkID := a.l2Syncer.OriginNetwork() + a.log.Infof("recovery: checking last certificate from AggLayer for network %d", networkID) + aggLayerLastCert, err := a.aggLayerClient.GetLatestKnownCertificateHeader(networkID) + if err != nil { + return fmt.Errorf("recovery: error getting latest known certificate header from agglayer: %w", err) + } + a.log.Infof("recovery: last certificate from AggLayer: %s", aggLayerLastCert.String()) + localLastCert, err := a.storage.GetLastSentCertificate() + if err != nil { + return fmt.Errorf("recovery: error getting last sent certificate from local storage: %w", err) + } + a.log.Infof("recovery: last certificate in storage: %s", localLastCert.String()) + + // CASE 1: No certificates in local storage and agglayer + if localLastCert == nil && aggLayerLastCert == nil { + a.log.Info("recovery: No certificates in local storage and agglayer: initial state") + return nil + } + // CASE 2: No certificates in local storage but agglayer has one + if localLastCert == nil && aggLayerLastCert != nil { + a.log.Info("recovery: No certificates in local storage but agglayer have one: recovery aggSender cert: %s", + aggLayerLastCert.String()) + if _, err := a.updateLocalStorageWithAggLayerCert(ctx, aggLayerLastCert); err != nil { + return fmt.Errorf("recovery: error updating local storage with agglayer certificate: %w", err) + } + return nil + } + // CASE 3: aggsender stopped between sending to agglayer and storing on DB + if aggLayerLastCert.Height == localLastCert.Height+1 { + a.log.Infof("recovery: AggLayer have next cert (height:%d), so is a recovery case: storing cert: %s", + aggLayerLastCert.Height, localLastCert.String()) + // we need to store the certificate in the local storage. + localLastCert, err = a.updateLocalStorageWithAggLayerCert(ctx, aggLayerLastCert) + if err != nil { + log.Errorf("recovery: error updating status certificate: %s status: %w", aggLayerLastCert.String(), err) + return fmt.Errorf("recovery: error updating certificate status: %w", err) + } + } + // CASE 4: AggSender and AggLayer are not on the same page + // note: we don't need to check individual fields of the certificate + // because CertificateID is a hash of all the fields + if localLastCert.CertificateID != aggLayerLastCert.CertificateID { + a.log.Errorf("recovery: Local certificate:\n %s \n is different from agglayer certificate:\n %s", + localLastCert.String(), aggLayerLastCert.String()) + return fmt.Errorf("recovery: mismatch between local and agglayer certificates") + } + // CASE 5: AggSender and AggLayer are at same page + // just update status + err = a.updateCertificateStatus(ctx, localLastCert, aggLayerLastCert) + if err != nil { + a.log.Errorf("recovery: error updating status certificate: %s status: %w", aggLayerLastCert.String(), err) + return fmt.Errorf("recovery: error updating certificate status: %w", err) + } + + a.log.Infof("recovery: successfully checked last certificate from AggLayer for network %d", networkID) + return nil +} + +// updateLocalStorageWithAggLayerCert updates the local storage with the certificate from the AggLayer +func (a *AggSender) updateLocalStorageWithAggLayerCert(ctx context.Context, + aggLayerCert *agglayer.CertificateHeader) (*types.CertificateInfo, error) { + certInfo := NewCertificateInfoFromAgglayerCertHeader(aggLayerCert) + log.Infof("setting initial certificate from AggLayer: %s", certInfo.String()) + return certInfo, a.storage.SaveLastSentCertificate(ctx, *certInfo) +} + // extractSignatureData extracts the R, S, and V from a 65-byte signature func extractSignatureData(signature []byte) (r, s common.Hash, isOddParity bool, err error) { if len(signature) != signatureSize { @@ -562,3 +695,25 @@ func extractSignatureData(signature []byte) (r, s common.Hash, isOddParity bool, func createCertificateMetadata(toBlock uint64) common.Hash { return common.BigToHash(new(big.Int).SetUint64(toBlock)) } + +func extractFromCertificateMetadataToBlock(metadata common.Hash) uint64 { + return metadata.Big().Uint64() +} + +func NewCertificateInfoFromAgglayerCertHeader(c *agglayer.CertificateHeader) *types.CertificateInfo { + if c == nil { + return nil + } + now := time.Now().UTC().UnixMilli() + return &types.CertificateInfo{ + Height: c.Height, + CertificateID: c.CertificateID, + NewLocalExitRoot: c.NewLocalExitRoot, + FromBlock: 0, + ToBlock: extractFromCertificateMetadataToBlock(c.Metadata), + Status: c.Status, + CreatedAt: now, + UpdatedAt: now, + SignedCertificate: "na/agglayer header", + } +} diff --git a/aggsender/aggsender_test.go b/aggsender/aggsender_test.go index b9242bdf..6d84c683 100644 --- a/aggsender/aggsender_test.go +++ b/aggsender/aggsender_test.go @@ -8,10 +8,12 @@ import ( "fmt" "math/big" "os" + "runtime" "testing" "time" "github.com/0xPolygon/cdk/agglayer" + "github.com/0xPolygon/cdk/aggsender/db" "github.com/0xPolygon/cdk/aggsender/mocks" aggsendertypes "github.com/0xPolygon/cdk/aggsender/types" "github.com/0xPolygon/cdk/bridgesync" @@ -25,21 +27,13 @@ import ( "github.com/stretchr/testify/require" ) -func TestExploratoryGetCertificateHeader(t *testing.T) { - t.Skip("This test is exploratory and should be skipped") - aggLayerClient := agglayer.NewAggLayerClient("http://localhost:32796") - certificateID := common.HexToHash("0xf153e75e24591432ac5deafaeaafba3fec0fd851261c86051b9c0d540b38c369") - certificateHeader, err := aggLayerClient.GetCertificateHeader(certificateID) - require.NoError(t, err) - fmt.Print(certificateHeader) -} -func TestExploratoryGetEpochConfiguration(t *testing.T) { - t.Skip("This test is exploratory and should be skipped") - aggLayerClient := agglayer.NewAggLayerClient("http://localhost:32796") - clockConfig, err := aggLayerClient.GetEpochConfiguration() - require.NoError(t, err) - fmt.Print(clockConfig) -} +const ( + networkIDTest = uint32(1234) +) + +var ( + errTest = errors.New("unitest error") +) func TestConfigString(t *testing.T) { config := Config{ @@ -59,7 +53,6 @@ func TestConfigString(t *testing.T) { "BlockGetInterval: 10s\n" + "CheckSettledInterval: 20s\n" + "AggsenderPrivateKeyPath: /path/to/key\n" + - "AggsenderPrivateKeyPassword: password\n" + "URLRPCL2: http://l2.rpc.url\n" + "BlockFinality: latestBlock\n" + "EpochNotificationPercentage: 50\n" + @@ -281,7 +274,7 @@ func TestGetBridgeExits(t *testing.T) { } func TestAggSenderStart(t *testing.T) { - AggLayerMock := agglayer.NewAgglayerClientMock(t) + aggLayerMock := agglayer.NewAgglayerClientMock(t) epochNotifierMock := mocks.NewEpochNotifier(t) bridgeL2SyncerMock := mocks.NewL2BridgeSyncer(t) ctx, cancel := context.WithCancel(context.Background()) @@ -290,9 +283,10 @@ func TestAggSenderStart(t *testing.T) { ctx, log.WithFields("test", "unittest"), Config{ - StoragePath: "file::memory:?cache=shared", + StoragePath: "file::memory:?cache=shared", + DelayBeetweenRetries: types.Duration{Duration: 1 * time.Microsecond}, }, - AggLayerMock, + aggLayerMock, nil, bridgeL2SyncerMock, epochNotifierMock) @@ -300,7 +294,9 @@ func TestAggSenderStart(t *testing.T) { require.NotNil(t, aggSender) ch := make(chan aggsendertypes.EpochEvent) epochNotifierMock.EXPECT().Subscribe("aggsender").Return(ch) + bridgeL2SyncerMock.EXPECT().OriginNetwork().Return(uint32(1)) bridgeL2SyncerMock.EXPECT().GetLastProcessedBlock(mock.Anything).Return(uint64(0), nil) + aggLayerMock.EXPECT().GetLatestKnownCertificateHeader(mock.Anything).Return(nil, nil) go aggSender.Start(ctx) ch <- aggsendertypes.EpochEvent{ @@ -902,15 +898,15 @@ func TestCheckIfCertificatesAreSettled(t *testing.T) { mockAggLayerClient := agglayer.NewAgglayerClientMock(t) mockLogger := log.WithFields("test", "unittest") - mockStorage.On("GetCertificatesByStatus", nonSettledStatuses).Return( + mockStorage.On("GetCertificatesByStatus", agglayer.NonSettledStatuses).Return( tt.pendingCertificates, tt.getFromDBError) for certID, header := range tt.certificateHeaders { mockAggLayerClient.On("GetCertificateHeader", certID).Return(header, tt.clientError) } if tt.updateDBError != nil { - mockStorage.On("UpdateCertificateStatus", mock.Anything, mock.Anything).Return(tt.updateDBError) + mockStorage.On("UpdateCertificate", mock.Anything, mock.Anything).Return(tt.updateDBError) } else if tt.clientError == nil && tt.getFromDBError == nil { - mockStorage.On("UpdateCertificateStatus", mock.Anything, mock.Anything).Return(nil) + mockStorage.On("UpdateCertificate", mock.Anything, mock.Anything).Return(nil) } aggSender := &AggSender{ @@ -961,7 +957,7 @@ func TestSendCertificate(t *testing.T) { var ( aggsender = &AggSender{ log: log.WithFields("aggsender", 1), - cfg: Config{}, + cfg: Config{MaxRetriesStoreCertificate: 3}, sequencerKey: cfg.sequencerKey, } mockStorage *mocks.AggSenderStorage @@ -973,8 +969,8 @@ func TestSendCertificate(t *testing.T) { if cfg.shouldSendCertificate != nil || cfg.getLastSentCertificate != nil || cfg.saveLastSentCertificate != nil { mockStorage = mocks.NewAggSenderStorage(t) - mockStorage.On("GetCertificatesByStatus", nonSettledStatuses). - Return(cfg.shouldSendCertificate...).Once() + mockStorage.On("GetCertificatesByStatus", agglayer.NonSettledStatuses). + Return(cfg.shouldSendCertificate...) aggsender.storage = mockStorage @@ -983,7 +979,7 @@ func TestSendCertificate(t *testing.T) { } if cfg.saveLastSentCertificate != nil { - mockStorage.On("SaveLastSentCertificate", mock.Anything, mock.Anything).Return(cfg.saveLastSentCertificate...).Once() + mockStorage.On("SaveLastSentCertificate", mock.Anything, mock.Anything).Return(cfg.saveLastSentCertificate...) } } @@ -1055,14 +1051,14 @@ func TestSendCertificate(t *testing.T) { name: "error getting last sent certificate", shouldSendCertificate: []interface{}{[]*aggsendertypes.CertificateInfo{}, nil}, lastL2BlockProcessed: []interface{}{uint64(8), nil}, - getLastSentCertificate: []interface{}{aggsendertypes.CertificateInfo{}, errors.New("error getting last sent certificate")}, + getLastSentCertificate: []interface{}{&aggsendertypes.CertificateInfo{}, errors.New("error getting last sent certificate")}, expectedError: "error getting last sent certificate", }, { name: "no new blocks to send certificate", shouldSendCertificate: []interface{}{[]*aggsendertypes.CertificateInfo{}, nil}, lastL2BlockProcessed: []interface{}{uint64(41), nil}, - getLastSentCertificate: []interface{}{aggsendertypes.CertificateInfo{ + getLastSentCertificate: []interface{}{&aggsendertypes.CertificateInfo{ Height: 41, CertificateID: common.HexToHash("0x111"), NewLocalExitRoot: common.HexToHash("0x13223"), @@ -1074,7 +1070,7 @@ func TestSendCertificate(t *testing.T) { name: "get bridges error", shouldSendCertificate: []interface{}{[]*aggsendertypes.CertificateInfo{}, nil}, lastL2BlockProcessed: []interface{}{uint64(59), nil}, - getLastSentCertificate: []interface{}{aggsendertypes.CertificateInfo{ + getLastSentCertificate: []interface{}{&aggsendertypes.CertificateInfo{ Height: 50, CertificateID: common.HexToHash("0x1111"), NewLocalExitRoot: common.HexToHash("0x132233"), @@ -1088,7 +1084,7 @@ func TestSendCertificate(t *testing.T) { name: "no bridges", shouldSendCertificate: []interface{}{[]*aggsendertypes.CertificateInfo{}, nil}, lastL2BlockProcessed: []interface{}{uint64(69), nil}, - getLastSentCertificate: []interface{}{aggsendertypes.CertificateInfo{ + getLastSentCertificate: []interface{}{&aggsendertypes.CertificateInfo{ Height: 60, CertificateID: common.HexToHash("0x11111"), NewLocalExitRoot: common.HexToHash("0x1322233"), @@ -1101,7 +1097,7 @@ func TestSendCertificate(t *testing.T) { name: "get claims error", shouldSendCertificate: []interface{}{[]*aggsendertypes.CertificateInfo{}, nil}, lastL2BlockProcessed: []interface{}{uint64(79), nil}, - getLastSentCertificate: []interface{}{aggsendertypes.CertificateInfo{ + getLastSentCertificate: []interface{}{&aggsendertypes.CertificateInfo{ Height: 70, CertificateID: common.HexToHash("0x121111"), NewLocalExitRoot: common.HexToHash("0x13122233"), @@ -1123,7 +1119,7 @@ func TestSendCertificate(t *testing.T) { name: "error getting info by global exit root", shouldSendCertificate: []interface{}{[]*aggsendertypes.CertificateInfo{}, nil}, lastL2BlockProcessed: []interface{}{uint64(89), nil}, - getLastSentCertificate: []interface{}{aggsendertypes.CertificateInfo{ + getLastSentCertificate: []interface{}{&aggsendertypes.CertificateInfo{ Height: 80, CertificateID: common.HexToHash("0x1321111"), NewLocalExitRoot: common.HexToHash("0x131122233"), @@ -1150,7 +1146,7 @@ func TestSendCertificate(t *testing.T) { name: "error getting L1 Info tree root by index", shouldSendCertificate: []interface{}{[]*aggsendertypes.CertificateInfo{}, nil}, lastL2BlockProcessed: []interface{}{uint64(89), nil}, - getLastSentCertificate: []interface{}{aggsendertypes.CertificateInfo{ + getLastSentCertificate: []interface{}{&aggsendertypes.CertificateInfo{ Height: 80, CertificateID: common.HexToHash("0x1321111"), NewLocalExitRoot: common.HexToHash("0x131122233"), @@ -1187,7 +1183,7 @@ func TestSendCertificate(t *testing.T) { name: "error getting L1 Info tree merkle proof from index to root", shouldSendCertificate: []interface{}{[]*aggsendertypes.CertificateInfo{}, nil}, lastL2BlockProcessed: []interface{}{uint64(89), nil}, - getLastSentCertificate: []interface{}{aggsendertypes.CertificateInfo{ + getLastSentCertificate: []interface{}{&aggsendertypes.CertificateInfo{ Height: 80, CertificateID: common.HexToHash("0x1321111"), NewLocalExitRoot: common.HexToHash("0x131122233"), @@ -1226,7 +1222,7 @@ func TestSendCertificate(t *testing.T) { name: "send certificate error", shouldSendCertificate: []interface{}{[]*aggsendertypes.CertificateInfo{}, nil}, lastL2BlockProcessed: []interface{}{uint64(99), nil}, - getLastSentCertificate: []interface{}{aggsendertypes.CertificateInfo{ + getLastSentCertificate: []interface{}{&aggsendertypes.CertificateInfo{ Height: 90, CertificateID: common.HexToHash("0x1121111"), NewLocalExitRoot: common.HexToHash("0x111122211"), @@ -1253,7 +1249,7 @@ func TestSendCertificate(t *testing.T) { name: "store last sent certificate error", shouldSendCertificate: []interface{}{[]*aggsendertypes.CertificateInfo{}, nil}, lastL2BlockProcessed: []interface{}{uint64(109), nil}, - getLastSentCertificate: []interface{}{aggsendertypes.CertificateInfo{ + getLastSentCertificate: []interface{}{&aggsendertypes.CertificateInfo{ Height: 100, CertificateID: common.HexToHash("0x11121111"), NewLocalExitRoot: common.HexToHash("0x1211122211"), @@ -1281,7 +1277,7 @@ func TestSendCertificate(t *testing.T) { name: "successful sending of certificate", shouldSendCertificate: []interface{}{[]*aggsendertypes.CertificateInfo{}, nil}, lastL2BlockProcessed: []interface{}{uint64(119), nil}, - getLastSentCertificate: []interface{}{aggsendertypes.CertificateInfo{ + getLastSentCertificate: []interface{}{&aggsendertypes.CertificateInfo{ Height: 110, CertificateID: common.HexToHash("0x12121111"), NewLocalExitRoot: common.HexToHash("0x1221122211"), @@ -1570,8 +1566,8 @@ func TestSendCertificate_NoClaims(t *testing.T) { }, } - mockStorage.On("GetCertificatesByStatus", nonSettledStatuses).Return([]*aggsendertypes.CertificateInfo{}, nil).Once() - mockStorage.On("GetLastSentCertificate").Return(aggsendertypes.CertificateInfo{ + mockStorage.On("GetCertificatesByStatus", agglayer.NonSettledStatuses).Return([]*aggsendertypes.CertificateInfo{}, nil).Once() + mockStorage.On("GetLastSentCertificate").Return(&aggsendertypes.CertificateInfo{ NewLocalExitRoot: common.HexToHash("0x123"), Height: 1, FromBlock: 0, @@ -1611,3 +1607,269 @@ func TestSendCertificate_NoClaims(t *testing.T) { mockAggLayerClient.AssertExpectations(t) mockL1InfoTreeSyncer.AssertExpectations(t) } + +func TestMetadataConversions(t *testing.T) { + toBlock := uint64(123567890) + c := createCertificateMetadata(toBlock) + extractBlock := extractFromCertificateMetadataToBlock(c) + require.Equal(t, toBlock, extractBlock) +} + +func TestExtractFromCertificateMetadataToBlock(t *testing.T) { + t.Parallel() + + tests := []struct { + name string + metadata common.Hash + expected uint64 + }{ + { + name: "Valid metadata", + metadata: common.BigToHash(big.NewInt(123567890)), + expected: 123567890, + }, + { + name: "Zero metadata", + metadata: common.BigToHash(big.NewInt(0)), + expected: 0, + }, + { + name: "Max uint64 metadata", + metadata: common.BigToHash(new(big.Int).SetUint64(^uint64(0))), + expected: ^uint64(0), + }, + } + + for _, tt := range tests { + tt := tt + + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + + result := extractFromCertificateMetadataToBlock(tt.metadata) + require.Equal(t, tt.expected, result) + }) + } +} + +func TestCheckLastCertificateFromAgglayer_ErrorAggLayer(t *testing.T) { + testData := newAggsenderTestData(t, testDataFlagMockStorage) + testData.l2syncerMock.EXPECT().OriginNetwork().Return(networkIDTest).Once() + testData.agglayerClientMock.EXPECT().GetLatestKnownCertificateHeader(networkIDTest).Return(nil, fmt.Errorf("unittest error")).Once() + + err := testData.sut.checkLastCertificateFromAgglayer(testData.ctx) + + require.Error(t, err) +} + +func TestCheckLastCertificateFromAgglayer_ErrorStorageGetLastSentCertificate(t *testing.T) { + testData := newAggsenderTestData(t, testDataFlagMockStorage) + testData.l2syncerMock.EXPECT().OriginNetwork().Return(networkIDTest).Once() + testData.agglayerClientMock.EXPECT().GetLatestKnownCertificateHeader(networkIDTest).Return(nil, nil).Once() + testData.storageMock.EXPECT().GetLastSentCertificate().Return(nil, fmt.Errorf("unittest error")) + + err := testData.sut.checkLastCertificateFromAgglayer(testData.ctx) + + require.Error(t, err) +} + +// TestCheckLastCertificateFromAgglayer_Case1NoCerts +// CASE 1: No certificates in local storage and agglayer +// Aggsender and agglayer are empty so it's ok +func TestCheckLastCertificateFromAgglayer_Case1NoCerts(t *testing.T) { + testData := newAggsenderTestData(t, testDataFlagNone) + testData.l2syncerMock.EXPECT().OriginNetwork().Return(networkIDTest).Once() + testData.agglayerClientMock.EXPECT().GetLatestKnownCertificateHeader(networkIDTest).Return(nil, nil).Once() + + err := testData.sut.checkLastCertificateFromAgglayer(testData.ctx) + + require.NoError(t, err) +} + +// TestCheckLastCertificateFromAgglayer_Case2NoCertLocalCertRemote +// CASE 2: No certificates in local storage but agglayer has one +// The local DB is empty and we set the lastCert reported by AggLayer +func TestCheckLastCertificateFromAgglayer_Case2NoCertLocalCertRemote(t *testing.T) { + testData := newAggsenderTestData(t, testDataFlagNone) + testData.l2syncerMock.EXPECT().OriginNetwork().Return(networkIDTest).Once() + testData.agglayerClientMock.EXPECT().GetLatestKnownCertificateHeader(networkIDTest). + Return(certInfoToCertHeader(&testData.testCerts[0], networkIDTest), nil).Once() + + err := testData.sut.checkLastCertificateFromAgglayer(testData.ctx) + + require.NoError(t, err) + localCert, err := testData.sut.storage.GetLastSentCertificate() + require.NoError(t, err) + require.Equal(t, testData.testCerts[0].CertificateID, localCert.CertificateID) +} + +// TestCheckLastCertificateFromAgglayer_Case2NoCertLocalCertRemoteErrorStorage +// sub case of previous one that fails to update local storage +func TestCheckLastCertificateFromAgglayer_Case2NoCertLocalCertRemoteErrorStorage(t *testing.T) { + testData := newAggsenderTestData(t, testDataFlagMockStorage) + testData.l2syncerMock.EXPECT().OriginNetwork().Return(networkIDTest).Once() + testData.agglayerClientMock.EXPECT().GetLatestKnownCertificateHeader(networkIDTest). + Return(certInfoToCertHeader(&testData.testCerts[0], networkIDTest), nil).Once() + testData.storageMock.EXPECT().GetLastSentCertificate().Return(nil, nil) + testData.storageMock.EXPECT().SaveLastSentCertificate(mock.Anything, mock.Anything).Return(errTest).Once() + err := testData.sut.checkLastCertificateFromAgglayer(testData.ctx) + + require.Error(t, err) +} + +// CASE 3: AggSender and AggLayer not same certificateID. AggLayer has a new certificate +func TestCheckLastCertificateFromAgglayer_Case3Mismatch(t *testing.T) { + testData := newAggsenderTestData(t, testDataFlagMockStorage) + testData.l2syncerMock.EXPECT().OriginNetwork().Return(networkIDTest).Once() + testData.agglayerClientMock.EXPECT().GetLatestKnownCertificateHeader(networkIDTest). + Return(certInfoToCertHeader(&testData.testCerts[1], networkIDTest), nil).Once() + testData.storageMock.EXPECT().GetLastSentCertificate().Return(&testData.testCerts[0], nil) + testData.storageMock.EXPECT().SaveLastSentCertificate(mock.Anything, mock.Anything).Return(nil).Once() + + err := testData.sut.checkLastCertificateFromAgglayer(testData.ctx) + + require.NoError(t, err) +} + +// CASE 4: AggSender and AggLayer not same certificateID +func TestCheckLastCertificateFromAgglayer_Case4Mismatch(t *testing.T) { + testData := newAggsenderTestData(t, testDataFlagMockStorage) + testData.l2syncerMock.EXPECT().OriginNetwork().Return(networkIDTest).Once() + testData.agglayerClientMock.EXPECT().GetLatestKnownCertificateHeader(networkIDTest). + Return(certInfoToCertHeader(&testData.testCerts[0], networkIDTest), nil).Once() + testData.storageMock.EXPECT().GetLastSentCertificate().Return(&testData.testCerts[1], nil) + + err := testData.sut.checkLastCertificateFromAgglayer(testData.ctx) + + require.Error(t, err) +} + +// CASE 5: AggSender and AggLayer same certificateID and same status +func TestCheckLastCertificateFromAgglayer_Case5SameStatus(t *testing.T) { + testData := newAggsenderTestData(t, testDataFlagMockStorage) + testData.l2syncerMock.EXPECT().OriginNetwork().Return(networkIDTest).Once() + testData.agglayerClientMock.EXPECT().GetLatestKnownCertificateHeader(networkIDTest). + Return(certInfoToCertHeader(&testData.testCerts[0], networkIDTest), nil).Once() + testData.storageMock.EXPECT().GetLastSentCertificate().Return(&testData.testCerts[0], nil) + + err := testData.sut.checkLastCertificateFromAgglayer(testData.ctx) + + require.NoError(t, err) +} + +// CASE 5: AggSender and AggLayer same certificateID and differ on status +func TestCheckLastCertificateFromAgglayer_Case5UpdateStatus(t *testing.T) { + testData := newAggsenderTestData(t, testDataFlagMockStorage) + testData.l2syncerMock.EXPECT().OriginNetwork().Return(networkIDTest).Once() + aggLayerCert := certInfoToCertHeader(&testData.testCerts[0], networkIDTest) + aggLayerCert.Status = agglayer.Settled + testData.agglayerClientMock.EXPECT().GetLatestKnownCertificateHeader(networkIDTest). + Return(aggLayerCert, nil).Once() + testData.storageMock.EXPECT().GetLastSentCertificate().Return(&testData.testCerts[0], nil) + testData.storageMock.EXPECT().UpdateCertificate(mock.Anything, mock.Anything).Return(nil).Once() + + err := testData.sut.checkLastCertificateFromAgglayer(testData.ctx) + + require.NoError(t, err) +} + +// CASE 4: AggSender and AggLayer same certificateID and differ on status but fails update +func TestCheckLastCertificateFromAgglayer_Case4ErrorUpdateStatus(t *testing.T) { + testData := newAggsenderTestData(t, testDataFlagMockStorage) + testData.l2syncerMock.EXPECT().OriginNetwork().Return(networkIDTest).Once() + aggLayerCert := certInfoToCertHeader(&testData.testCerts[0], networkIDTest) + aggLayerCert.Status = agglayer.Settled + testData.agglayerClientMock.EXPECT().GetLatestKnownCertificateHeader(networkIDTest). + Return(aggLayerCert, nil).Once() + testData.storageMock.EXPECT().GetLastSentCertificate().Return(&testData.testCerts[0], nil) + testData.storageMock.EXPECT().UpdateCertificate(mock.Anything, mock.Anything).Return(errTest).Once() + + err := testData.sut.checkLastCertificateFromAgglayer(testData.ctx) + + require.Error(t, err) +} + +type testDataFlags = int + +const ( + testDataFlagNone testDataFlags = 0 + testDataFlagMockStorage testDataFlags = 1 +) + +type aggsenderTestData struct { + ctx context.Context + agglayerClientMock *agglayer.AgglayerClientMock + l2syncerMock *mocks.L2BridgeSyncer + l1InfoTreeSyncerMock *mocks.L1InfoTreeSyncer + storageMock *mocks.AggSenderStorage + sut *AggSender + testCerts []aggsendertypes.CertificateInfo +} + +func certInfoToCertHeader(certInfo *aggsendertypes.CertificateInfo, networkID uint32) *agglayer.CertificateHeader { + if certInfo == nil { + return nil + } + return &agglayer.CertificateHeader{ + Height: certInfo.Height, + NetworkID: networkID, + CertificateID: certInfo.CertificateID, + NewLocalExitRoot: certInfo.NewLocalExitRoot, + Status: agglayer.Pending, + Metadata: createCertificateMetadata(certInfo.ToBlock), + } +} + +func newAggsenderTestData(t *testing.T, creationFlags testDataFlags) *aggsenderTestData { + t.Helper() + l2syncerMock := mocks.NewL2BridgeSyncer(t) + agglayerClientMock := agglayer.NewAgglayerClientMock(t) + l1InfoTreeSyncerMock := mocks.NewL1InfoTreeSyncer(t) + logger := log.WithFields("aggsender-test", "checkLastCertificateFromAgglayer") + var storageMock *mocks.AggSenderStorage + var storage db.AggSenderStorage + var err error + if creationFlags&testDataFlagMockStorage != 0 { + storageMock = mocks.NewAggSenderStorage(t) + storage = storageMock + } else { + pc, _, _, _ := runtime.Caller(1) + part := runtime.FuncForPC(pc) + dbPath := fmt.Sprintf("file:%d?mode=memory&cache=shared", part.Entry()) + storage, err = db.NewAggSenderSQLStorage(logger, dbPath) + require.NoError(t, err) + } + + ctx := context.TODO() + sut := &AggSender{ + log: logger, + l2Syncer: l2syncerMock, + aggLayerClient: agglayerClientMock, + storage: storage, + l1infoTreeSyncer: l1InfoTreeSyncerMock, + } + testCerts := []aggsendertypes.CertificateInfo{ + { + Height: 1, + CertificateID: common.HexToHash("0x1"), + NewLocalExitRoot: common.HexToHash("0x2"), + Status: agglayer.Pending, + }, + { + Height: 2, + CertificateID: common.HexToHash("0x1a111"), + NewLocalExitRoot: common.HexToHash("0x2a2"), + Status: agglayer.Pending, + }, + } + + return &aggsenderTestData{ + ctx: ctx, + agglayerClientMock: agglayerClientMock, + l2syncerMock: l2syncerMock, + l1InfoTreeSyncerMock: l1InfoTreeSyncerMock, + storageMock: storageMock, + sut: sut, + testCerts: testCerts, + } +} diff --git a/aggsender/block_notifier_polling_test.go b/aggsender/block_notifier_polling_test.go index 83b3b643..e4f15ad7 100644 --- a/aggsender/block_notifier_polling_test.go +++ b/aggsender/block_notifier_polling_test.go @@ -32,11 +32,8 @@ func TestExploratoryBlockNotifierPolling(t *testing.T) { require.NoError(t, errSut) go sut.Start(context.Background()) ch := sut.Subscribe("test") - for { - select { - case block := <-ch: - fmt.Println(block) - } + for block := range ch { + fmt.Println(block) } } diff --git a/aggsender/config.go b/aggsender/config.go index 8ae0b759..b36fbe7a 100644 --- a/aggsender/config.go +++ b/aggsender/config.go @@ -29,6 +29,13 @@ type Config struct { EpochNotificationPercentage uint `mapstructure:"EpochNotificationPercentage"` // SaveCertificatesToFilesPath if != "" tells the AggSender to save the certificates to a file in this path SaveCertificatesToFilesPath string `mapstructure:"SaveCertificatesToFilesPath"` + + // MaxRetriesStoreCertificate is the maximum number of retries to store a certificate + // 0 is infinite + MaxRetriesStoreCertificate int `mapstructure:"MaxRetriesStoreCertificate"` + // DelayBeetweenRetries is the delay between retries: + // is used on store Certificate and also in initial check + DelayBeetweenRetries types.Duration `mapstructure:"DelayBeetweenRetries"` } // String returns a string representation of the Config @@ -38,7 +45,6 @@ func (c Config) String() string { "BlockGetInterval: " + c.BlockGetInterval.String() + "\n" + "CheckSettledInterval: " + c.CheckSettledInterval.String() + "\n" + "AggsenderPrivateKeyPath: " + c.AggsenderPrivateKey.Path + "\n" + - "AggsenderPrivateKeyPassword: " + c.AggsenderPrivateKey.Password + "\n" + "URLRPCL2: " + c.URLRPCL2 + "\n" + "BlockFinality: " + c.BlockFinality + "\n" + "EpochNotificationPercentage: " + fmt.Sprintf("%d", c.EpochNotificationPercentage) + "\n" + diff --git a/aggsender/db/aggsender_db_storage.go b/aggsender/db/aggsender_db_storage.go index 15866c29..00440f4a 100644 --- a/aggsender/db/aggsender_db_storage.go +++ b/aggsender/db/aggsender_db_storage.go @@ -21,17 +21,17 @@ const errWhileRollbackFormat = "error while rolling back tx: %w" // AggSenderStorage is the interface that defines the methods to interact with the storage type AggSenderStorage interface { // GetCertificateByHeight returns a certificate by its height - GetCertificateByHeight(height uint64) (types.CertificateInfo, error) + GetCertificateByHeight(height uint64) (*types.CertificateInfo, error) // GetLastSentCertificate returns the last certificate sent to the aggLayer - GetLastSentCertificate() (types.CertificateInfo, error) + GetLastSentCertificate() (*types.CertificateInfo, error) // SaveLastSentCertificate saves the last certificate sent to the aggLayer SaveLastSentCertificate(ctx context.Context, certificate types.CertificateInfo) error // DeleteCertificate deletes a certificate from the storage DeleteCertificate(ctx context.Context, certificateID common.Hash) error // GetCertificatesByStatus returns a list of certificates by their status GetCertificatesByStatus(status []agglayer.CertificateStatus) ([]*types.CertificateInfo, error) - // UpdateCertificateStatus updates the status of a certificate - UpdateCertificateStatus(ctx context.Context, certificate types.CertificateInfo) error + // UpdateCertificate updates certificate in db + UpdateCertificate(ctx context.Context, certificate types.CertificateInfo) error } var _ AggSenderStorage = (*AggSenderSQLStorage)(nil) @@ -88,31 +88,31 @@ func (a *AggSenderSQLStorage) GetCertificatesByStatus( } // GetCertificateByHeight returns a certificate by its height -func (a *AggSenderSQLStorage) GetCertificateByHeight(height uint64) (types.CertificateInfo, error) { +func (a *AggSenderSQLStorage) GetCertificateByHeight(height uint64) (*types.CertificateInfo, error) { return getCertificateByHeight(a.db, height) } // getCertificateByHeight returns a certificate by its height using the provided db func getCertificateByHeight(db meddler.DB, - height uint64) (types.CertificateInfo, error) { + height uint64) (*types.CertificateInfo, error) { var certificateInfo types.CertificateInfo if err := meddler.QueryRow(db, &certificateInfo, "SELECT * FROM certificate_info WHERE height = $1;", height); err != nil { - return types.CertificateInfo{}, getSelectQueryError(height, err) + return nil, getSelectQueryError(height, err) } - return certificateInfo, nil + return &certificateInfo, nil } // GetLastSentCertificate returns the last certificate sent to the aggLayer -func (a *AggSenderSQLStorage) GetLastSentCertificate() (types.CertificateInfo, error) { +func (a *AggSenderSQLStorage) GetLastSentCertificate() (*types.CertificateInfo, error) { var certificateInfo types.CertificateInfo if err := meddler.QueryRow(a.db, &certificateInfo, "SELECT * FROM certificate_info ORDER BY height DESC LIMIT 1;"); err != nil { - return types.CertificateInfo{}, getSelectQueryError(0, err) + return nil, getSelectQueryError(0, err) } - return certificateInfo, nil + return &certificateInfo, nil } // SaveLastSentCertificate saves the last certificate sent to the aggLayer @@ -134,7 +134,7 @@ func (a *AggSenderSQLStorage) SaveLastSentCertificate(ctx context.Context, certi return err } - if cert.CertificateID != (common.Hash{}) { + if cert != nil { // we already have a certificate with this height // we need to delete it before inserting the new one if err = deleteCertificate(tx, cert.CertificateID); err != nil { @@ -191,8 +191,8 @@ func deleteCertificate(db meddler.DB, certificateID common.Hash) error { return nil } -// UpdateCertificateStatus updates the status of a certificate -func (a *AggSenderSQLStorage) UpdateCertificateStatus(ctx context.Context, certificate types.CertificateInfo) error { +// UpdateCertificate updates a certificate +func (a *AggSenderSQLStorage) UpdateCertificate(ctx context.Context, certificate types.CertificateInfo) error { tx, err := db.NewTx(ctx, a.db) if err != nil { return err diff --git a/aggsender/db/aggsender_db_storage_test.go b/aggsender/db/aggsender_db_storage_test.go index a0a20894..642c8ae7 100644 --- a/aggsender/db/aggsender_db_storage_test.go +++ b/aggsender/db/aggsender_db_storage_test.go @@ -45,7 +45,7 @@ func Test_Storage(t *testing.T) { certificateFromDB, err := storage.GetCertificateByHeight(certificate.Height) require.NoError(t, err) - require.Equal(t, certificate, certificateFromDB) + require.Equal(t, certificate, *certificateFromDB) require.NoError(t, storage.clean()) }) @@ -66,7 +66,7 @@ func Test_Storage(t *testing.T) { certificateFromDB, err := storage.GetCertificateByHeight(certificate.Height) require.ErrorIs(t, err, db.ErrNotFound) - require.Equal(t, types.CertificateInfo{}, certificateFromDB) + require.Nil(t, certificateFromDB) require.NoError(t, storage.clean()) }) @@ -74,7 +74,7 @@ func Test_Storage(t *testing.T) { // try getting a certificate that doesn't exist certificateFromDB, err := storage.GetLastSentCertificate() require.NoError(t, err) - require.Equal(t, types.CertificateInfo{}, certificateFromDB) + require.Nil(t, certificateFromDB) // try getting a certificate that exists certificate := types.CertificateInfo{ @@ -91,8 +91,8 @@ func Test_Storage(t *testing.T) { certificateFromDB, err = storage.GetLastSentCertificate() require.NoError(t, err) - - require.Equal(t, certificate, certificateFromDB) + require.NotNil(t, certificateFromDB) + require.Equal(t, certificate, *certificateFromDB) require.NoError(t, storage.clean()) }) @@ -100,12 +100,12 @@ func Test_Storage(t *testing.T) { // try getting height 0 certificateFromDB, err := storage.GetCertificateByHeight(0) require.NoError(t, err) - require.Equal(t, types.CertificateInfo{}, certificateFromDB) + require.Nil(t, certificateFromDB) // try getting a certificate that doesn't exist certificateFromDB, err = storage.GetCertificateByHeight(4) require.ErrorIs(t, err, db.ErrNotFound) - require.Equal(t, types.CertificateInfo{}, certificateFromDB) + require.Nil(t, certificateFromDB) // try getting a certificate that exists certificate := types.CertificateInfo{ @@ -122,8 +122,8 @@ func Test_Storage(t *testing.T) { certificateFromDB, err = storage.GetCertificateByHeight(certificate.Height) require.NoError(t, err) - - require.Equal(t, certificate, certificateFromDB) + require.NotNil(t, certificateFromDB) + require.Equal(t, certificate, *certificateFromDB) require.NoError(t, storage.clean()) }) @@ -213,7 +213,7 @@ func Test_Storage(t *testing.T) { // Update the status of the certificate certificate.Status = agglayer.Settled - require.NoError(t, storage.UpdateCertificateStatus(ctx, certificate)) + require.NoError(t, storage.UpdateCertificate(ctx, certificate)) // Fetch the certificate and verify the status has been updated certificateFromDB, err := storage.GetCertificateByHeight(certificate.Height) @@ -251,7 +251,7 @@ func Test_SaveLastSentCertificate(t *testing.T) { certificateFromDB, err := storage.GetCertificateByHeight(certificate.Height) require.NoError(t, err) - require.Equal(t, certificate, certificateFromDB) + require.Equal(t, certificate, *certificateFromDB) require.NoError(t, storage.clean()) }) @@ -281,7 +281,7 @@ func Test_SaveLastSentCertificate(t *testing.T) { certificateFromDB, err := storage.GetCertificateByHeight(updatedCertificate.Height) require.NoError(t, err) - require.Equal(t, updatedCertificate, certificateFromDB) + require.Equal(t, updatedCertificate, *certificateFromDB) require.NoError(t, storage.clean()) }) @@ -310,7 +310,7 @@ func Test_SaveLastSentCertificate(t *testing.T) { certificateFromDB, err := storage.GetCertificateByHeight(certificate.Height) require.ErrorIs(t, err, db.ErrNotFound) - require.Equal(t, types.CertificateInfo{}, certificateFromDB) + require.Nil(t, certificateFromDB) require.NoError(t, storage.clean()) }) @@ -362,7 +362,7 @@ func Test_SaveLastSentCertificate(t *testing.T) { certificateFromDB, err := storage.GetCertificateByHeight(certificate.Height) require.NoError(t, err) - require.Equal(t, certificate, certificateFromDB) + require.Equal(t, certificate, *certificateFromDB) require.Equal(t, raw, []byte(certificateFromDB.SignedCertificate)) require.NoError(t, storage.clean()) diff --git a/aggsender/mocks/agg_sender_storage.go b/aggsender/mocks/agg_sender_storage.go index 1816d4a3..b6337180 100644 --- a/aggsender/mocks/agg_sender_storage.go +++ b/aggsender/mocks/agg_sender_storage.go @@ -74,22 +74,24 @@ func (_c *AggSenderStorage_DeleteCertificate_Call) RunAndReturn(run func(context } // GetCertificateByHeight provides a mock function with given fields: height -func (_m *AggSenderStorage) GetCertificateByHeight(height uint64) (types.CertificateInfo, error) { +func (_m *AggSenderStorage) GetCertificateByHeight(height uint64) (*types.CertificateInfo, error) { ret := _m.Called(height) if len(ret) == 0 { panic("no return value specified for GetCertificateByHeight") } - var r0 types.CertificateInfo + var r0 *types.CertificateInfo var r1 error - if rf, ok := ret.Get(0).(func(uint64) (types.CertificateInfo, error)); ok { + if rf, ok := ret.Get(0).(func(uint64) (*types.CertificateInfo, error)); ok { return rf(height) } - if rf, ok := ret.Get(0).(func(uint64) types.CertificateInfo); ok { + if rf, ok := ret.Get(0).(func(uint64) *types.CertificateInfo); ok { r0 = rf(height) } else { - r0 = ret.Get(0).(types.CertificateInfo) + if ret.Get(0) != nil { + r0 = ret.Get(0).(*types.CertificateInfo) + } } if rf, ok := ret.Get(1).(func(uint64) error); ok { @@ -119,12 +121,12 @@ func (_c *AggSenderStorage_GetCertificateByHeight_Call) Run(run func(height uint return _c } -func (_c *AggSenderStorage_GetCertificateByHeight_Call) Return(_a0 types.CertificateInfo, _a1 error) *AggSenderStorage_GetCertificateByHeight_Call { +func (_c *AggSenderStorage_GetCertificateByHeight_Call) Return(_a0 *types.CertificateInfo, _a1 error) *AggSenderStorage_GetCertificateByHeight_Call { _c.Call.Return(_a0, _a1) return _c } -func (_c *AggSenderStorage_GetCertificateByHeight_Call) RunAndReturn(run func(uint64) (types.CertificateInfo, error)) *AggSenderStorage_GetCertificateByHeight_Call { +func (_c *AggSenderStorage_GetCertificateByHeight_Call) RunAndReturn(run func(uint64) (*types.CertificateInfo, error)) *AggSenderStorage_GetCertificateByHeight_Call { _c.Call.Return(run) return _c } @@ -188,22 +190,24 @@ func (_c *AggSenderStorage_GetCertificatesByStatus_Call) RunAndReturn(run func([ } // GetLastSentCertificate provides a mock function with given fields: -func (_m *AggSenderStorage) GetLastSentCertificate() (types.CertificateInfo, error) { +func (_m *AggSenderStorage) GetLastSentCertificate() (*types.CertificateInfo, error) { ret := _m.Called() if len(ret) == 0 { panic("no return value specified for GetLastSentCertificate") } - var r0 types.CertificateInfo + var r0 *types.CertificateInfo var r1 error - if rf, ok := ret.Get(0).(func() (types.CertificateInfo, error)); ok { + if rf, ok := ret.Get(0).(func() (*types.CertificateInfo, error)); ok { return rf() } - if rf, ok := ret.Get(0).(func() types.CertificateInfo); ok { + if rf, ok := ret.Get(0).(func() *types.CertificateInfo); ok { r0 = rf() } else { - r0 = ret.Get(0).(types.CertificateInfo) + if ret.Get(0) != nil { + r0 = ret.Get(0).(*types.CertificateInfo) + } } if rf, ok := ret.Get(1).(func() error); ok { @@ -232,12 +236,12 @@ func (_c *AggSenderStorage_GetLastSentCertificate_Call) Run(run func()) *AggSend return _c } -func (_c *AggSenderStorage_GetLastSentCertificate_Call) Return(_a0 types.CertificateInfo, _a1 error) *AggSenderStorage_GetLastSentCertificate_Call { +func (_c *AggSenderStorage_GetLastSentCertificate_Call) Return(_a0 *types.CertificateInfo, _a1 error) *AggSenderStorage_GetLastSentCertificate_Call { _c.Call.Return(_a0, _a1) return _c } -func (_c *AggSenderStorage_GetLastSentCertificate_Call) RunAndReturn(run func() (types.CertificateInfo, error)) *AggSenderStorage_GetLastSentCertificate_Call { +func (_c *AggSenderStorage_GetLastSentCertificate_Call) RunAndReturn(run func() (*types.CertificateInfo, error)) *AggSenderStorage_GetLastSentCertificate_Call { _c.Call.Return(run) return _c } @@ -289,12 +293,12 @@ func (_c *AggSenderStorage_SaveLastSentCertificate_Call) RunAndReturn(run func(c return _c } -// UpdateCertificateStatus provides a mock function with given fields: ctx, certificate -func (_m *AggSenderStorage) UpdateCertificateStatus(ctx context.Context, certificate types.CertificateInfo) error { +// UpdateCertificate provides a mock function with given fields: ctx, certificate +func (_m *AggSenderStorage) UpdateCertificate(ctx context.Context, certificate types.CertificateInfo) error { ret := _m.Called(ctx, certificate) if len(ret) == 0 { - panic("no return value specified for UpdateCertificateStatus") + panic("no return value specified for UpdateCertificate") } var r0 error @@ -307,31 +311,31 @@ func (_m *AggSenderStorage) UpdateCertificateStatus(ctx context.Context, certifi return r0 } -// AggSenderStorage_UpdateCertificateStatus_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'UpdateCertificateStatus' -type AggSenderStorage_UpdateCertificateStatus_Call struct { +// AggSenderStorage_UpdateCertificate_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'UpdateCertificate' +type AggSenderStorage_UpdateCertificate_Call struct { *mock.Call } -// UpdateCertificateStatus is a helper method to define mock.On call +// UpdateCertificate is a helper method to define mock.On call // - ctx context.Context // - certificate types.CertificateInfo -func (_e *AggSenderStorage_Expecter) UpdateCertificateStatus(ctx interface{}, certificate interface{}) *AggSenderStorage_UpdateCertificateStatus_Call { - return &AggSenderStorage_UpdateCertificateStatus_Call{Call: _e.mock.On("UpdateCertificateStatus", ctx, certificate)} +func (_e *AggSenderStorage_Expecter) UpdateCertificate(ctx interface{}, certificate interface{}) *AggSenderStorage_UpdateCertificate_Call { + return &AggSenderStorage_UpdateCertificate_Call{Call: _e.mock.On("UpdateCertificate", ctx, certificate)} } -func (_c *AggSenderStorage_UpdateCertificateStatus_Call) Run(run func(ctx context.Context, certificate types.CertificateInfo)) *AggSenderStorage_UpdateCertificateStatus_Call { +func (_c *AggSenderStorage_UpdateCertificate_Call) Run(run func(ctx context.Context, certificate types.CertificateInfo)) *AggSenderStorage_UpdateCertificate_Call { _c.Call.Run(func(args mock.Arguments) { run(args[0].(context.Context), args[1].(types.CertificateInfo)) }) return _c } -func (_c *AggSenderStorage_UpdateCertificateStatus_Call) Return(_a0 error) *AggSenderStorage_UpdateCertificateStatus_Call { +func (_c *AggSenderStorage_UpdateCertificate_Call) Return(_a0 error) *AggSenderStorage_UpdateCertificate_Call { _c.Call.Return(_a0) return _c } -func (_c *AggSenderStorage_UpdateCertificateStatus_Call) RunAndReturn(run func(context.Context, types.CertificateInfo) error) *AggSenderStorage_UpdateCertificateStatus_Call { +func (_c *AggSenderStorage_UpdateCertificate_Call) RunAndReturn(run func(context.Context, types.CertificateInfo) error) *AggSenderStorage_UpdateCertificate_Call { _c.Call.Return(run) return _c } diff --git a/aggsender/types/types.go b/aggsender/types/types.go index d9e0b2e7..d6f2650e 100644 --- a/aggsender/types/types.go +++ b/aggsender/types/types.go @@ -43,6 +43,7 @@ type EthClient interface { // Logger is an interface that defines the methods to log messages type Logger interface { + Fatalf(format string, args ...interface{}) Info(args ...interface{}) Infof(format string, args ...interface{}) Error(args ...interface{}) @@ -65,23 +66,50 @@ type CertificateInfo struct { SignedCertificate string `meddler:"signed_certificate"` } -func (c CertificateInfo) String() string { +func (c *CertificateInfo) String() string { + if c == nil { + return "nil" + } return fmt.Sprintf( - "Height: %d\n"+ - "CertificateID: %s\n"+ - "FromBlock: %d\n"+ - "ToBlock: %d\n"+ - "NewLocalExitRoot: %s\n"+ - "Status: %s\n"+ - "CreatedAt: %s\n"+ - "UpdatedAt: %s\n", + "Height: %d "+ + "CertificateID: %s "+ + "NewLocalExitRoot: %s "+ + "Status: %s "+ + "FromBlock: %d "+ + "ToBlock: %d "+ + "CreatedAt: %s "+ + "UpdatedAt: %s", c.Height, c.CertificateID.String(), - c.FromBlock, - c.ToBlock, c.NewLocalExitRoot.String(), c.Status.String(), + c.FromBlock, + c.ToBlock, time.UnixMilli(c.CreatedAt), time.UnixMilli(c.UpdatedAt), ) } + +// ID returns a string with the ident of this cert (height/certID) +func (c *CertificateInfo) ID() string { + if c == nil { + return "nil" + } + return fmt.Sprintf("%d/%s", c.Height, c.CertificateID.String()) +} + +// IsClosed returns true if the certificate is closed (settled or inError) +func (c *CertificateInfo) IsClosed() bool { + if c == nil { + return false + } + return c.Status.IsClosed() +} + +// ElapsedTimeSinceCreation returns the time elapsed since the certificate was created +func (c *CertificateInfo) ElapsedTimeSinceCreation() time.Duration { + if c == nil { + return 0 + } + return time.Now().UTC().Sub(time.UnixMilli(c.CreatedAt)) +} diff --git a/config/default.go b/config/default.go index 316cfb76..b1cfe16e 100644 --- a/config/default.go +++ b/config/default.go @@ -213,7 +213,7 @@ SyncBlockChunkSize=100 BlockFinality="LatestBlock" URLRPCL1="{{L1URL}}" WaitForNewBlocksPeriod="100ms" -InitialBlock=0 +InitialBlock={{genesisBlockNumber}} [AggOracle] TargetChainType="EVM" @@ -238,7 +238,7 @@ WaitPeriodNextGER="100ms" ForcedGas = 0 GasPriceMarginFactor = 1 MaxGasPriceLimit = 0 - StoragePath = "/{{PathRWData}}/ethtxmanager-sequencesender.sqlite" + StoragePath = "{{PathRWData}}/ethtxmanager-sequencesender.sqlite" ReadPendingL1Txs = false SafeStatusL1NumberOfBlocks = 5 FinalizedStatusL1NumberOfBlocks = 10 @@ -256,7 +256,7 @@ WriteTimeout = "2s" MaxRequestsPerIPAndSecond = 10 [ClaimSponsor] -DBPath = "/{{PathRWData}}/claimsopnsor.sqlite" +DBPath = "{{PathRWData}}/claimsopnsor.sqlite" Enabled = true SenderAddr = "0xfa3b44587990f97ba8b6ba7e230a5f0e95d14b3d" BridgeAddrL2 = "0xB7098a13a48EcE087d3DA15b2D28eCE0f89819B8" @@ -277,7 +277,7 @@ GasOffset = 0 ForcedGas = 0 GasPriceMarginFactor = 1 MaxGasPriceLimit = 0 - StoragePath = "/{{PathRWData}}/ethtxmanager-claimsponsor.sqlite" + StoragePath = "{{PathRWData}}/ethtxmanager-claimsponsor.sqlite" ReadPendingL1Txs = false SafeStatusL1NumberOfBlocks = 5 FinalizedStatusL1NumberOfBlocks = 10 @@ -337,4 +337,6 @@ CheckSettledInterval = "2s" BlockFinality = "LatestBlock" EpochNotificationPercentage = 50 SaveCertificatesToFilesPath = "" +MaxRetriesStoreCertificate = 3 +DelayBeetweenRetries = "60s" ` diff --git a/scripts/local_config b/scripts/local_config index ca25dbbb..90b5ae11 100755 --- a/scripts/local_config +++ b/scripts/local_config @@ -3,7 +3,7 @@ source $(dirname $0)/../test/scripts/env.sh ############################################################################### function log_debug() { - echo -e "\033[0;30mDebug: $*" "\033[0m" + echo -e "\033[0;90mDebug: $*" "\033[0m" } ############################################################################### function log_error() { diff --git a/test/bats/pp/e2e-pp.bats b/test/bats/pp/e2e-pp.bats index 77d7910d..f7963592 100644 --- a/test/bats/pp/e2e-pp.bats +++ b/test/bats/pp/e2e-pp.bats @@ -1,10 +1,25 @@ setup() { load '../../helpers/common-setup' _common_setup + + if [ -z "$BRIDGE_ADDRESS" ]; then + local combined_json_file="/opt/zkevm/combined.json" + echo "BRIDGE_ADDRESS env variable is not provided, resolving the bridge address from the Kurtosis CDK '$combined_json_file'" >&3 + + # Fetching the combined JSON output and filtering to get polygonZkEVMBridgeAddress + combined_json_output=$($contracts_service_wrapper "cat $combined_json_file" | tail -n +2) + bridge_default_address=$(echo "$combined_json_output" | jq -r .polygonZkEVMBridgeAddress) + BRIDGE_ADDRESS=$bridge_default_address + fi + echo "Bridge address=$BRIDGE_ADDRESS" >&3 } -@test "Verify batches" { - echo "Waiting 10 minutes to get some settle certificate...." - run $PROJECT_ROOT/../scripts/agglayer_certificates_monitor.sh 1 600 +@test "Verify certificate settlement" { + echo "Waiting 10 minutes to get some settle certificate...." >&3 + + readonly bridge_addr=$BRIDGE_ADDRESS + readonly l2_rpc_network_id=$(cast call --rpc-url $l2_rpc_url $bridge_addr 'networkID() (uint32)') + + run $PROJECT_ROOT/../scripts/agglayer_certificates_monitor.sh 1 600 $l2_rpc_network_id assert_success } diff --git a/test/combinations/fork12-pessimistic.yml b/test/combinations/fork12-pessimistic.yml index 088822c5..d92375c5 100644 --- a/test/combinations/fork12-pessimistic.yml +++ b/test/combinations/fork12-pessimistic.yml @@ -1,5 +1,5 @@ args: - agglayer_image: ghcr.io/agglayer/agglayer:0.2.0-rc.11 + agglayer_image: ghcr.io/agglayer/agglayer:0.2.0-rc.12 cdk_erigon_node_image: hermeznetwork/cdk-erigon:v2.60.0-beta8 cdk_node_image: cdk zkevm_bridge_proxy_image: haproxy:3.0-bookworm @@ -11,4 +11,5 @@ args: sequencer_type: erigon erigon_strict_mode: false zkevm_use_gas_token_contract: true - enable_normalcy: true \ No newline at end of file + agglayer_prover_sp1_key: {{.agglayer_prover_sp1_key}} + enable_normalcy: true diff --git a/test/run-e2e.sh b/test/run-e2e.sh index 08a6b2cd..adbbcbcb 100755 --- a/test/run-e2e.sh +++ b/test/run-e2e.sh @@ -9,7 +9,7 @@ fi DATA_AVAILABILITY_MODE=$2 if [ -z $DATA_AVAILABILITY_MODE ]; then - echo "Missing DATA_AVAILABILITY_MODE: ['rollup', 'cdk-validium']" + echo "Missing DATA_AVAILABILITY_MODE: ['rollup', 'cdk-validium', 'pessimistic']" exit 1 fi @@ -27,4 +27,9 @@ fi kurtosis clean --all echo "Override cdk config file" cp $BASE_FOLDER/config/kurtosis-cdk-node-config.toml.template $KURTOSIS_FOLDER/templates/trusted-node/cdk-node-config.toml -kurtosis run --enclave cdk --args-file "combinations/$FORK-$DATA_AVAILABILITY_MODE.yml" --image-download always $KURTOSIS_FOLDER +KURTOSIS_CONFIG_FILE="combinations/$FORK-$DATA_AVAILABILITY_MODE.yml" +TEMP_CONFIG_FILE=$(mktemp --suffix ".yml") +echo "rendering $KURTOSIS_CONFIG_FILE to temp file $TEMP_CONFIG_FILE" +go run ../scripts/run_template.go $KURTOSIS_CONFIG_FILE > $TEMP_CONFIG_FILE +kurtosis run --enclave cdk --args-file "$TEMP_CONFIG_FILE" --image-download always $KURTOSIS_FOLDER +rm $TEMP_CONFIG_FILE \ No newline at end of file diff --git a/test/scripts/agglayer_certificates_monitor.sh b/test/scripts/agglayer_certificates_monitor.sh index 47c116b6..c530548f 100755 --- a/test/scripts/agglayer_certificates_monitor.sh +++ b/test/scripts/agglayer_certificates_monitor.sh @@ -3,8 +3,8 @@ function parse_params(){ # Check if the required arguments are provided. - if [ "$#" -lt 2 ]; then - echo "Usage: $0 " + if [ "$#" -lt 3 ]; then + echo "Usage: $0 " exit 1 fi @@ -13,6 +13,9 @@ function parse_params(){ # The script timeout (in seconds). timeout="$2" + + # The network id of the L2 network. + l2_rpc_network_id="$3" } function check_timeout(){ @@ -25,39 +28,55 @@ function check_timeout(){ } function check_num_certificates(){ - local _cmd="echo 'select status, count(*) from certificate_info group by status;' | sqlite3 /tmp/aggsender.sqlite" - local _outcmd=$(mktemp) - kurtosis service exec cdk cdk-node-001 "$_cmd" > $_outcmd + readonly agglayer_rpc_url="$(kurtosis port print cdk agglayer agglayer)" + + cast_output=$(cast rpc --rpc-url "$agglayer_rpc_url" "interop_getLatestKnownCertificateHeader" "$l2_rpc_network_id" 2>&1) + if [ $? -ne 0 ]; then - echo "[$(date '+%Y-%m-%d %H:%M:%S')] Error executing command kurtosis service: $_cmd" - # clean temp file - rm $_outcmd - return + echo "[$(date '+%Y-%m-%d %H:%M:%S')] Error executing command cast rpc: $cast_output" + return fi - local num_certs=$(cat $_outcmd | tail -n +2 | cut -f 2 -d '|' | xargs |tr ' ' '+' | bc) - # Get the number of settled certificates "4|0" - local _num_settle_certs=$(cat $_outcmd | tail -n +2 | grep ^${aggsender_status_settled} | cut -d'|' -f2) - [ -z "$_num_settle_certs" ] && _num_settle_certs=0 - # clean temp file - rm $_outcmd - echo "[$(date '+%Y-%m-%d %H:%M:%S')] Num certificates on aggsender: $num_certs. Settled certificates : $_num_settle_certs" - if [ $num_certs -ge $settle_certificates_target ]; then - echo "[$(date '+%Y-%m-%d %H:%M:%S')] ✅ Exiting... $num_certs certificates were settled! (total certs $num_certs)" + + height=$(extract_certificate_height "$cast_output") + [[ -z "$height" ]] && { + echo "Error: Failed to extract certificate height: $height." >&3 + return + } + + status=$(extract_certificate_status "$cast_output") + [[ -z "$status" ]] && { + echo "Error: Failed to extract certificate status." >&3 + return + } + + echo "[$(date '+%Y-%m-%d %H:%M:%S')] Last known agglayer certificate height: $height, status: $status" >&3 + + if (( height > settle_certificates_target - 1 )); then + echo "[$(date '+%Y-%m-%d %H:%M:%S')] ✅ Success! The number of settled certificates has reached the target." >&3 exit 0 fi + + if (( height == settle_certificates_target - 1 )); then + if [ "$status" == "Settled" ]; then + echo "[$(date '+%Y-%m-%d %H:%M:%S')] ✅ Success! The number of settled certificates has reached the target." >&3 + exit 0 + fi + + echo "[$(date '+%Y-%m-%d %H:%M:%S')] ⚠️ Warning! The number of settled certificates is one less than the target." >&3 + fi } -# MAIN -declare -A aggsender_status_map=( - [0]="Pending" - [1]="Proven" - [2]="Candidate" - [3]="InError" - [4]="Settled" -) +function extract_certificate_height() { + local cast_output="$1" + echo "$cast_output" | jq -r '.height' +} -readonly aggsender_status_settled=4 +function extract_certificate_status() { + local cast_output="$1" + echo "$cast_output" | jq -r '.status' +} +# MAIN parse_params $* start_time=$(date +%s) From 1dfea4a624fde7f2d904d8903e45f3185fcafad7 Mon Sep 17 00:00:00 2001 From: Joan Esteban <129153821+joanestebanr@users.noreply.github.com> Date: Tue, 26 Nov 2024 12:45:43 +0100 Subject: [PATCH 14/23] fix: Integration Bali PP (#198) - Issues a long integration: - Wrong process of unknown error field `PError` on certificate header - Fix use of aggsender log (to be able to filter by module) - Fix a bad condition to show a warning of a reopen certificate - Improved test for epoch notifier - Fix certificate on local DB but AggLayer have none certificate (CDK-604) - Add logs to `synv/evmdownloader.go` to have more information on error: `error calling FilterLogs to eth client: invalid params` --- agglayer/types.go | 16 +- agglayer/types_test.go | 16 + aggsender/aggsender.go | 16 +- aggsender/aggsender_test.go | 13 + aggsender/block_notifier_polling.go | 2 +- aggsender/epoch_notifier_per_block.go | 21 +- aggsender/epoch_notifier_per_block_test.go | 11 + aggsender/types/block_notifier.go | 7 +- sync/evmdownloader.go | 11 +- sync/evmdownloader_test.go | 38 ++ sync/mock_downloader_test.go | 158 +++++- sync/mock_l2_test.go | 533 ++++++++++++++++++++- sync/mock_processor_test.go | 96 +++- sync/mock_reorgdetector_test.go | 69 ++- test/Makefile | 9 +- test/bats/pp/bridge-e2e.bats | 4 +- 16 files changed, 996 insertions(+), 24 deletions(-) diff --git a/agglayer/types.go b/agglayer/types.go index 9a56d878..09697d3d 100644 --- a/agglayer/types.go +++ b/agglayer/types.go @@ -563,6 +563,15 @@ func (c *ImportedBridgeExit) Hash() common.Hash { ) } +type GenericPPError struct { + Key string + Value string +} + +func (p *GenericPPError) String() string { + return fmt.Sprintf("Generic error: %s: %s", p.Key, p.Value) +} + // CertificateHeader is the structure returned by the interop_getCertificateHeader RPC call type CertificateHeader struct { NetworkID uint32 `json:"network_id"` @@ -654,7 +663,12 @@ func (c *CertificateHeader) UnmarshalJSON(data []byte) error { ppError = p default: - return fmt.Errorf("invalid error type: %s", key) + valueStr, err := json.Marshal(value) + if err != nil { + ppError = &GenericPPError{Key: key, Value: "error marshalling value"} + } else { + ppError = &GenericPPError{Key: key, Value: string(valueStr)} + } } } diff --git a/agglayer/types_test.go b/agglayer/types_test.go index f2133923..ecef4bca 100644 --- a/agglayer/types_test.go +++ b/agglayer/types_test.go @@ -15,6 +15,11 @@ const ( expectedSignedCertificateyMetadataJSON = `{"network_id":1,"height":1,"prev_local_exit_root":[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],"new_local_exit_root":[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],"bridge_exits":[{"leaf_type":"Transfer","token_info":null,"dest_network":0,"dest_address":"0x0000000000000000000000000000000000000000","amount":"1","metadata":[1,2,3]}],"imported_bridge_exits":[{"bridge_exit":{"leaf_type":"Transfer","token_info":null,"dest_network":0,"dest_address":"0x0000000000000000000000000000000000000000","amount":"1","metadata":[]},"claim_data":null,"global_index":{"mainnet_flag":false,"rollup_index":1,"leaf_index":1}}],"metadata":"0x0000000000000000000000000000000000000000000000000000000000000000","signature":{"r":"0x0000000000000000000000000000000000000000000000000000000000000000","s":"0x0000000000000000000000000000000000000000000000000000000000000000","odd_y_parity":false}}` ) +func TestMGenericPPError(t *testing.T) { + err := GenericPPError{"test", "value"} + require.Equal(t, "Generic error: test: value", err.String()) +} + func TestMarshalJSON(t *testing.T) { cert := SignedCertificate{ Certificate: &Certificate{ @@ -251,3 +256,14 @@ func TestGlobalIndex_UnmarshalFromMap(t *testing.T) { }) } } + +func TestUnmarshalCertificateHeaderUnknownError(t *testing.T) { + str := "{\"network_id\":14,\"height\":0,\"epoch_number\":null,\"certificate_index\":null,\"certificate_id\":\"0x3af88c9ca106822bd141fdc680dcb888f4e9d4997fad1645ba3d5d747059eb32\",\"new_local_exit_root\":\"0x625e889ced3c31277c6653229096374d396a2fd3564a8894aaad2ff935d2fc8c\",\"metadata\":\"0x0000000000000000000000000000000000000000000000000000000000002f3d\",\"status\":{\"InError\":{\"error\":{\"ProofVerificationFailed\":{\"Plonk\":\"the verifying key does not match the inner plonk bn254 proof's committed verifying key\"}}}}}" + data := []byte(str) + var result *CertificateHeader + err := json.Unmarshal(data, &result) + require.NoError(t, err) + require.NotNil(t, result) + ppError := result.Error.String() + require.Equal(t, `Generic error: ProofVerificationFailed: {"Plonk":"the verifying key does not match the inner plonk bn254 proof's committed verifying key"}`, ppError) +} diff --git a/aggsender/aggsender.go b/aggsender/aggsender.go index 39cd6867..eea9f125 100644 --- a/aggsender/aggsender.go +++ b/aggsender/aggsender.go @@ -93,9 +93,9 @@ func (a *AggSender) checkInitialStatus(ctx context.Context) { for { if err := a.checkLastCertificateFromAgglayer(ctx); err != nil { - log.Errorf("error checking initial status: %w, retrying in %s", err, a.cfg.DelayBeetweenRetries.String()) + a.log.Errorf("error checking initial status: %w, retrying in %s", err, a.cfg.DelayBeetweenRetries.String()) } else { - log.Info("Initial status checked successfully") + a.log.Info("Initial status checked successfully") return } select { @@ -116,7 +116,7 @@ func (a *AggSender) sendCertificates(ctx context.Context) { thereArePendingCerts := a.checkPendingCertificatesStatus(ctx) if !thereArePendingCerts { if _, err := a.sendCertificate(ctx); err != nil { - log.Error(err) + a.log.Error(err) } } else { log.Infof("Skipping epoch %s because there are pending certificates", @@ -202,7 +202,6 @@ func (a *AggSender) sendCertificate(ctx context.Context) (*agglayer.SignedCertif a.saveCertificateToFile(signedCertificate) a.log.Infof("certificate ready to be send to AggLayer: %s", signedCertificate.String()) - certificateHash, err := a.aggLayerClient.SendCertificate(signedCertificate) if err != nil { return nil, fmt.Errorf("error sending certificate: %w", err) @@ -584,7 +583,7 @@ func (a *AggSender) updateCertificateStatus(ctx context.Context, localCert.String()) // That is a strange situation - if agglayerCert.Status.IsOpen() == localCert.Status.IsClosed() { + if agglayerCert.Status.IsOpen() && localCert.Status.IsClosed() { a.log.Warnf("certificate %s is reopen! from [%s] to [%s]", localCert.ID(), localCert.Status, agglayerCert.Status) } @@ -638,6 +637,11 @@ func (a *AggSender) checkLastCertificateFromAgglayer(ctx context.Context) error } return nil } + // CASE 2.1: certificate in storage but not in agglayer + // this is a non-sense, so thrown an error + if localLastCert != nil && aggLayerLastCert == nil { + return fmt.Errorf("recovery: certificate in storage but not in agglayer. Inconsistency") + } // CASE 3: aggsender stopped between sending to agglayer and storing on DB if aggLayerLastCert.Height == localLastCert.Height+1 { a.log.Infof("recovery: AggLayer have next cert (height:%d), so is a recovery case: storing cert: %s", @@ -673,7 +677,7 @@ func (a *AggSender) checkLastCertificateFromAgglayer(ctx context.Context) error func (a *AggSender) updateLocalStorageWithAggLayerCert(ctx context.Context, aggLayerCert *agglayer.CertificateHeader) (*types.CertificateInfo, error) { certInfo := NewCertificateInfoFromAgglayerCertHeader(aggLayerCert) - log.Infof("setting initial certificate from AggLayer: %s", certInfo.String()) + a.log.Infof("setting initial certificate from AggLayer: %s", certInfo.String()) return certInfo, a.storage.SaveLastSentCertificate(ctx, *certInfo) } diff --git a/aggsender/aggsender_test.go b/aggsender/aggsender_test.go index 6d84c683..9185050f 100644 --- a/aggsender/aggsender_test.go +++ b/aggsender/aggsender_test.go @@ -1717,6 +1717,19 @@ func TestCheckLastCertificateFromAgglayer_Case2NoCertLocalCertRemoteErrorStorage require.Error(t, err) } +// CASE 2.1: certificate in storage but not in agglayer +// sub case of previous one that fails to update local storage +func TestCheckLastCertificateFromAgglayer_Case2_1NoCertRemoteButCertLocal(t *testing.T) { + testData := newAggsenderTestData(t, testDataFlagMockStorage) + testData.l2syncerMock.EXPECT().OriginNetwork().Return(networkIDTest).Once() + testData.agglayerClientMock.EXPECT().GetLatestKnownCertificateHeader(networkIDTest). + Return(nil, nil).Once() + testData.storageMock.EXPECT().GetLastSentCertificate().Return(&testData.testCerts[0], nil) + err := testData.sut.checkLastCertificateFromAgglayer(testData.ctx) + + require.Error(t, err) +} + // CASE 3: AggSender and AggLayer not same certificateID. AggLayer has a new certificate func TestCheckLastCertificateFromAgglayer_Case3Mismatch(t *testing.T) { testData := newAggsenderTestData(t, testDataFlagMockStorage) diff --git a/aggsender/block_notifier_polling.go b/aggsender/block_notifier_polling.go index 17dafefa..dbae1a38 100644 --- a/aggsender/block_notifier_polling.go +++ b/aggsender/block_notifier_polling.go @@ -158,7 +158,7 @@ func (b *BlockNotifierPolling) step(ctx context.Context, newState := previousState.incommingNewBlock(currentBlock.Number.Uint64()) b.logger.Debugf("New block seen [finality:%s]: %d. blockRate:%s", b.config.BlockFinalityType, currentBlock.Number.Uint64(), newState.previousBlockTime) - + eventToEmit.BlockRate = *newState.previousBlockTime return b.nextBlockRequestDelay(newState, nil), newState, eventToEmit } diff --git a/aggsender/epoch_notifier_per_block.go b/aggsender/epoch_notifier_per_block.go index 3b560731..80494cc0 100644 --- a/aggsender/epoch_notifier_per_block.go +++ b/aggsender/epoch_notifier_per_block.go @@ -31,6 +31,14 @@ type ConfigEpochNotifierPerBlock struct { EpochNotificationPercentage uint } +func (c *ConfigEpochNotifierPerBlock) String() string { + if c == nil { + return "nil" + } + return fmt.Sprintf("{startEpochBlock=%d, sizeEpoch=%d, threshold=%d%%}", + c.StartingEpochBlock, c.NumBlockPerEpoch, c.EpochNotificationPercentage) +} + func NewConfigEpochNotifierPerBlock(aggLayer agglayer.AggLayerClientGetEpochConfiguration, epochNotificationPercentage uint) (*ConfigEpochNotifierPerBlock, error) { if aggLayer == nil { @@ -89,9 +97,7 @@ func NewEpochNotifierPerBlock(blockNotifier types.BlockNotifier, } func (e *EpochNotifierPerBlock) String() string { - return fmt.Sprintf("EpochNotifierPerBlock: startingEpochBlock=%d, numBlockPerEpoch=%d,"+ - " EpochNotificationPercentage=%d", - e.Config.StartingEpochBlock, e.Config.NumBlockPerEpoch, e.Config.EpochNotificationPercentage) + return fmt.Sprintf("EpochNotifierPerBlock: config: %s", e.Config.String()) } // StartAsync starts the notifier in a goroutine @@ -147,6 +153,14 @@ func (e *EpochNotifierPerBlock) step(status internalStatus, status.lastBlockSeen = currentBlock needNotify, closingEpoch := e.isNotificationRequired(currentBlock, status.waitingForEpoch) + percentEpoch := e.percentEpoch(currentBlock) + logFunc := e.logger.Debugf + if needNotify { + logFunc = e.logger.Infof + } + logFunc("New block seen [finality:%s]: %d. blockRate:%s Epoch:%d Percent:%f%% notify:%v config:%s", + newBlock.BlockFinalityType, newBlock.BlockNumber, newBlock.BlockRate, closingEpoch, + percentEpoch*maxPercent, needNotify, e.Config.String()) if needNotify { // Notify the epoch has started info := e.infoEpoch(currentBlock, closingEpoch) @@ -179,7 +193,6 @@ func (e *EpochNotifierPerBlock) isNotificationRequired(currentBlock, lastEpochNo thresholdPercent = maxTresholdPercent } if percentEpoch < thresholdPercent { - e.logger.Debugf("Block %d is at %f%% of the epoch no notify", currentBlock, percentEpoch*maxPercent) return false, e.epochNumber(currentBlock) } nextEpoch := e.epochNumber(currentBlock) + 1 diff --git a/aggsender/epoch_notifier_per_block_test.go b/aggsender/epoch_notifier_per_block_test.go index 203116d0..ac35350e 100644 --- a/aggsender/epoch_notifier_per_block_test.go +++ b/aggsender/epoch_notifier_per_block_test.go @@ -14,6 +14,17 @@ import ( "github.com/stretchr/testify/require" ) +func TestConfigEpochNotifierPerBlockString(t *testing.T) { + cfg := ConfigEpochNotifierPerBlock{ + StartingEpochBlock: 123, + NumBlockPerEpoch: 456, + EpochNotificationPercentage: 789, + } + require.Equal(t, "{startEpochBlock=123, sizeEpoch=456, threshold=789%}", cfg.String()) + var cfg2 *ConfigEpochNotifierPerBlock + require.Equal(t, "nil", cfg2.String()) +} + func TestStartingBlockEpoch(t *testing.T) { testData := newNotifierPerBlockTestData(t, &ConfigEpochNotifierPerBlock{ StartingEpochBlock: 9, diff --git a/aggsender/types/block_notifier.go b/aggsender/types/block_notifier.go index 475abc1b..5dde2702 100644 --- a/aggsender/types/block_notifier.go +++ b/aggsender/types/block_notifier.go @@ -1,10 +1,15 @@ package types -import "github.com/0xPolygon/cdk/etherman" +import ( + "time" + + "github.com/0xPolygon/cdk/etherman" +) type EventNewBlock struct { BlockNumber uint64 BlockFinalityType etherman.BlockNumberFinality + BlockRate time.Duration } // BlockNotifier is the interface that wraps the basic methods to notify a new block. diff --git a/sync/evmdownloader.go b/sync/evmdownloader.go index 74b8ec7c..2c32b7ea 100644 --- a/sync/evmdownloader.go +++ b/sync/evmdownloader.go @@ -3,6 +3,7 @@ package sync import ( "context" "errors" + "fmt" "math/big" "time" @@ -229,6 +230,11 @@ func (d *EVMDownloaderImplementation) GetEventsByBlockRange(ctx context.Context, } } +func filterQueryToString(query ethereum.FilterQuery) string { + return fmt.Sprintf("FromBlock: %s, ToBlock: %s, Addresses: %s, Topics: %s", + query.FromBlock.String(), query.ToBlock.String(), query.Addresses, query.Topics) +} + func (d *EVMDownloaderImplementation) GetLogs(ctx context.Context, fromBlock, toBlock uint64) []types.Log { query := ethereum.FilterQuery{ FromBlock: new(big.Int).SetUint64(fromBlock), @@ -249,7 +255,10 @@ func (d *EVMDownloaderImplementation) GetLogs(ctx context.Context, fromBlock, to } attempts++ - d.log.Error("error calling FilterLogs to eth client: ", err) + d.log.Errorf("error calling FilterLogs to eth client: filter: %s err:%w ", + filterQueryToString(query), + err, + ) d.rh.Handle("getLogs", attempts) continue } diff --git a/sync/evmdownloader_test.go b/sync/evmdownloader_test.go index 04c92e72..f654db05 100644 --- a/sync/evmdownloader_test.go +++ b/sync/evmdownloader_test.go @@ -381,6 +381,44 @@ func TestGetBlockHeader(t *testing.T) { assert.False(t, isCanceled) } +func TestFilterQueryToString(t *testing.T) { + addr1 := common.HexToAddress("0xf000") + addr2 := common.HexToAddress("0xabcd") + query := ethereum.FilterQuery{ + FromBlock: new(big.Int).SetUint64(1000), + Addresses: []common.Address{addr1, addr2}, + ToBlock: new(big.Int).SetUint64(1100), + } + + assert.Equal(t, "FromBlock: 1000, ToBlock: 1100, Addresses: [0x000000000000000000000000000000000000f000 0x000000000000000000000000000000000000ABcD], Topics: []", filterQueryToString(query)) + + query = ethereum.FilterQuery{ + FromBlock: new(big.Int).SetUint64(1000), + Addresses: []common.Address{addr1, addr2}, + ToBlock: new(big.Int).SetUint64(1100), + Topics: [][]common.Hash{{common.HexToHash("0x1234"), common.HexToHash("0x5678")}}, + } + assert.Equal(t, "FromBlock: 1000, ToBlock: 1100, Addresses: [0x000000000000000000000000000000000000f000 0x000000000000000000000000000000000000ABcD], Topics: [[0x0000000000000000000000000000000000000000000000000000000000001234 0x0000000000000000000000000000000000000000000000000000000000005678]]", filterQueryToString(query)) +} + +func TestGetLogs(t *testing.T) { + mockEthClient := NewL2Mock(t) + sut := EVMDownloaderImplementation{ + ethClient: mockEthClient, + adressessToQuery: []common.Address{contractAddr}, + log: log.WithFields("test", "EVMDownloaderImplementation"), + rh: &RetryHandler{ + RetryAfterErrorPeriod: time.Millisecond, + MaxRetryAttemptsAfterError: 5, + }, + } + ctx := context.TODO() + mockEthClient.EXPECT().FilterLogs(ctx, mock.Anything).Return(nil, errors.New("foo")).Once() + mockEthClient.EXPECT().FilterLogs(ctx, mock.Anything).Return(nil, nil).Once() + logs := sut.GetLogs(ctx, 0, 1) + require.Equal(t, []types.Log{}, logs) +} + func buildAppender() LogAppenderMap { appender := make(LogAppenderMap) appender[eventSignature] = func(b *EVMBlock, l types.Log) error { diff --git a/sync/mock_downloader_test.go b/sync/mock_downloader_test.go index f28045b5..45a53b84 100644 --- a/sync/mock_downloader_test.go +++ b/sync/mock_downloader_test.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.45.0. DO NOT EDIT. +// Code generated by mockery. DO NOT EDIT. package sync @@ -14,11 +14,49 @@ type EVMDownloaderMock struct { mock.Mock } +type EVMDownloaderMock_Expecter struct { + mock *mock.Mock +} + +func (_m *EVMDownloaderMock) EXPECT() *EVMDownloaderMock_Expecter { + return &EVMDownloaderMock_Expecter{mock: &_m.Mock} +} + // Download provides a mock function with given fields: ctx, fromBlock, downloadedCh func (_m *EVMDownloaderMock) Download(ctx context.Context, fromBlock uint64, downloadedCh chan EVMBlock) { _m.Called(ctx, fromBlock, downloadedCh) } +// EVMDownloaderMock_Download_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Download' +type EVMDownloaderMock_Download_Call struct { + *mock.Call +} + +// Download is a helper method to define mock.On call +// - ctx context.Context +// - fromBlock uint64 +// - downloadedCh chan EVMBlock +func (_e *EVMDownloaderMock_Expecter) Download(ctx interface{}, fromBlock interface{}, downloadedCh interface{}) *EVMDownloaderMock_Download_Call { + return &EVMDownloaderMock_Download_Call{Call: _e.mock.On("Download", ctx, fromBlock, downloadedCh)} +} + +func (_c *EVMDownloaderMock_Download_Call) Run(run func(ctx context.Context, fromBlock uint64, downloadedCh chan EVMBlock)) *EVMDownloaderMock_Download_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context), args[1].(uint64), args[2].(chan EVMBlock)) + }) + return _c +} + +func (_c *EVMDownloaderMock_Download_Call) Return() *EVMDownloaderMock_Download_Call { + _c.Call.Return() + return _c +} + +func (_c *EVMDownloaderMock_Download_Call) RunAndReturn(run func(context.Context, uint64, chan EVMBlock)) *EVMDownloaderMock_Download_Call { + _c.Call.Return(run) + return _c +} + // GetBlockHeader provides a mock function with given fields: ctx, blockNum func (_m *EVMDownloaderMock) GetBlockHeader(ctx context.Context, blockNum uint64) (EVMBlockHeader, bool) { ret := _m.Called(ctx, blockNum) @@ -47,6 +85,35 @@ func (_m *EVMDownloaderMock) GetBlockHeader(ctx context.Context, blockNum uint64 return r0, r1 } +// EVMDownloaderMock_GetBlockHeader_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GetBlockHeader' +type EVMDownloaderMock_GetBlockHeader_Call struct { + *mock.Call +} + +// GetBlockHeader is a helper method to define mock.On call +// - ctx context.Context +// - blockNum uint64 +func (_e *EVMDownloaderMock_Expecter) GetBlockHeader(ctx interface{}, blockNum interface{}) *EVMDownloaderMock_GetBlockHeader_Call { + return &EVMDownloaderMock_GetBlockHeader_Call{Call: _e.mock.On("GetBlockHeader", ctx, blockNum)} +} + +func (_c *EVMDownloaderMock_GetBlockHeader_Call) Run(run func(ctx context.Context, blockNum uint64)) *EVMDownloaderMock_GetBlockHeader_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context), args[1].(uint64)) + }) + return _c +} + +func (_c *EVMDownloaderMock_GetBlockHeader_Call) Return(_a0 EVMBlockHeader, _a1 bool) *EVMDownloaderMock_GetBlockHeader_Call { + _c.Call.Return(_a0, _a1) + return _c +} + +func (_c *EVMDownloaderMock_GetBlockHeader_Call) RunAndReturn(run func(context.Context, uint64) (EVMBlockHeader, bool)) *EVMDownloaderMock_GetBlockHeader_Call { + _c.Call.Return(run) + return _c +} + // GetEventsByBlockRange provides a mock function with given fields: ctx, fromBlock, toBlock func (_m *EVMDownloaderMock) GetEventsByBlockRange(ctx context.Context, fromBlock uint64, toBlock uint64) []EVMBlock { ret := _m.Called(ctx, fromBlock, toBlock) @@ -67,6 +134,36 @@ func (_m *EVMDownloaderMock) GetEventsByBlockRange(ctx context.Context, fromBloc return r0 } +// EVMDownloaderMock_GetEventsByBlockRange_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GetEventsByBlockRange' +type EVMDownloaderMock_GetEventsByBlockRange_Call struct { + *mock.Call +} + +// GetEventsByBlockRange is a helper method to define mock.On call +// - ctx context.Context +// - fromBlock uint64 +// - toBlock uint64 +func (_e *EVMDownloaderMock_Expecter) GetEventsByBlockRange(ctx interface{}, fromBlock interface{}, toBlock interface{}) *EVMDownloaderMock_GetEventsByBlockRange_Call { + return &EVMDownloaderMock_GetEventsByBlockRange_Call{Call: _e.mock.On("GetEventsByBlockRange", ctx, fromBlock, toBlock)} +} + +func (_c *EVMDownloaderMock_GetEventsByBlockRange_Call) Run(run func(ctx context.Context, fromBlock uint64, toBlock uint64)) *EVMDownloaderMock_GetEventsByBlockRange_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context), args[1].(uint64), args[2].(uint64)) + }) + return _c +} + +func (_c *EVMDownloaderMock_GetEventsByBlockRange_Call) Return(_a0 []EVMBlock) *EVMDownloaderMock_GetEventsByBlockRange_Call { + _c.Call.Return(_a0) + return _c +} + +func (_c *EVMDownloaderMock_GetEventsByBlockRange_Call) RunAndReturn(run func(context.Context, uint64, uint64) []EVMBlock) *EVMDownloaderMock_GetEventsByBlockRange_Call { + _c.Call.Return(run) + return _c +} + // GetLogs provides a mock function with given fields: ctx, fromBlock, toBlock func (_m *EVMDownloaderMock) GetLogs(ctx context.Context, fromBlock uint64, toBlock uint64) []types.Log { ret := _m.Called(ctx, fromBlock, toBlock) @@ -87,6 +184,36 @@ func (_m *EVMDownloaderMock) GetLogs(ctx context.Context, fromBlock uint64, toBl return r0 } +// EVMDownloaderMock_GetLogs_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GetLogs' +type EVMDownloaderMock_GetLogs_Call struct { + *mock.Call +} + +// GetLogs is a helper method to define mock.On call +// - ctx context.Context +// - fromBlock uint64 +// - toBlock uint64 +func (_e *EVMDownloaderMock_Expecter) GetLogs(ctx interface{}, fromBlock interface{}, toBlock interface{}) *EVMDownloaderMock_GetLogs_Call { + return &EVMDownloaderMock_GetLogs_Call{Call: _e.mock.On("GetLogs", ctx, fromBlock, toBlock)} +} + +func (_c *EVMDownloaderMock_GetLogs_Call) Run(run func(ctx context.Context, fromBlock uint64, toBlock uint64)) *EVMDownloaderMock_GetLogs_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context), args[1].(uint64), args[2].(uint64)) + }) + return _c +} + +func (_c *EVMDownloaderMock_GetLogs_Call) Return(_a0 []types.Log) *EVMDownloaderMock_GetLogs_Call { + _c.Call.Return(_a0) + return _c +} + +func (_c *EVMDownloaderMock_GetLogs_Call) RunAndReturn(run func(context.Context, uint64, uint64) []types.Log) *EVMDownloaderMock_GetLogs_Call { + _c.Call.Return(run) + return _c +} + // WaitForNewBlocks provides a mock function with given fields: ctx, lastBlockSeen func (_m *EVMDownloaderMock) WaitForNewBlocks(ctx context.Context, lastBlockSeen uint64) uint64 { ret := _m.Called(ctx, lastBlockSeen) @@ -105,6 +232,35 @@ func (_m *EVMDownloaderMock) WaitForNewBlocks(ctx context.Context, lastBlockSeen return r0 } +// EVMDownloaderMock_WaitForNewBlocks_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'WaitForNewBlocks' +type EVMDownloaderMock_WaitForNewBlocks_Call struct { + *mock.Call +} + +// WaitForNewBlocks is a helper method to define mock.On call +// - ctx context.Context +// - lastBlockSeen uint64 +func (_e *EVMDownloaderMock_Expecter) WaitForNewBlocks(ctx interface{}, lastBlockSeen interface{}) *EVMDownloaderMock_WaitForNewBlocks_Call { + return &EVMDownloaderMock_WaitForNewBlocks_Call{Call: _e.mock.On("WaitForNewBlocks", ctx, lastBlockSeen)} +} + +func (_c *EVMDownloaderMock_WaitForNewBlocks_Call) Run(run func(ctx context.Context, lastBlockSeen uint64)) *EVMDownloaderMock_WaitForNewBlocks_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context), args[1].(uint64)) + }) + return _c +} + +func (_c *EVMDownloaderMock_WaitForNewBlocks_Call) Return(newLastBlock uint64) *EVMDownloaderMock_WaitForNewBlocks_Call { + _c.Call.Return(newLastBlock) + return _c +} + +func (_c *EVMDownloaderMock_WaitForNewBlocks_Call) RunAndReturn(run func(context.Context, uint64) uint64) *EVMDownloaderMock_WaitForNewBlocks_Call { + _c.Call.Return(run) + return _c +} + // NewEVMDownloaderMock creates a new instance of EVMDownloaderMock. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. // The first argument is typically a *testing.T value. func NewEVMDownloaderMock(t interface { diff --git a/sync/mock_l2_test.go b/sync/mock_l2_test.go index 7a4bae36..955af0db 100644 --- a/sync/mock_l2_test.go +++ b/sync/mock_l2_test.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.39.0. DO NOT EDIT. +// Code generated by mockery. DO NOT EDIT. package sync @@ -20,6 +20,14 @@ type L2Mock struct { mock.Mock } +type L2Mock_Expecter struct { + mock *mock.Mock +} + +func (_m *L2Mock) EXPECT() *L2Mock_Expecter { + return &L2Mock_Expecter{mock: &_m.Mock} +} + // BlockByHash provides a mock function with given fields: ctx, hash func (_m *L2Mock) BlockByHash(ctx context.Context, hash common.Hash) (*types.Block, error) { ret := _m.Called(ctx, hash) @@ -50,6 +58,35 @@ func (_m *L2Mock) BlockByHash(ctx context.Context, hash common.Hash) (*types.Blo return r0, r1 } +// L2Mock_BlockByHash_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'BlockByHash' +type L2Mock_BlockByHash_Call struct { + *mock.Call +} + +// BlockByHash is a helper method to define mock.On call +// - ctx context.Context +// - hash common.Hash +func (_e *L2Mock_Expecter) BlockByHash(ctx interface{}, hash interface{}) *L2Mock_BlockByHash_Call { + return &L2Mock_BlockByHash_Call{Call: _e.mock.On("BlockByHash", ctx, hash)} +} + +func (_c *L2Mock_BlockByHash_Call) Run(run func(ctx context.Context, hash common.Hash)) *L2Mock_BlockByHash_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context), args[1].(common.Hash)) + }) + return _c +} + +func (_c *L2Mock_BlockByHash_Call) Return(_a0 *types.Block, _a1 error) *L2Mock_BlockByHash_Call { + _c.Call.Return(_a0, _a1) + return _c +} + +func (_c *L2Mock_BlockByHash_Call) RunAndReturn(run func(context.Context, common.Hash) (*types.Block, error)) *L2Mock_BlockByHash_Call { + _c.Call.Return(run) + return _c +} + // BlockByNumber provides a mock function with given fields: ctx, number func (_m *L2Mock) BlockByNumber(ctx context.Context, number *big.Int) (*types.Block, error) { ret := _m.Called(ctx, number) @@ -80,6 +117,35 @@ func (_m *L2Mock) BlockByNumber(ctx context.Context, number *big.Int) (*types.Bl return r0, r1 } +// L2Mock_BlockByNumber_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'BlockByNumber' +type L2Mock_BlockByNumber_Call struct { + *mock.Call +} + +// BlockByNumber is a helper method to define mock.On call +// - ctx context.Context +// - number *big.Int +func (_e *L2Mock_Expecter) BlockByNumber(ctx interface{}, number interface{}) *L2Mock_BlockByNumber_Call { + return &L2Mock_BlockByNumber_Call{Call: _e.mock.On("BlockByNumber", ctx, number)} +} + +func (_c *L2Mock_BlockByNumber_Call) Run(run func(ctx context.Context, number *big.Int)) *L2Mock_BlockByNumber_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context), args[1].(*big.Int)) + }) + return _c +} + +func (_c *L2Mock_BlockByNumber_Call) Return(_a0 *types.Block, _a1 error) *L2Mock_BlockByNumber_Call { + _c.Call.Return(_a0, _a1) + return _c +} + +func (_c *L2Mock_BlockByNumber_Call) RunAndReturn(run func(context.Context, *big.Int) (*types.Block, error)) *L2Mock_BlockByNumber_Call { + _c.Call.Return(run) + return _c +} + // BlockNumber provides a mock function with given fields: ctx func (_m *L2Mock) BlockNumber(ctx context.Context) (uint64, error) { ret := _m.Called(ctx) @@ -108,6 +174,34 @@ func (_m *L2Mock) BlockNumber(ctx context.Context) (uint64, error) { return r0, r1 } +// L2Mock_BlockNumber_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'BlockNumber' +type L2Mock_BlockNumber_Call struct { + *mock.Call +} + +// BlockNumber is a helper method to define mock.On call +// - ctx context.Context +func (_e *L2Mock_Expecter) BlockNumber(ctx interface{}) *L2Mock_BlockNumber_Call { + return &L2Mock_BlockNumber_Call{Call: _e.mock.On("BlockNumber", ctx)} +} + +func (_c *L2Mock_BlockNumber_Call) Run(run func(ctx context.Context)) *L2Mock_BlockNumber_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context)) + }) + return _c +} + +func (_c *L2Mock_BlockNumber_Call) Return(_a0 uint64, _a1 error) *L2Mock_BlockNumber_Call { + _c.Call.Return(_a0, _a1) + return _c +} + +func (_c *L2Mock_BlockNumber_Call) RunAndReturn(run func(context.Context) (uint64, error)) *L2Mock_BlockNumber_Call { + _c.Call.Return(run) + return _c +} + // CallContract provides a mock function with given fields: ctx, call, blockNumber func (_m *L2Mock) CallContract(ctx context.Context, call ethereum.CallMsg, blockNumber *big.Int) ([]byte, error) { ret := _m.Called(ctx, call, blockNumber) @@ -138,6 +232,36 @@ func (_m *L2Mock) CallContract(ctx context.Context, call ethereum.CallMsg, block return r0, r1 } +// L2Mock_CallContract_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'CallContract' +type L2Mock_CallContract_Call struct { + *mock.Call +} + +// CallContract is a helper method to define mock.On call +// - ctx context.Context +// - call ethereum.CallMsg +// - blockNumber *big.Int +func (_e *L2Mock_Expecter) CallContract(ctx interface{}, call interface{}, blockNumber interface{}) *L2Mock_CallContract_Call { + return &L2Mock_CallContract_Call{Call: _e.mock.On("CallContract", ctx, call, blockNumber)} +} + +func (_c *L2Mock_CallContract_Call) Run(run func(ctx context.Context, call ethereum.CallMsg, blockNumber *big.Int)) *L2Mock_CallContract_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context), args[1].(ethereum.CallMsg), args[2].(*big.Int)) + }) + return _c +} + +func (_c *L2Mock_CallContract_Call) Return(_a0 []byte, _a1 error) *L2Mock_CallContract_Call { + _c.Call.Return(_a0, _a1) + return _c +} + +func (_c *L2Mock_CallContract_Call) RunAndReturn(run func(context.Context, ethereum.CallMsg, *big.Int) ([]byte, error)) *L2Mock_CallContract_Call { + _c.Call.Return(run) + return _c +} + // CodeAt provides a mock function with given fields: ctx, contract, blockNumber func (_m *L2Mock) CodeAt(ctx context.Context, contract common.Address, blockNumber *big.Int) ([]byte, error) { ret := _m.Called(ctx, contract, blockNumber) @@ -168,6 +292,36 @@ func (_m *L2Mock) CodeAt(ctx context.Context, contract common.Address, blockNumb return r0, r1 } +// L2Mock_CodeAt_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'CodeAt' +type L2Mock_CodeAt_Call struct { + *mock.Call +} + +// CodeAt is a helper method to define mock.On call +// - ctx context.Context +// - contract common.Address +// - blockNumber *big.Int +func (_e *L2Mock_Expecter) CodeAt(ctx interface{}, contract interface{}, blockNumber interface{}) *L2Mock_CodeAt_Call { + return &L2Mock_CodeAt_Call{Call: _e.mock.On("CodeAt", ctx, contract, blockNumber)} +} + +func (_c *L2Mock_CodeAt_Call) Run(run func(ctx context.Context, contract common.Address, blockNumber *big.Int)) *L2Mock_CodeAt_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context), args[1].(common.Address), args[2].(*big.Int)) + }) + return _c +} + +func (_c *L2Mock_CodeAt_Call) Return(_a0 []byte, _a1 error) *L2Mock_CodeAt_Call { + _c.Call.Return(_a0, _a1) + return _c +} + +func (_c *L2Mock_CodeAt_Call) RunAndReturn(run func(context.Context, common.Address, *big.Int) ([]byte, error)) *L2Mock_CodeAt_Call { + _c.Call.Return(run) + return _c +} + // EstimateGas provides a mock function with given fields: ctx, call func (_m *L2Mock) EstimateGas(ctx context.Context, call ethereum.CallMsg) (uint64, error) { ret := _m.Called(ctx, call) @@ -196,6 +350,35 @@ func (_m *L2Mock) EstimateGas(ctx context.Context, call ethereum.CallMsg) (uint6 return r0, r1 } +// L2Mock_EstimateGas_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'EstimateGas' +type L2Mock_EstimateGas_Call struct { + *mock.Call +} + +// EstimateGas is a helper method to define mock.On call +// - ctx context.Context +// - call ethereum.CallMsg +func (_e *L2Mock_Expecter) EstimateGas(ctx interface{}, call interface{}) *L2Mock_EstimateGas_Call { + return &L2Mock_EstimateGas_Call{Call: _e.mock.On("EstimateGas", ctx, call)} +} + +func (_c *L2Mock_EstimateGas_Call) Run(run func(ctx context.Context, call ethereum.CallMsg)) *L2Mock_EstimateGas_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context), args[1].(ethereum.CallMsg)) + }) + return _c +} + +func (_c *L2Mock_EstimateGas_Call) Return(_a0 uint64, _a1 error) *L2Mock_EstimateGas_Call { + _c.Call.Return(_a0, _a1) + return _c +} + +func (_c *L2Mock_EstimateGas_Call) RunAndReturn(run func(context.Context, ethereum.CallMsg) (uint64, error)) *L2Mock_EstimateGas_Call { + _c.Call.Return(run) + return _c +} + // FilterLogs provides a mock function with given fields: ctx, q func (_m *L2Mock) FilterLogs(ctx context.Context, q ethereum.FilterQuery) ([]types.Log, error) { ret := _m.Called(ctx, q) @@ -226,6 +409,35 @@ func (_m *L2Mock) FilterLogs(ctx context.Context, q ethereum.FilterQuery) ([]typ return r0, r1 } +// L2Mock_FilterLogs_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'FilterLogs' +type L2Mock_FilterLogs_Call struct { + *mock.Call +} + +// FilterLogs is a helper method to define mock.On call +// - ctx context.Context +// - q ethereum.FilterQuery +func (_e *L2Mock_Expecter) FilterLogs(ctx interface{}, q interface{}) *L2Mock_FilterLogs_Call { + return &L2Mock_FilterLogs_Call{Call: _e.mock.On("FilterLogs", ctx, q)} +} + +func (_c *L2Mock_FilterLogs_Call) Run(run func(ctx context.Context, q ethereum.FilterQuery)) *L2Mock_FilterLogs_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context), args[1].(ethereum.FilterQuery)) + }) + return _c +} + +func (_c *L2Mock_FilterLogs_Call) Return(_a0 []types.Log, _a1 error) *L2Mock_FilterLogs_Call { + _c.Call.Return(_a0, _a1) + return _c +} + +func (_c *L2Mock_FilterLogs_Call) RunAndReturn(run func(context.Context, ethereum.FilterQuery) ([]types.Log, error)) *L2Mock_FilterLogs_Call { + _c.Call.Return(run) + return _c +} + // HeaderByHash provides a mock function with given fields: ctx, hash func (_m *L2Mock) HeaderByHash(ctx context.Context, hash common.Hash) (*types.Header, error) { ret := _m.Called(ctx, hash) @@ -256,6 +468,35 @@ func (_m *L2Mock) HeaderByHash(ctx context.Context, hash common.Hash) (*types.He return r0, r1 } +// L2Mock_HeaderByHash_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'HeaderByHash' +type L2Mock_HeaderByHash_Call struct { + *mock.Call +} + +// HeaderByHash is a helper method to define mock.On call +// - ctx context.Context +// - hash common.Hash +func (_e *L2Mock_Expecter) HeaderByHash(ctx interface{}, hash interface{}) *L2Mock_HeaderByHash_Call { + return &L2Mock_HeaderByHash_Call{Call: _e.mock.On("HeaderByHash", ctx, hash)} +} + +func (_c *L2Mock_HeaderByHash_Call) Run(run func(ctx context.Context, hash common.Hash)) *L2Mock_HeaderByHash_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context), args[1].(common.Hash)) + }) + return _c +} + +func (_c *L2Mock_HeaderByHash_Call) Return(_a0 *types.Header, _a1 error) *L2Mock_HeaderByHash_Call { + _c.Call.Return(_a0, _a1) + return _c +} + +func (_c *L2Mock_HeaderByHash_Call) RunAndReturn(run func(context.Context, common.Hash) (*types.Header, error)) *L2Mock_HeaderByHash_Call { + _c.Call.Return(run) + return _c +} + // HeaderByNumber provides a mock function with given fields: ctx, number func (_m *L2Mock) HeaderByNumber(ctx context.Context, number *big.Int) (*types.Header, error) { ret := _m.Called(ctx, number) @@ -286,6 +527,35 @@ func (_m *L2Mock) HeaderByNumber(ctx context.Context, number *big.Int) (*types.H return r0, r1 } +// L2Mock_HeaderByNumber_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'HeaderByNumber' +type L2Mock_HeaderByNumber_Call struct { + *mock.Call +} + +// HeaderByNumber is a helper method to define mock.On call +// - ctx context.Context +// - number *big.Int +func (_e *L2Mock_Expecter) HeaderByNumber(ctx interface{}, number interface{}) *L2Mock_HeaderByNumber_Call { + return &L2Mock_HeaderByNumber_Call{Call: _e.mock.On("HeaderByNumber", ctx, number)} +} + +func (_c *L2Mock_HeaderByNumber_Call) Run(run func(ctx context.Context, number *big.Int)) *L2Mock_HeaderByNumber_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context), args[1].(*big.Int)) + }) + return _c +} + +func (_c *L2Mock_HeaderByNumber_Call) Return(_a0 *types.Header, _a1 error) *L2Mock_HeaderByNumber_Call { + _c.Call.Return(_a0, _a1) + return _c +} + +func (_c *L2Mock_HeaderByNumber_Call) RunAndReturn(run func(context.Context, *big.Int) (*types.Header, error)) *L2Mock_HeaderByNumber_Call { + _c.Call.Return(run) + return _c +} + // PendingCodeAt provides a mock function with given fields: ctx, account func (_m *L2Mock) PendingCodeAt(ctx context.Context, account common.Address) ([]byte, error) { ret := _m.Called(ctx, account) @@ -316,6 +586,35 @@ func (_m *L2Mock) PendingCodeAt(ctx context.Context, account common.Address) ([] return r0, r1 } +// L2Mock_PendingCodeAt_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'PendingCodeAt' +type L2Mock_PendingCodeAt_Call struct { + *mock.Call +} + +// PendingCodeAt is a helper method to define mock.On call +// - ctx context.Context +// - account common.Address +func (_e *L2Mock_Expecter) PendingCodeAt(ctx interface{}, account interface{}) *L2Mock_PendingCodeAt_Call { + return &L2Mock_PendingCodeAt_Call{Call: _e.mock.On("PendingCodeAt", ctx, account)} +} + +func (_c *L2Mock_PendingCodeAt_Call) Run(run func(ctx context.Context, account common.Address)) *L2Mock_PendingCodeAt_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context), args[1].(common.Address)) + }) + return _c +} + +func (_c *L2Mock_PendingCodeAt_Call) Return(_a0 []byte, _a1 error) *L2Mock_PendingCodeAt_Call { + _c.Call.Return(_a0, _a1) + return _c +} + +func (_c *L2Mock_PendingCodeAt_Call) RunAndReturn(run func(context.Context, common.Address) ([]byte, error)) *L2Mock_PendingCodeAt_Call { + _c.Call.Return(run) + return _c +} + // PendingNonceAt provides a mock function with given fields: ctx, account func (_m *L2Mock) PendingNonceAt(ctx context.Context, account common.Address) (uint64, error) { ret := _m.Called(ctx, account) @@ -344,6 +643,35 @@ func (_m *L2Mock) PendingNonceAt(ctx context.Context, account common.Address) (u return r0, r1 } +// L2Mock_PendingNonceAt_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'PendingNonceAt' +type L2Mock_PendingNonceAt_Call struct { + *mock.Call +} + +// PendingNonceAt is a helper method to define mock.On call +// - ctx context.Context +// - account common.Address +func (_e *L2Mock_Expecter) PendingNonceAt(ctx interface{}, account interface{}) *L2Mock_PendingNonceAt_Call { + return &L2Mock_PendingNonceAt_Call{Call: _e.mock.On("PendingNonceAt", ctx, account)} +} + +func (_c *L2Mock_PendingNonceAt_Call) Run(run func(ctx context.Context, account common.Address)) *L2Mock_PendingNonceAt_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context), args[1].(common.Address)) + }) + return _c +} + +func (_c *L2Mock_PendingNonceAt_Call) Return(_a0 uint64, _a1 error) *L2Mock_PendingNonceAt_Call { + _c.Call.Return(_a0, _a1) + return _c +} + +func (_c *L2Mock_PendingNonceAt_Call) RunAndReturn(run func(context.Context, common.Address) (uint64, error)) *L2Mock_PendingNonceAt_Call { + _c.Call.Return(run) + return _c +} + // SendTransaction provides a mock function with given fields: ctx, tx func (_m *L2Mock) SendTransaction(ctx context.Context, tx *types.Transaction) error { ret := _m.Called(ctx, tx) @@ -362,6 +690,35 @@ func (_m *L2Mock) SendTransaction(ctx context.Context, tx *types.Transaction) er return r0 } +// L2Mock_SendTransaction_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'SendTransaction' +type L2Mock_SendTransaction_Call struct { + *mock.Call +} + +// SendTransaction is a helper method to define mock.On call +// - ctx context.Context +// - tx *types.Transaction +func (_e *L2Mock_Expecter) SendTransaction(ctx interface{}, tx interface{}) *L2Mock_SendTransaction_Call { + return &L2Mock_SendTransaction_Call{Call: _e.mock.On("SendTransaction", ctx, tx)} +} + +func (_c *L2Mock_SendTransaction_Call) Run(run func(ctx context.Context, tx *types.Transaction)) *L2Mock_SendTransaction_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context), args[1].(*types.Transaction)) + }) + return _c +} + +func (_c *L2Mock_SendTransaction_Call) Return(_a0 error) *L2Mock_SendTransaction_Call { + _c.Call.Return(_a0) + return _c +} + +func (_c *L2Mock_SendTransaction_Call) RunAndReturn(run func(context.Context, *types.Transaction) error) *L2Mock_SendTransaction_Call { + _c.Call.Return(run) + return _c +} + // SubscribeFilterLogs provides a mock function with given fields: ctx, q, ch func (_m *L2Mock) SubscribeFilterLogs(ctx context.Context, q ethereum.FilterQuery, ch chan<- types.Log) (ethereum.Subscription, error) { ret := _m.Called(ctx, q, ch) @@ -392,6 +749,36 @@ func (_m *L2Mock) SubscribeFilterLogs(ctx context.Context, q ethereum.FilterQuer return r0, r1 } +// L2Mock_SubscribeFilterLogs_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'SubscribeFilterLogs' +type L2Mock_SubscribeFilterLogs_Call struct { + *mock.Call +} + +// SubscribeFilterLogs is a helper method to define mock.On call +// - ctx context.Context +// - q ethereum.FilterQuery +// - ch chan<- types.Log +func (_e *L2Mock_Expecter) SubscribeFilterLogs(ctx interface{}, q interface{}, ch interface{}) *L2Mock_SubscribeFilterLogs_Call { + return &L2Mock_SubscribeFilterLogs_Call{Call: _e.mock.On("SubscribeFilterLogs", ctx, q, ch)} +} + +func (_c *L2Mock_SubscribeFilterLogs_Call) Run(run func(ctx context.Context, q ethereum.FilterQuery, ch chan<- types.Log)) *L2Mock_SubscribeFilterLogs_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context), args[1].(ethereum.FilterQuery), args[2].(chan<- types.Log)) + }) + return _c +} + +func (_c *L2Mock_SubscribeFilterLogs_Call) Return(_a0 ethereum.Subscription, _a1 error) *L2Mock_SubscribeFilterLogs_Call { + _c.Call.Return(_a0, _a1) + return _c +} + +func (_c *L2Mock_SubscribeFilterLogs_Call) RunAndReturn(run func(context.Context, ethereum.FilterQuery, chan<- types.Log) (ethereum.Subscription, error)) *L2Mock_SubscribeFilterLogs_Call { + _c.Call.Return(run) + return _c +} + // SubscribeNewHead provides a mock function with given fields: ctx, ch func (_m *L2Mock) SubscribeNewHead(ctx context.Context, ch chan<- *types.Header) (ethereum.Subscription, error) { ret := _m.Called(ctx, ch) @@ -422,6 +809,35 @@ func (_m *L2Mock) SubscribeNewHead(ctx context.Context, ch chan<- *types.Header) return r0, r1 } +// L2Mock_SubscribeNewHead_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'SubscribeNewHead' +type L2Mock_SubscribeNewHead_Call struct { + *mock.Call +} + +// SubscribeNewHead is a helper method to define mock.On call +// - ctx context.Context +// - ch chan<- *types.Header +func (_e *L2Mock_Expecter) SubscribeNewHead(ctx interface{}, ch interface{}) *L2Mock_SubscribeNewHead_Call { + return &L2Mock_SubscribeNewHead_Call{Call: _e.mock.On("SubscribeNewHead", ctx, ch)} +} + +func (_c *L2Mock_SubscribeNewHead_Call) Run(run func(ctx context.Context, ch chan<- *types.Header)) *L2Mock_SubscribeNewHead_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context), args[1].(chan<- *types.Header)) + }) + return _c +} + +func (_c *L2Mock_SubscribeNewHead_Call) Return(_a0 ethereum.Subscription, _a1 error) *L2Mock_SubscribeNewHead_Call { + _c.Call.Return(_a0, _a1) + return _c +} + +func (_c *L2Mock_SubscribeNewHead_Call) RunAndReturn(run func(context.Context, chan<- *types.Header) (ethereum.Subscription, error)) *L2Mock_SubscribeNewHead_Call { + _c.Call.Return(run) + return _c +} + // SuggestGasPrice provides a mock function with given fields: ctx func (_m *L2Mock) SuggestGasPrice(ctx context.Context) (*big.Int, error) { ret := _m.Called(ctx) @@ -452,6 +868,34 @@ func (_m *L2Mock) SuggestGasPrice(ctx context.Context) (*big.Int, error) { return r0, r1 } +// L2Mock_SuggestGasPrice_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'SuggestGasPrice' +type L2Mock_SuggestGasPrice_Call struct { + *mock.Call +} + +// SuggestGasPrice is a helper method to define mock.On call +// - ctx context.Context +func (_e *L2Mock_Expecter) SuggestGasPrice(ctx interface{}) *L2Mock_SuggestGasPrice_Call { + return &L2Mock_SuggestGasPrice_Call{Call: _e.mock.On("SuggestGasPrice", ctx)} +} + +func (_c *L2Mock_SuggestGasPrice_Call) Run(run func(ctx context.Context)) *L2Mock_SuggestGasPrice_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context)) + }) + return _c +} + +func (_c *L2Mock_SuggestGasPrice_Call) Return(_a0 *big.Int, _a1 error) *L2Mock_SuggestGasPrice_Call { + _c.Call.Return(_a0, _a1) + return _c +} + +func (_c *L2Mock_SuggestGasPrice_Call) RunAndReturn(run func(context.Context) (*big.Int, error)) *L2Mock_SuggestGasPrice_Call { + _c.Call.Return(run) + return _c +} + // SuggestGasTipCap provides a mock function with given fields: ctx func (_m *L2Mock) SuggestGasTipCap(ctx context.Context) (*big.Int, error) { ret := _m.Called(ctx) @@ -482,6 +926,34 @@ func (_m *L2Mock) SuggestGasTipCap(ctx context.Context) (*big.Int, error) { return r0, r1 } +// L2Mock_SuggestGasTipCap_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'SuggestGasTipCap' +type L2Mock_SuggestGasTipCap_Call struct { + *mock.Call +} + +// SuggestGasTipCap is a helper method to define mock.On call +// - ctx context.Context +func (_e *L2Mock_Expecter) SuggestGasTipCap(ctx interface{}) *L2Mock_SuggestGasTipCap_Call { + return &L2Mock_SuggestGasTipCap_Call{Call: _e.mock.On("SuggestGasTipCap", ctx)} +} + +func (_c *L2Mock_SuggestGasTipCap_Call) Run(run func(ctx context.Context)) *L2Mock_SuggestGasTipCap_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context)) + }) + return _c +} + +func (_c *L2Mock_SuggestGasTipCap_Call) Return(_a0 *big.Int, _a1 error) *L2Mock_SuggestGasTipCap_Call { + _c.Call.Return(_a0, _a1) + return _c +} + +func (_c *L2Mock_SuggestGasTipCap_Call) RunAndReturn(run func(context.Context) (*big.Int, error)) *L2Mock_SuggestGasTipCap_Call { + _c.Call.Return(run) + return _c +} + // TransactionCount provides a mock function with given fields: ctx, blockHash func (_m *L2Mock) TransactionCount(ctx context.Context, blockHash common.Hash) (uint, error) { ret := _m.Called(ctx, blockHash) @@ -510,6 +982,35 @@ func (_m *L2Mock) TransactionCount(ctx context.Context, blockHash common.Hash) ( return r0, r1 } +// L2Mock_TransactionCount_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'TransactionCount' +type L2Mock_TransactionCount_Call struct { + *mock.Call +} + +// TransactionCount is a helper method to define mock.On call +// - ctx context.Context +// - blockHash common.Hash +func (_e *L2Mock_Expecter) TransactionCount(ctx interface{}, blockHash interface{}) *L2Mock_TransactionCount_Call { + return &L2Mock_TransactionCount_Call{Call: _e.mock.On("TransactionCount", ctx, blockHash)} +} + +func (_c *L2Mock_TransactionCount_Call) Run(run func(ctx context.Context, blockHash common.Hash)) *L2Mock_TransactionCount_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context), args[1].(common.Hash)) + }) + return _c +} + +func (_c *L2Mock_TransactionCount_Call) Return(_a0 uint, _a1 error) *L2Mock_TransactionCount_Call { + _c.Call.Return(_a0, _a1) + return _c +} + +func (_c *L2Mock_TransactionCount_Call) RunAndReturn(run func(context.Context, common.Hash) (uint, error)) *L2Mock_TransactionCount_Call { + _c.Call.Return(run) + return _c +} + // TransactionInBlock provides a mock function with given fields: ctx, blockHash, index func (_m *L2Mock) TransactionInBlock(ctx context.Context, blockHash common.Hash, index uint) (*types.Transaction, error) { ret := _m.Called(ctx, blockHash, index) @@ -540,6 +1041,36 @@ func (_m *L2Mock) TransactionInBlock(ctx context.Context, blockHash common.Hash, return r0, r1 } +// L2Mock_TransactionInBlock_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'TransactionInBlock' +type L2Mock_TransactionInBlock_Call struct { + *mock.Call +} + +// TransactionInBlock is a helper method to define mock.On call +// - ctx context.Context +// - blockHash common.Hash +// - index uint +func (_e *L2Mock_Expecter) TransactionInBlock(ctx interface{}, blockHash interface{}, index interface{}) *L2Mock_TransactionInBlock_Call { + return &L2Mock_TransactionInBlock_Call{Call: _e.mock.On("TransactionInBlock", ctx, blockHash, index)} +} + +func (_c *L2Mock_TransactionInBlock_Call) Run(run func(ctx context.Context, blockHash common.Hash, index uint)) *L2Mock_TransactionInBlock_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context), args[1].(common.Hash), args[2].(uint)) + }) + return _c +} + +func (_c *L2Mock_TransactionInBlock_Call) Return(_a0 *types.Transaction, _a1 error) *L2Mock_TransactionInBlock_Call { + _c.Call.Return(_a0, _a1) + return _c +} + +func (_c *L2Mock_TransactionInBlock_Call) RunAndReturn(run func(context.Context, common.Hash, uint) (*types.Transaction, error)) *L2Mock_TransactionInBlock_Call { + _c.Call.Return(run) + return _c +} + // NewL2Mock creates a new instance of L2Mock. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. // The first argument is typically a *testing.T value. func NewL2Mock(t interface { diff --git a/sync/mock_processor_test.go b/sync/mock_processor_test.go index afbb34cb..96ece8d4 100644 --- a/sync/mock_processor_test.go +++ b/sync/mock_processor_test.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.39.0. DO NOT EDIT. +// Code generated by mockery. DO NOT EDIT. package sync @@ -13,6 +13,14 @@ type ProcessorMock struct { mock.Mock } +type ProcessorMock_Expecter struct { + mock *mock.Mock +} + +func (_m *ProcessorMock) EXPECT() *ProcessorMock_Expecter { + return &ProcessorMock_Expecter{mock: &_m.Mock} +} + // GetLastProcessedBlock provides a mock function with given fields: ctx func (_m *ProcessorMock) GetLastProcessedBlock(ctx context.Context) (uint64, error) { ret := _m.Called(ctx) @@ -41,6 +49,34 @@ func (_m *ProcessorMock) GetLastProcessedBlock(ctx context.Context) (uint64, err return r0, r1 } +// ProcessorMock_GetLastProcessedBlock_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GetLastProcessedBlock' +type ProcessorMock_GetLastProcessedBlock_Call struct { + *mock.Call +} + +// GetLastProcessedBlock is a helper method to define mock.On call +// - ctx context.Context +func (_e *ProcessorMock_Expecter) GetLastProcessedBlock(ctx interface{}) *ProcessorMock_GetLastProcessedBlock_Call { + return &ProcessorMock_GetLastProcessedBlock_Call{Call: _e.mock.On("GetLastProcessedBlock", ctx)} +} + +func (_c *ProcessorMock_GetLastProcessedBlock_Call) Run(run func(ctx context.Context)) *ProcessorMock_GetLastProcessedBlock_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context)) + }) + return _c +} + +func (_c *ProcessorMock_GetLastProcessedBlock_Call) Return(_a0 uint64, _a1 error) *ProcessorMock_GetLastProcessedBlock_Call { + _c.Call.Return(_a0, _a1) + return _c +} + +func (_c *ProcessorMock_GetLastProcessedBlock_Call) RunAndReturn(run func(context.Context) (uint64, error)) *ProcessorMock_GetLastProcessedBlock_Call { + _c.Call.Return(run) + return _c +} + // ProcessBlock provides a mock function with given fields: ctx, block func (_m *ProcessorMock) ProcessBlock(ctx context.Context, block Block) error { ret := _m.Called(ctx, block) @@ -59,6 +95,35 @@ func (_m *ProcessorMock) ProcessBlock(ctx context.Context, block Block) error { return r0 } +// ProcessorMock_ProcessBlock_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'ProcessBlock' +type ProcessorMock_ProcessBlock_Call struct { + *mock.Call +} + +// ProcessBlock is a helper method to define mock.On call +// - ctx context.Context +// - block Block +func (_e *ProcessorMock_Expecter) ProcessBlock(ctx interface{}, block interface{}) *ProcessorMock_ProcessBlock_Call { + return &ProcessorMock_ProcessBlock_Call{Call: _e.mock.On("ProcessBlock", ctx, block)} +} + +func (_c *ProcessorMock_ProcessBlock_Call) Run(run func(ctx context.Context, block Block)) *ProcessorMock_ProcessBlock_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context), args[1].(Block)) + }) + return _c +} + +func (_c *ProcessorMock_ProcessBlock_Call) Return(_a0 error) *ProcessorMock_ProcessBlock_Call { + _c.Call.Return(_a0) + return _c +} + +func (_c *ProcessorMock_ProcessBlock_Call) RunAndReturn(run func(context.Context, Block) error) *ProcessorMock_ProcessBlock_Call { + _c.Call.Return(run) + return _c +} + // Reorg provides a mock function with given fields: ctx, firstReorgedBlock func (_m *ProcessorMock) Reorg(ctx context.Context, firstReorgedBlock uint64) error { ret := _m.Called(ctx, firstReorgedBlock) @@ -77,6 +142,35 @@ func (_m *ProcessorMock) Reorg(ctx context.Context, firstReorgedBlock uint64) er return r0 } +// ProcessorMock_Reorg_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Reorg' +type ProcessorMock_Reorg_Call struct { + *mock.Call +} + +// Reorg is a helper method to define mock.On call +// - ctx context.Context +// - firstReorgedBlock uint64 +func (_e *ProcessorMock_Expecter) Reorg(ctx interface{}, firstReorgedBlock interface{}) *ProcessorMock_Reorg_Call { + return &ProcessorMock_Reorg_Call{Call: _e.mock.On("Reorg", ctx, firstReorgedBlock)} +} + +func (_c *ProcessorMock_Reorg_Call) Run(run func(ctx context.Context, firstReorgedBlock uint64)) *ProcessorMock_Reorg_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context), args[1].(uint64)) + }) + return _c +} + +func (_c *ProcessorMock_Reorg_Call) Return(_a0 error) *ProcessorMock_Reorg_Call { + _c.Call.Return(_a0) + return _c +} + +func (_c *ProcessorMock_Reorg_Call) RunAndReturn(run func(context.Context, uint64) error) *ProcessorMock_Reorg_Call { + _c.Call.Return(run) + return _c +} + // NewProcessorMock creates a new instance of ProcessorMock. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. // The first argument is typically a *testing.T value. func NewProcessorMock(t interface { diff --git a/sync/mock_reorgdetector_test.go b/sync/mock_reorgdetector_test.go index 9689f7e7..43551baa 100644 --- a/sync/mock_reorgdetector_test.go +++ b/sync/mock_reorgdetector_test.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.39.0. DO NOT EDIT. +// Code generated by mockery. DO NOT EDIT. package sync @@ -17,6 +17,14 @@ type ReorgDetectorMock struct { mock.Mock } +type ReorgDetectorMock_Expecter struct { + mock *mock.Mock +} + +func (_m *ReorgDetectorMock) EXPECT() *ReorgDetectorMock_Expecter { + return &ReorgDetectorMock_Expecter{mock: &_m.Mock} +} + // AddBlockToTrack provides a mock function with given fields: ctx, id, blockNum, blockHash func (_m *ReorgDetectorMock) AddBlockToTrack(ctx context.Context, id string, blockNum uint64, blockHash common.Hash) error { ret := _m.Called(ctx, id, blockNum, blockHash) @@ -35,6 +43,37 @@ func (_m *ReorgDetectorMock) AddBlockToTrack(ctx context.Context, id string, blo return r0 } +// ReorgDetectorMock_AddBlockToTrack_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'AddBlockToTrack' +type ReorgDetectorMock_AddBlockToTrack_Call struct { + *mock.Call +} + +// AddBlockToTrack is a helper method to define mock.On call +// - ctx context.Context +// - id string +// - blockNum uint64 +// - blockHash common.Hash +func (_e *ReorgDetectorMock_Expecter) AddBlockToTrack(ctx interface{}, id interface{}, blockNum interface{}, blockHash interface{}) *ReorgDetectorMock_AddBlockToTrack_Call { + return &ReorgDetectorMock_AddBlockToTrack_Call{Call: _e.mock.On("AddBlockToTrack", ctx, id, blockNum, blockHash)} +} + +func (_c *ReorgDetectorMock_AddBlockToTrack_Call) Run(run func(ctx context.Context, id string, blockNum uint64, blockHash common.Hash)) *ReorgDetectorMock_AddBlockToTrack_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context), args[1].(string), args[2].(uint64), args[3].(common.Hash)) + }) + return _c +} + +func (_c *ReorgDetectorMock_AddBlockToTrack_Call) Return(_a0 error) *ReorgDetectorMock_AddBlockToTrack_Call { + _c.Call.Return(_a0) + return _c +} + +func (_c *ReorgDetectorMock_AddBlockToTrack_Call) RunAndReturn(run func(context.Context, string, uint64, common.Hash) error) *ReorgDetectorMock_AddBlockToTrack_Call { + _c.Call.Return(run) + return _c +} + // Subscribe provides a mock function with given fields: id func (_m *ReorgDetectorMock) Subscribe(id string) (*reorgdetector.Subscription, error) { ret := _m.Called(id) @@ -65,6 +104,34 @@ func (_m *ReorgDetectorMock) Subscribe(id string) (*reorgdetector.Subscription, return r0, r1 } +// ReorgDetectorMock_Subscribe_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Subscribe' +type ReorgDetectorMock_Subscribe_Call struct { + *mock.Call +} + +// Subscribe is a helper method to define mock.On call +// - id string +func (_e *ReorgDetectorMock_Expecter) Subscribe(id interface{}) *ReorgDetectorMock_Subscribe_Call { + return &ReorgDetectorMock_Subscribe_Call{Call: _e.mock.On("Subscribe", id)} +} + +func (_c *ReorgDetectorMock_Subscribe_Call) Run(run func(id string)) *ReorgDetectorMock_Subscribe_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(string)) + }) + return _c +} + +func (_c *ReorgDetectorMock_Subscribe_Call) Return(_a0 *reorgdetector.Subscription, _a1 error) *ReorgDetectorMock_Subscribe_Call { + _c.Call.Return(_a0, _a1) + return _c +} + +func (_c *ReorgDetectorMock_Subscribe_Call) RunAndReturn(run func(string) (*reorgdetector.Subscription, error)) *ReorgDetectorMock_Subscribe_Call { + _c.Call.Return(run) + return _c +} + // NewReorgDetectorMock creates a new instance of ReorgDetectorMock. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. // The first argument is typically a *testing.T value. func NewReorgDetectorMock(t interface { diff --git a/test/Makefile b/test/Makefile index d4e3c274..05823e66 100644 --- a/test/Makefile +++ b/test/Makefile @@ -45,10 +45,11 @@ generate-mocks-helpers: ## Generates mocks for helpers , using mockery tool .PHONY: generate-mocks-sync generate-mocks-sync: ## Generates mocks for sync, using mockery tool - export "GOROOT=$$(go env GOROOT)" && $$(go env GOPATH)/bin/mockery --name=EthClienter --dir=../sync --output=../sync --outpkg=sync --inpackage --structname=L2Mock --filename=mock_l2_test.go - export "GOROOT=$$(go env GOROOT)" && $$(go env GOPATH)/bin/mockery --name=evmDownloaderFull --dir=../sync --output=../sync --outpkg=sync --inpackage --structname=EVMDownloaderMock --filename=mock_downloader_test.go - export "GOROOT=$$(go env GOROOT)" && $$(go env GOPATH)/bin/mockery --name=processorInterface --dir=../sync --output=../sync --outpkg=sync --inpackage --structname=ProcessorMock --filename=mock_processor_test.go - export "GOROOT=$$(go env GOROOT)" && $$(go env GOPATH)/bin/mockery --name=ReorgDetector --dir=../sync --output=../sync --outpkg=sync --inpackage --structname=ReorgDetectorMock --filename=mock_reorgdetector_test.go + export "GOROOT=$$(go env GOROOT)" && $$(go env GOPATH)/bin/mockery --name=EthClienter --dir=../sync --output=../sync --outpkg=sync --inpackage --structname=L2Mock --filename=mock_l2_test.go ${COMMON_MOCKERY_PARAMS} + export "GOROOT=$$(go env GOROOT)" && $$(go env GOPATH)/bin/mockery --name=evmDownloaderFull --dir=../sync --output=../sync --outpkg=sync --inpackage --structname=EVMDownloaderMock --filename=mock_downloader_test.go ${COMMON_MOCKERY_PARAMS} + export "GOROOT=$$(go env GOROOT)" && $$(go env GOPATH)/bin/mockery --name=processorInterface --dir=../sync --output=../sync --outpkg=sync --inpackage --structname=ProcessorMock --filename=mock_processor_test.go ${COMMON_MOCKERY_PARAMS} + export "GOROOT=$$(go env GOROOT)" && $$(go env GOPATH)/bin/mockery --name=ReorgDetector --dir=../sync --output=../sync --outpkg=sync --inpackage --structname=ReorgDetectorMock --filename=mock_reorgdetector_test.go ${COMMON_MOCKERY_PARAMS} + .PHONY: generate-mocks-aggregator generate-mocks-aggregator: ## Generates mocks for aggregator, using mockery tool diff --git a/test/bats/pp/bridge-e2e.bats b/test/bats/pp/bridge-e2e.bats index 953082a9..18ed5c86 100644 --- a/test/bats/pp/bridge-e2e.bats +++ b/test/bats/pp/bridge-e2e.bats @@ -55,7 +55,7 @@ setup() { echo "=== Running LxLy deposit on L1 to network: $l2_rpc_network_id native_token: $native_token_addr" >&3 destination_net=$l2_rpc_network_id - run bridgeAsset "$native_token_addr" "$l1_rpc_url" + run bridge_asset "$native_token_addr" "$l1_rpc_url" assert_success echo "=== Running LxLy claim on L2" >&3 @@ -70,7 +70,7 @@ setup() { echo "=== bridgeAsset L2 WETH: $weth_token_addr to L1 ETH" >&3 destination_addr=$sender_addr destination_net=0 - run bridgeAsset "$weth_token_addr" "$l2_rpc_url" + run bridge_asset "$weth_token_addr" "$l2_rpc_url" assert_success } From 521b9680b86f4d47de508cbd286e59bb3d3903dd Mon Sep 17 00:00:00 2001 From: Joan Esteban <129153821+joanestebanr@users.noreply.github.com> Date: Wed, 27 Nov 2024 10:14:43 +0100 Subject: [PATCH 15/23] fix: cdk603 error calculating previousLocalExitRoot (#199) Support `agglayer:0.2.0-rc.15` the responses of ` CertificateHeader` add a new field: `PreviousLocalExitRoot` - Fixes error calculating previousLocalExitRoot on new certificate if a certificate is inError NOTE: **Database incompatibility with previous version!, require to delete it!** --- agglayer/client_test.go | 20 ++- agglayer/types.go | 35 +++-- agglayer/types_test.go | 23 ++++ aggsender/aggsender.go | 103 +++++++++----- aggsender/aggsender_test.go | 160 ++++++++++++++++++---- aggsender/db/aggsender_db_storage_test.go | 37 +++++ aggsender/db/migrations/0001.sql | 1 + aggsender/mocks/logger.go | 44 ++++++ aggsender/types/types.go | 27 ++-- db/meddler.go | 19 ++- 10 files changed, 378 insertions(+), 91 deletions(-) diff --git a/agglayer/client_test.go b/agglayer/client_test.go index 09bea14e..c4117eb8 100644 --- a/agglayer/client_test.go +++ b/agglayer/client_test.go @@ -45,7 +45,7 @@ func TestExploratoryGetEpochConfiguration(t *testing.T) { func TestExploratoryGetLatestKnownCertificateHeader(t *testing.T) { t.Skip("This test is exploratory and should be skipped") - aggLayerClient := NewAggLayerClient("http://localhost:32781") + aggLayerClient := NewAggLayerClient("http://localhost:32843") cert, err := aggLayerClient.GetLatestKnownCertificateHeader(1) require.NoError(t, err) fmt.Print(cert) @@ -116,7 +116,9 @@ func TestGetLatestKnownCertificateHeaderOkResponse(t *testing.T) { cert, err := sut.GetLatestKnownCertificateHeader(1) require.NotNil(t, cert) require.NoError(t, err) + require.Nil(t, cert.PreviousLocalExitRoot) } + func TestGetLatestKnownCertificateHeaderErrorResponse(t *testing.T) { sut := NewAggLayerClient(testURL) jSONRPCCall = func(url, method string, params ...interface{}) (rpc.Response, error) { @@ -143,3 +145,19 @@ func TestGetLatestKnownCertificateHeaderResponseBadJson(t *testing.T) { require.Nil(t, cert) require.Error(t, err) } + +func TestGetLatestKnownCertificateHeaderWithPrevLERResponse(t *testing.T) { + sut := NewAggLayerClient(testURL) + response := rpc.Response{ + Result: []byte(`{"network_id":1,"height":0,"epoch_number":223,"certificate_index":0,"certificate_id":"0xf9179d2fbe535814b5a14496e2eed474f49c6131227a9dfc5d2d8caf9e212054","prev_local_exit_root":"0x27ae5ba08d7291c96c8cbddcc148bf48a6d68c7974b94356f53754ef6171d757","new_local_exit_root":"0x7ae06f4a5d0b6da7dd4973fb6ef40d82c9f2680899b3baaf9e564413b59cc160","metadata":"0x00000000000000000000000000000000000000000000000000000000000001a7","status":"Settled"}`), + } + jSONRPCCall = func(url, method string, params ...interface{}) (rpc.Response, error) { + return response, nil + } + cert, err := sut.GetLatestKnownCertificateHeader(1) + + require.NoError(t, err) + require.NotNil(t, cert) + + require.Equal(t, "0x27ae5ba08d7291c96c8cbddcc148bf48a6d68c7974b94356f53754ef6171d757", cert.PreviousLocalExitRoot.String()) +} diff --git a/agglayer/types.go b/agglayer/types.go index 09697d3d..c910beac 100644 --- a/agglayer/types.go +++ b/agglayer/types.go @@ -23,6 +23,8 @@ const ( Candidate InError Settled + + nilStr = "nil" ) var ( @@ -574,36 +576,41 @@ func (p *GenericPPError) String() string { // CertificateHeader is the structure returned by the interop_getCertificateHeader RPC call type CertificateHeader struct { - NetworkID uint32 `json:"network_id"` - Height uint64 `json:"height"` - EpochNumber *uint64 `json:"epoch_number"` - CertificateIndex *uint64 `json:"certificate_index"` - CertificateID common.Hash `json:"certificate_id"` - NewLocalExitRoot common.Hash `json:"new_local_exit_root"` - Status CertificateStatus `json:"status"` - Metadata common.Hash `json:"metadata"` - Error PPError `json:"-"` + NetworkID uint32 `json:"network_id"` + Height uint64 `json:"height"` + EpochNumber *uint64 `json:"epoch_number"` + CertificateIndex *uint64 `json:"certificate_index"` + CertificateID common.Hash `json:"certificate_id"` + PreviousLocalExitRoot *common.Hash `json:"prev_local_exit_root,omitempty"` + NewLocalExitRoot common.Hash `json:"new_local_exit_root"` + Status CertificateStatus `json:"status"` + Metadata common.Hash `json:"metadata"` + Error PPError `json:"-"` } // ID returns a string with the ident of this cert (height/certID) func (c *CertificateHeader) ID() string { if c == nil { - return "nil" + return nilStr } return fmt.Sprintf("%d/%s", c.Height, c.CertificateID.String()) } func (c *CertificateHeader) String() string { if c == nil { - return "nil" + return nilStr } 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) + previousLocalExitRoot := "nil" + if c.PreviousLocalExitRoot != nil { + previousLocalExitRoot = c.PreviousLocalExitRoot.String() + } + return fmt.Sprintf("Height: %d, CertificateID: %s, previousLocalExitRoot:%s, NewLocalExitRoot: %s. Status: %s."+ + " Errors: [%s]", + c.Height, c.CertificateID.String(), previousLocalExitRoot, c.NewLocalExitRoot.String(), c.Status.String(), errors) } func (c *CertificateHeader) UnmarshalJSON(data []byte) error { diff --git a/agglayer/types_test.go b/agglayer/types_test.go index ecef4bca..c19abf72 100644 --- a/agglayer/types_test.go +++ b/agglayer/types_test.go @@ -20,6 +20,29 @@ func TestMGenericPPError(t *testing.T) { require.Equal(t, "Generic error: test: value", err.String()) } +func TestCertificateHeaderID(t *testing.T) { + certificate := CertificateHeader{ + Height: 1, + CertificateID: common.HexToHash("0x123"), + } + require.Equal(t, "1/0x0000000000000000000000000000000000000000000000000000000000000123", certificate.ID()) + + var certNil *CertificateHeader + require.Equal(t, "nil", certNil.ID()) +} + +func TestCertificateHeaderString(t *testing.T) { + certificate := CertificateHeader{ + Height: 1, + CertificateID: common.HexToHash("0x123"), + } + require.Equal(t, "Height: 1, CertificateID: 0x0000000000000000000000000000000000000000000000000000000000000123, previousLocalExitRoot:nil, NewLocalExitRoot: 0x0000000000000000000000000000000000000000000000000000000000000000. Status: Pending. Errors: []", + certificate.String()) + + var certNil *CertificateHeader + require.Equal(t, "nil", certNil.String()) +} + func TestMarshalJSON(t *testing.T) { cert := SignedCertificate{ Certificate: &Certificate{ diff --git a/aggsender/aggsender.go b/aggsender/aggsender.go index eea9f125..9856a3bf 100644 --- a/aggsender/aggsender.go +++ b/aggsender/aggsender.go @@ -152,16 +152,14 @@ func (a *AggSender) sendCertificate(ctx context.Context) (*agglayer.SignedCertif if err != nil { return nil, err } - if lastSentCertificateInfo == nil { - // There are no certificates, so we set that to a empty one - lastSentCertificateInfo = &types.CertificateInfo{} - } - - previousToBlock := lastSentCertificateInfo.ToBlock - if lastSentCertificateInfo.Status == agglayer.InError { - // if the last certificate was in error, we need to resend it - // from the block before the error - previousToBlock = lastSentCertificateInfo.FromBlock - 1 + previousToBlock := uint64(0) + if lastSentCertificateInfo != nil { + previousToBlock = lastSentCertificateInfo.ToBlock + if lastSentCertificateInfo.Status == agglayer.InError { + // if the last certificate was in error, we need to resend it + // from the block before the error + previousToBlock = lastSentCertificateInfo.FromBlock - 1 + } } if previousToBlock >= lasL2BlockSynced { @@ -190,7 +188,7 @@ func (a *AggSender) sendCertificate(ctx context.Context) (*agglayer.SignedCertif a.log.Infof("building certificate for block: %d to block: %d", fromBlock, toBlock) - certificate, err := a.buildCertificate(ctx, bridges, claims, *lastSentCertificateInfo, toBlock) + certificate, err := a.buildCertificate(ctx, bridges, claims, lastSentCertificateInfo, toBlock) if err != nil { return nil, fmt.Errorf("error building certificate: %w", err) } @@ -215,15 +213,17 @@ func (a *AggSender) sendCertificate(ctx context.Context) (*agglayer.SignedCertif } createdTime := time.Now().UTC().UnixMilli() + prevLER := common.BytesToHash(certificate.PrevLocalExitRoot[:]) certInfo := types.CertificateInfo{ - Height: certificate.Height, - CertificateID: certificateHash, - NewLocalExitRoot: certificate.NewLocalExitRoot, - FromBlock: fromBlock, - ToBlock: toBlock, - CreatedAt: createdTime, - UpdatedAt: createdTime, - SignedCertificate: string(raw), + Height: certificate.Height, + CertificateID: certificateHash, + NewLocalExitRoot: certificate.NewLocalExitRoot, + PreviousLocalExitRoot: &prevLER, + FromBlock: fromBlock, + ToBlock: toBlock, + CreatedAt: createdTime, + UpdatedAt: createdTime, + SignedCertificate: string(raw), } // TODO: Improve this case, if a cert is not save in the storage, we are going to settle a unknown certificate err = a.saveCertificateToStorage(ctx, certInfo, a.cfg.MaxRetriesStoreCertificate) @@ -278,31 +278,53 @@ func (a *AggSender) saveCertificateToFile(signedCertificate *agglayer.SignedCert // getNextHeightAndPreviousLER returns the height and previous LER for the new certificate func (a *AggSender) getNextHeightAndPreviousLER( - lastSentCertificateInfo *types.CertificateInfo) (uint64, common.Hash) { - height := lastSentCertificateInfo.Height + 1 - if lastSentCertificateInfo.Status.IsInError() { - // previous certificate was in error, so we need to resend it - a.log.Debugf("Last certificate %s failed so reusing height %d", - lastSentCertificateInfo.CertificateID, lastSentCertificateInfo.Height) - height = lastSentCertificateInfo.Height + lastSentCertificateInfo *types.CertificateInfo) (uint64, common.Hash, error) { + if lastSentCertificateInfo == nil { + return 0, zeroLER, nil } - - previousLER := lastSentCertificateInfo.NewLocalExitRoot - if lastSentCertificateInfo.NewLocalExitRoot == (common.Hash{}) || - lastSentCertificateInfo.Height == 0 && lastSentCertificateInfo.Status.IsInError() { - // meaning this is the first certificate - height = 0 - previousLER = zeroLER + if !lastSentCertificateInfo.Status.IsClosed() { + return 0, zeroLER, fmt.Errorf("last certificate %s is not closed (status: %s)", + lastSentCertificateInfo.ID(), lastSentCertificateInfo.Status.String()) + } + if lastSentCertificateInfo.Status.IsSettled() { + return lastSentCertificateInfo.Height + 1, lastSentCertificateInfo.NewLocalExitRoot, nil } - return height, previousLER + if lastSentCertificateInfo.Status.IsInError() { + // We can reuse last one of lastCert? + if lastSentCertificateInfo.PreviousLocalExitRoot != nil { + return lastSentCertificateInfo.Height, *lastSentCertificateInfo.PreviousLocalExitRoot, nil + } + // Is the first one, so we can set the zeroLER + if lastSentCertificateInfo.Height == 0 { + return 0, zeroLER, nil + } + // We get previous certificate that must be settled + a.log.Debugf("last certificate %s is in error, getting previous settled certificate height:%d", + lastSentCertificateInfo.Height-1) + lastSettleCert, err := a.storage.GetCertificateByHeight(lastSentCertificateInfo.Height - 1) + if err != nil { + return 0, common.Hash{}, fmt.Errorf("error getting last settled certificate: %w", err) + } + if lastSettleCert == nil { + return 0, common.Hash{}, fmt.Errorf("none settled certificate: %w", err) + } + if !lastSettleCert.Status.IsSettled() { + return 0, common.Hash{}, fmt.Errorf("last settled certificate %s is not settled (status: %s)", + lastSettleCert.ID(), lastSettleCert.Status.String()) + } + + return lastSentCertificateInfo.Height, lastSettleCert.NewLocalExitRoot, nil + } + return 0, zeroLER, fmt.Errorf("last certificate %s has an unknown status: %s", + lastSentCertificateInfo.ID(), lastSentCertificateInfo.Status.String()) } // buildCertificate builds a certificate from the bridge events func (a *AggSender) buildCertificate(ctx context.Context, bridges []bridgesync.Bridge, claims []bridgesync.Claim, - lastSentCertificateInfo types.CertificateInfo, + lastSentCertificateInfo *types.CertificateInfo, toBlock uint64) (*agglayer.Certificate, error) { if len(bridges) == 0 && len(claims) == 0 { return nil, errNoBridgesAndClaims @@ -325,7 +347,10 @@ func (a *AggSender) buildCertificate(ctx context.Context, return nil, fmt.Errorf("error getting exit root by index: %d. Error: %w", depositCount, err) } - height, previousLER := a.getNextHeightAndPreviousLER(&lastSentCertificateInfo) + height, previousLER, err := a.getNextHeightAndPreviousLER(lastSentCertificateInfo) + if err != nil { + return nil, fmt.Errorf("error getting next height and previous LER: %w", err) + } return &agglayer.Certificate{ NetworkID: a.l2Syncer.OriginNetwork(), @@ -709,7 +734,7 @@ func NewCertificateInfoFromAgglayerCertHeader(c *agglayer.CertificateHeader) *ty return nil } now := time.Now().UTC().UnixMilli() - return &types.CertificateInfo{ + res := &types.CertificateInfo{ Height: c.Height, CertificateID: c.CertificateID, NewLocalExitRoot: c.NewLocalExitRoot, @@ -720,4 +745,8 @@ func NewCertificateInfoFromAgglayerCertHeader(c *agglayer.CertificateHeader) *ty UpdatedAt: now, SignedCertificate: "na/agglayer header", } + if c.PreviousLocalExitRoot != nil { + res.PreviousLocalExitRoot = c.PreviousLocalExitRoot + } + return res } diff --git a/aggsender/aggsender_test.go b/aggsender/aggsender_test.go index 9185050f..79504f6a 100644 --- a/aggsender/aggsender_test.go +++ b/aggsender/aggsender_test.go @@ -33,6 +33,8 @@ const ( var ( errTest = errors.New("unitest error") + ler1 = common.HexToHash("0x123") + ler2 = common.HexToHash("0x12345") ) func TestConfigString(t *testing.T) { @@ -283,7 +285,7 @@ func TestAggSenderStart(t *testing.T) { ctx, log.WithFields("test", "unittest"), Config{ - StoragePath: "file::memory:?cache=shared", + StoragePath: "file:TestAggSenderStart?mode=memory&cache=shared", DelayBeetweenRetries: types.Duration{Duration: 1 * time.Microsecond}, }, aggLayerMock, @@ -627,6 +629,7 @@ func TestBuildCertificate(t *testing.T) { lastSentCertificateInfo: aggsendertypes.CertificateInfo{ NewLocalExitRoot: common.HexToHash("0x123"), Height: 1, + Status: agglayer.Settled, }, toBlock: 10, expectedCert: &agglayer.Certificate{ @@ -784,7 +787,7 @@ func TestBuildCertificate(t *testing.T) { l1infoTreeSyncer: mockL1InfoTreeSyncer, log: log.WithFields("test", "unittest"), } - cert, err := aggSender.buildCertificate(context.Background(), tt.bridges, tt.claims, tt.lastSentCertificateInfo, tt.toBlock) + cert, err := aggSender.buildCertificate(context.Background(), tt.bridges, tt.claims, &tt.lastSentCertificateInfo, tt.toBlock) if tt.expectedError { require.Error(t, err) @@ -957,7 +960,7 @@ func TestSendCertificate(t *testing.T) { var ( aggsender = &AggSender{ log: log.WithFields("aggsender", 1), - cfg: Config{MaxRetriesStoreCertificate: 3}, + cfg: Config{MaxRetriesStoreCertificate: 1}, sequencerKey: cfg.sequencerKey, } mockStorage *mocks.AggSenderStorage @@ -1223,11 +1226,13 @@ func TestSendCertificate(t *testing.T) { shouldSendCertificate: []interface{}{[]*aggsendertypes.CertificateInfo{}, nil}, lastL2BlockProcessed: []interface{}{uint64(99), nil}, getLastSentCertificate: []interface{}{&aggsendertypes.CertificateInfo{ - Height: 90, - CertificateID: common.HexToHash("0x1121111"), - NewLocalExitRoot: common.HexToHash("0x111122211"), - FromBlock: 80, - ToBlock: 81, + Height: 90, + CertificateID: common.HexToHash("0x1121111"), + NewLocalExitRoot: common.HexToHash("0x111122211"), + PreviousLocalExitRoot: &ler1, + FromBlock: 80, + ToBlock: 81, + Status: agglayer.Settled, }, nil}, getBridges: []interface{}{[]bridgesync.Bridge{ { @@ -1255,6 +1260,7 @@ func TestSendCertificate(t *testing.T) { NewLocalExitRoot: common.HexToHash("0x1211122211"), FromBlock: 90, ToBlock: 91, + Status: agglayer.Settled, }, nil}, getBridges: []interface{}{[]bridgesync.Bridge{ { @@ -1283,6 +1289,7 @@ func TestSendCertificate(t *testing.T) { NewLocalExitRoot: common.HexToHash("0x1221122211"), FromBlock: 100, ToBlock: 101, + Status: agglayer.Settled, }, nil}, getBridges: []interface{}{[]bridgesync.Bridge{ { @@ -1489,14 +1496,18 @@ func TestGetNextHeightAndPreviousLER(t *testing.T) { t.Parallel() tests := []struct { - name string - lastSentCertificateInfo aggsendertypes.CertificateInfo - expectedHeight uint64 - expectedPreviousLER common.Hash + name string + lastSentCertificateInfo *aggsendertypes.CertificateInfo + lastSettleCertificateInfoCall bool + lastSettleCertificateInfo *aggsendertypes.CertificateInfo + lastSettleCertificateInfoError error + expectedHeight uint64 + expectedPreviousLER common.Hash + expectedError bool }{ { name: "Normal case", - lastSentCertificateInfo: aggsendertypes.CertificateInfo{ + lastSentCertificateInfo: &aggsendertypes.CertificateInfo{ Height: 10, NewLocalExitRoot: common.HexToHash("0x123"), Status: agglayer.Settled, @@ -1505,24 +1516,107 @@ func TestGetNextHeightAndPreviousLER(t *testing.T) { expectedPreviousLER: common.HexToHash("0x123"), }, { - name: "Previous certificate in error", - lastSentCertificateInfo: aggsendertypes.CertificateInfo{ - Height: 10, + name: "First certificate", + lastSentCertificateInfo: nil, + expectedHeight: 0, + expectedPreviousLER: zeroLER, + }, + { + name: "First certificate error, with prevLER", + lastSentCertificateInfo: &aggsendertypes.CertificateInfo{ + Height: 0, + NewLocalExitRoot: common.HexToHash("0x123"), + Status: agglayer.InError, + PreviousLocalExitRoot: &ler1, + }, + expectedHeight: 0, + expectedPreviousLER: ler1, + }, + { + name: "First certificate error, no prevLER", + lastSentCertificateInfo: &aggsendertypes.CertificateInfo{ + Height: 0, NewLocalExitRoot: common.HexToHash("0x123"), Status: agglayer.InError, }, + expectedHeight: 0, + expectedPreviousLER: zeroLER, + }, + { + name: "n certificate error, prevLER", + lastSentCertificateInfo: &aggsendertypes.CertificateInfo{ + Height: 10, + NewLocalExitRoot: common.HexToHash("0x123"), + PreviousLocalExitRoot: &ler1, + Status: agglayer.InError, + }, expectedHeight: 10, - expectedPreviousLER: common.HexToHash("0x123"), + expectedPreviousLER: ler1, }, { - name: "First certificate", - lastSentCertificateInfo: aggsendertypes.CertificateInfo{ - Height: 0, - NewLocalExitRoot: common.Hash{}, + name: "last cert not closed, error", + lastSentCertificateInfo: &aggsendertypes.CertificateInfo{ + Height: 10, + NewLocalExitRoot: common.HexToHash("0x123"), + PreviousLocalExitRoot: &ler1, + Status: agglayer.Pending, + }, + expectedHeight: 10, + expectedPreviousLER: ler1, + expectedError: true, + }, + { + name: "Previous certificate in error, no prevLER", + lastSentCertificateInfo: &aggsendertypes.CertificateInfo{ + Height: 10, + NewLocalExitRoot: common.HexToHash("0x123"), + Status: agglayer.InError, + }, + lastSettleCertificateInfo: &aggsendertypes.CertificateInfo{ + Height: 9, + NewLocalExitRoot: common.HexToHash("0x3456"), Status: agglayer.Settled, }, - expectedHeight: 0, - expectedPreviousLER: zeroLER, + expectedHeight: 10, + expectedPreviousLER: common.HexToHash("0x3456"), + }, + { + name: "Previous certificate in error, no prevLER. Error getting previous cert", + lastSentCertificateInfo: &aggsendertypes.CertificateInfo{ + Height: 10, + NewLocalExitRoot: common.HexToHash("0x123"), + Status: agglayer.InError, + }, + lastSettleCertificateInfo: nil, + lastSettleCertificateInfoError: errors.New("error getting last settle certificate"), + expectedError: true, + }, + { + name: "Previous certificate in error, no prevLER. prev cert not available on storage", + lastSentCertificateInfo: &aggsendertypes.CertificateInfo{ + Height: 10, + NewLocalExitRoot: common.HexToHash("0x123"), + Status: agglayer.InError, + }, + lastSettleCertificateInfoCall: true, + lastSettleCertificateInfo: nil, + lastSettleCertificateInfoError: nil, + expectedError: true, + }, + { + name: "Previous certificate in error, no prevLER. prev cert not available on storage", + lastSentCertificateInfo: &aggsendertypes.CertificateInfo{ + Height: 10, + NewLocalExitRoot: common.HexToHash("0x123"), + Status: agglayer.InError, + }, + lastSettleCertificateInfo: &aggsendertypes.CertificateInfo{ + Height: 9, + NewLocalExitRoot: common.HexToHash("0x3456"), + Status: agglayer.InError, + }, + lastSettleCertificateInfoError: nil, + expectedError: true, }, } @@ -1531,12 +1625,19 @@ func TestGetNextHeightAndPreviousLER(t *testing.T) { t.Run(tt.name, func(t *testing.T) { t.Parallel() - - aggSender := &AggSender{log: log.WithFields("aggsender-test", "getNextHeightAndPreviousLER")} - height, previousLER := aggSender.getNextHeightAndPreviousLER(&tt.lastSentCertificateInfo) - - require.Equal(t, tt.expectedHeight, height) - require.Equal(t, tt.expectedPreviousLER, previousLER) + storageMock := mocks.NewAggSenderStorage(t) + aggSender := &AggSender{log: log.WithFields("aggsender-test", "getNextHeightAndPreviousLER"), storage: storageMock} + if tt.lastSettleCertificateInfoCall || tt.lastSettleCertificateInfo != nil || tt.lastSettleCertificateInfoError != nil { + storageMock.EXPECT().GetCertificateByHeight(mock.Anything).Return(tt.lastSettleCertificateInfo, tt.lastSettleCertificateInfoError).Once() + } + height, previousLER, err := aggSender.getNextHeightAndPreviousLER(tt.lastSentCertificateInfo) + if tt.expectedError { + require.Error(t, err) + } else { + require.NoError(t, err) + require.Equal(t, tt.expectedHeight, height) + require.Equal(t, tt.expectedPreviousLER, previousLER) + } }) } } @@ -1572,6 +1673,7 @@ func TestSendCertificate_NoClaims(t *testing.T) { Height: 1, FromBlock: 0, ToBlock: 10, + Status: agglayer.Settled, }, nil).Once() mockStorage.On("SaveLastSentCertificate", mock.Anything, mock.Anything).Return(nil).Once() mockL2Syncer.On("GetLastProcessedBlock", mock.Anything).Return(uint64(50), nil) diff --git a/aggsender/db/aggsender_db_storage_test.go b/aggsender/db/aggsender_db_storage_test.go index 642c8ae7..15c017bc 100644 --- a/aggsender/db/aggsender_db_storage_test.go +++ b/aggsender/db/aggsender_db_storage_test.go @@ -368,3 +368,40 @@ func Test_SaveLastSentCertificate(t *testing.T) { require.NoError(t, storage.clean()) }) } + +func Test_StoragePreviousLER(t *testing.T) { + ctx := context.TODO() + dbPath := path.Join(t.TempDir(), "Test_StoragePreviousLER.sqlite") + storage, err := NewAggSenderSQLStorage(log.WithFields("aggsender-db"), dbPath) + require.NoError(t, err) + require.NotNil(t, storage) + + certNoLER := types.CertificateInfo{ + Height: 0, + CertificateID: common.HexToHash("0x1"), + Status: agglayer.InError, + NewLocalExitRoot: common.HexToHash("0x2"), + } + err = storage.SaveLastSentCertificate(ctx, certNoLER) + require.NoError(t, err) + + readCertNoLER, err := storage.GetCertificateByHeight(0) + require.NoError(t, err) + require.NotNil(t, readCertNoLER) + require.Equal(t, certNoLER, *readCertNoLER) + + certLER := types.CertificateInfo{ + Height: 1, + CertificateID: common.HexToHash("0x2"), + Status: agglayer.InError, + NewLocalExitRoot: common.HexToHash("0x2"), + PreviousLocalExitRoot: &common.Hash{}, + } + err = storage.SaveLastSentCertificate(ctx, certLER) + require.NoError(t, err) + + readCertWithLER, err := storage.GetCertificateByHeight(1) + require.NoError(t, err) + require.NotNil(t, readCertWithLER) + require.Equal(t, certLER, *readCertWithLER) +} diff --git a/aggsender/db/migrations/0001.sql b/aggsender/db/migrations/0001.sql index b2d600b8..bdc2b3d3 100644 --- a/aggsender/db/migrations/0001.sql +++ b/aggsender/db/migrations/0001.sql @@ -6,6 +6,7 @@ CREATE TABLE certificate_info ( height INTEGER NOT NULL, certificate_id VARCHAR NOT NULL PRIMARY KEY, status INTEGER NOT NULL, + previous_local_exit_root VARCHAR , new_local_exit_root VARCHAR NOT NULL, from_block INTEGER NOT NULL, to_block INTEGER NOT NULL, diff --git a/aggsender/mocks/logger.go b/aggsender/mocks/logger.go index bb26739e..54be6942 100644 --- a/aggsender/mocks/logger.go +++ b/aggsender/mocks/logger.go @@ -189,6 +189,50 @@ func (_c *Logger_Errorf_Call) RunAndReturn(run func(string, ...interface{})) *Lo return _c } +// Fatalf provides a mock function with given fields: format, args +func (_m *Logger) Fatalf(format string, args ...interface{}) { + var _ca []interface{} + _ca = append(_ca, format) + _ca = append(_ca, args...) + _m.Called(_ca...) +} + +// Logger_Fatalf_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Fatalf' +type Logger_Fatalf_Call struct { + *mock.Call +} + +// Fatalf is a helper method to define mock.On call +// - format string +// - args ...interface{} +func (_e *Logger_Expecter) Fatalf(format interface{}, args ...interface{}) *Logger_Fatalf_Call { + return &Logger_Fatalf_Call{Call: _e.mock.On("Fatalf", + append([]interface{}{format}, args...)...)} +} + +func (_c *Logger_Fatalf_Call) Run(run func(format string, args ...interface{})) *Logger_Fatalf_Call { + _c.Call.Run(func(args mock.Arguments) { + variadicArgs := make([]interface{}, len(args)-1) + for i, a := range args[1:] { + if a != nil { + variadicArgs[i] = a.(interface{}) + } + } + run(args[0].(string), variadicArgs...) + }) + return _c +} + +func (_c *Logger_Fatalf_Call) Return() *Logger_Fatalf_Call { + _c.Call.Return() + return _c +} + +func (_c *Logger_Fatalf_Call) RunAndReturn(run func(string, ...interface{})) *Logger_Fatalf_Call { + _c.Call.Return(run) + return _c +} + // Info provides a mock function with given fields: args func (_m *Logger) Info(args ...interface{}) { var _ca []interface{} diff --git a/aggsender/types/types.go b/aggsender/types/types.go index d6f2650e..3199932b 100644 --- a/aggsender/types/types.go +++ b/aggsender/types/types.go @@ -55,24 +55,32 @@ type Logger interface { } type CertificateInfo struct { - Height uint64 `meddler:"height"` - CertificateID common.Hash `meddler:"certificate_id,hash"` - NewLocalExitRoot common.Hash `meddler:"new_local_exit_root,hash"` - FromBlock uint64 `meddler:"from_block"` - ToBlock uint64 `meddler:"to_block"` - Status agglayer.CertificateStatus `meddler:"status"` - CreatedAt int64 `meddler:"created_at"` - UpdatedAt int64 `meddler:"updated_at"` - SignedCertificate string `meddler:"signed_certificate"` + Height uint64 `meddler:"height"` + CertificateID common.Hash `meddler:"certificate_id,hash"` + // PreviousLocalExitRoot if it's nil means no reported + PreviousLocalExitRoot *common.Hash `meddler:"previous_local_exit_root,hash"` + NewLocalExitRoot common.Hash `meddler:"new_local_exit_root,hash"` + FromBlock uint64 `meddler:"from_block"` + ToBlock uint64 `meddler:"to_block"` + Status agglayer.CertificateStatus `meddler:"status"` + CreatedAt int64 `meddler:"created_at"` + UpdatedAt int64 `meddler:"updated_at"` + SignedCertificate string `meddler:"signed_certificate"` } func (c *CertificateInfo) String() string { if c == nil { + //nolint:all return "nil" } + previousLocalExitRoot := "nil" + if c.PreviousLocalExitRoot != nil { + previousLocalExitRoot = c.PreviousLocalExitRoot.String() + } return fmt.Sprintf( "Height: %d "+ "CertificateID: %s "+ + "PreviousLocalExitRoot: %s "+ "NewLocalExitRoot: %s "+ "Status: %s "+ "FromBlock: %d "+ @@ -81,6 +89,7 @@ func (c *CertificateInfo) String() string { "UpdatedAt: %s", c.Height, c.CertificateID.String(), + previousLocalExitRoot, c.NewLocalExitRoot.String(), c.Status.String(), c.FromBlock, diff --git a/db/meddler.go b/db/meddler.go index 8dd17fe8..6eaa5994 100644 --- a/db/meddler.go +++ b/db/meddler.go @@ -163,6 +163,16 @@ func (b HashMeddler) PostRead(fieldPtr, scanTarget interface{}) error { } field, ok := fieldPtr.(*common.Hash) if !ok { + hashPtr, ok := fieldPtr.(**common.Hash) + if ok { + if ptr == nil || len(*ptr) == 0 { + *hashPtr = nil + } else { + tmp := common.HexToHash(*ptr) + *hashPtr = &tmp + } + return nil + } return errors.New("fieldPtr is not common.Hash") } *field = common.HexToHash(*ptr) @@ -173,7 +183,14 @@ func (b HashMeddler) PostRead(fieldPtr, scanTarget interface{}) error { func (b HashMeddler) PreWrite(fieldPtr interface{}) (saveValue interface{}, err error) { field, ok := fieldPtr.(common.Hash) if !ok { - return nil, errors.New("fieldPtr is not common.Hash") + hashPtr, ok := fieldPtr.(*common.Hash) + if !ok { + return nil, errors.New("fieldPtr is not common.Hash") + } + if hashPtr == nil { + return []byte{}, nil + } + return hashPtr.Hex(), nil } return field.Hex(), nil } From 03b4e7e2993ead2955fc5c94789e3c9bfc79b364 Mon Sep 17 00:00:00 2001 From: Joan Esteban <129153821+joanestebanr@users.noreply.github.com> Date: Thu, 28 Nov 2024 09:50:39 +0100 Subject: [PATCH 16/23] feat: improve logs (#204) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Avoid to log full certificate because it's too long - Add logs to `bridgesync` creation - Reduce log for `l1infotreesync/processor.go:401` `block 7157878 processed with 0 events` in case of 0 events --------- Co-authored-by: Stefan Negovanović <93934272+Stefan-Ethernal@users.noreply.github.com> --- agglayer/types.go | 33 ++++++++++---------------------- aggsender/aggsender.go | 14 ++++++++------ aggsender/db/migrations/0001.sql | 2 +- aggsender/types/types.go | 18 ++++++++--------- bridgesync/bridgesync.go | 11 ++++++++++- bridgesync/processor.go | 4 ++-- bridgesync/processor_test.go | 9 ++++++--- l1infotreesync/processor.go | 7 +++++-- 8 files changed, 51 insertions(+), 47 deletions(-) diff --git a/agglayer/types.go b/agglayer/types.go index c910beac..71a37deb 100644 --- a/agglayer/types.go +++ b/agglayer/types.go @@ -112,27 +112,14 @@ type Certificate struct { Metadata common.Hash `json:"metadata"` } -func (c *Certificate) String() string { - res := fmt.Sprintf("NetworkID: %d, Height: %d, PrevLocalExitRoot: %s, NewLocalExitRoot: %s, Metadata: %s\n", - c.NetworkID, c.Height, common.Bytes2Hex(c.PrevLocalExitRoot[:]), - common.Bytes2Hex(c.NewLocalExitRoot[:]), common.Bytes2Hex(c.Metadata[:])) - - if c.BridgeExits == nil { - res += " BridgeExits: nil\n" - } else { - for i, bridgeExit := range c.BridgeExits { - res += fmt.Sprintf(", BridgeExit[%d]: %s\n", i, bridgeExit.String()) - } - } - - if c.ImportedBridgeExits == nil { - res += " ImportedBridgeExits: nil\n" - } else { - for i, importedBridgeExit := range c.ImportedBridgeExits { - res += fmt.Sprintf(" ImportedBridgeExit[%d]: %s\n", i, importedBridgeExit.String()) - } +// Brief returns a string with a brief cert +func (c *Certificate) Brief() string { + if c == nil { + return nilStr } - + res := fmt.Sprintf("agglayer.Cert {height: %d prevLER: %s newLER: %s exits: %d imported_exits: %d}", c.Height, + common.Bytes2Hex(c.PrevLocalExitRoot[:]), common.Bytes2Hex(c.NewLocalExitRoot[:]), + len(c.BridgeExits), len(c.ImportedBridgeExits)) return res } @@ -181,8 +168,8 @@ type SignedCertificate struct { Signature *Signature `json:"signature"` } -func (s *SignedCertificate) String() string { - return fmt.Sprintf("Certificate:%s,\nSignature: %s", s.Certificate.String(), s.Signature.String()) +func (s *SignedCertificate) Brief() string { + return fmt.Sprintf("Certificate:%s,\nSignature: %s", s.Certificate.Brief(), s.Signature.String()) } // CopyWithDefaulting returns a shallow copy of the signed certificate @@ -604,7 +591,7 @@ func (c *CertificateHeader) String() string { if c.Error != nil { errors = c.Error.String() } - previousLocalExitRoot := "nil" + previousLocalExitRoot := nilStr if c.PreviousLocalExitRoot != nil { previousLocalExitRoot = c.PreviousLocalExitRoot.String() } diff --git a/aggsender/aggsender.go b/aggsender/aggsender.go index 9856a3bf..42e518fd 100644 --- a/aggsender/aggsender.go +++ b/aggsender/aggsender.go @@ -199,17 +199,17 @@ func (a *AggSender) sendCertificate(ctx context.Context) (*agglayer.SignedCertif } a.saveCertificateToFile(signedCertificate) - a.log.Infof("certificate ready to be send to AggLayer: %s", signedCertificate.String()) + a.log.Infof("certificate ready to be send to AggLayer: %s", signedCertificate.Brief()) certificateHash, err := a.aggLayerClient.SendCertificate(signedCertificate) if err != nil { return nil, fmt.Errorf("error sending certificate: %w", err) } - a.log.Debugf("certificate send: Height: %d hash: %s", signedCertificate.Height, certificateHash.String()) + a.log.Debugf("certificate send: Height: %d cert: %s", signedCertificate.Height, signedCertificate.Brief()) raw, err := json.Marshal(signedCertificate) if err != nil { - return nil, fmt.Errorf("error marshalling signed certificate: %w", err) + return nil, fmt.Errorf("error marshalling signed certificate. Cert:%s. Err: %w", signedCertificate.Brief(), err) } createdTime := time.Now().UTC().UnixMilli() @@ -228,12 +228,12 @@ func (a *AggSender) sendCertificate(ctx context.Context) (*agglayer.SignedCertif // TODO: Improve this case, if a cert is not save in the storage, we are going to settle a unknown certificate err = a.saveCertificateToStorage(ctx, certInfo, a.cfg.MaxRetriesStoreCertificate) if err != nil { - a.log.Errorf("error saving certificate to storage: %w", err) + a.log.Errorf("error saving certificate to storage. Cert:%s Err: %w", certInfo.String(), err) return nil, fmt.Errorf("error saving last sent certificate %s in db: %w", certInfo.String(), err) } a.log.Infof("certificate: %s sent successfully for range of l2 blocks (from block: %d, to block: %d) cert:%s", - certificateHash, fromBlock, toBlock, signedCertificate.String()) + certInfo.ID(), fromBlock, toBlock, signedCertificate.Brief()) return signedCertificate, nil } @@ -454,7 +454,9 @@ func (a *AggSender) getImportedBridgeExits( for i, claim := range claims { l1Info := claimL1Info[i] - a.log.Debugf("claim[%d]: destAddr: %s GER:%s", i, claim.DestinationAddress.String(), claim.GlobalExitRoot.String()) + a.log.Debugf("claim[%d]: destAddr: %s GER: %s Block: %d Pos: %d GlobalIndex: 0x%x", + i, claim.DestinationAddress.String(), claim.GlobalExitRoot.String(), + claim.BlockNum, claim.BlockPos, claim.GlobalIndex) ibe, err := a.convertClaimToImportedBridgeExit(claim) if err != nil { return nil, fmt.Errorf("error converting claim to imported bridge exit: %w", err) diff --git a/aggsender/db/migrations/0001.sql b/aggsender/db/migrations/0001.sql index bdc2b3d3..ebc68c51 100644 --- a/aggsender/db/migrations/0001.sql +++ b/aggsender/db/migrations/0001.sql @@ -6,7 +6,7 @@ CREATE TABLE certificate_info ( height INTEGER NOT NULL, certificate_id VARCHAR NOT NULL PRIMARY KEY, status INTEGER NOT NULL, - previous_local_exit_root VARCHAR , + previous_local_exit_root VARCHAR, new_local_exit_root VARCHAR NOT NULL, from_block INTEGER NOT NULL, to_block INTEGER NOT NULL, diff --git a/aggsender/types/types.go b/aggsender/types/types.go index 3199932b..1c0d8353 100644 --- a/aggsender/types/types.go +++ b/aggsender/types/types.go @@ -77,16 +77,16 @@ func (c *CertificateInfo) String() string { if c.PreviousLocalExitRoot != nil { previousLocalExitRoot = c.PreviousLocalExitRoot.String() } - return fmt.Sprintf( + return fmt.Sprintf("aggsender.CertificateInfo: "+ "Height: %d "+ - "CertificateID: %s "+ - "PreviousLocalExitRoot: %s "+ - "NewLocalExitRoot: %s "+ - "Status: %s "+ - "FromBlock: %d "+ - "ToBlock: %d "+ - "CreatedAt: %s "+ - "UpdatedAt: %s", + "CertificateID: %s "+ + "PreviousLocalExitRoot: %s "+ + "NewLocalExitRoot: %s "+ + "Status: %s "+ + "FromBlock: %d "+ + "ToBlock: %d "+ + "CreatedAt: %s "+ + "UpdatedAt: %s", c.Height, c.CertificateID.String(), previousLocalExitRoot, diff --git a/bridgesync/bridgesync.go b/bridgesync/bridgesync.go index b3c3c853..dc0ca8d4 100644 --- a/bridgesync/bridgesync.go +++ b/bridgesync/bridgesync.go @@ -5,6 +5,7 @@ import ( "time" "github.com/0xPolygon/cdk/etherman" + "github.com/0xPolygon/cdk/log" "github.com/0xPolygon/cdk/sync" tree "github.com/0xPolygon/cdk/tree/types" "github.com/ethereum/go-ethereum/common" @@ -111,7 +112,8 @@ func newBridgeSync( originNetwork uint32, syncFullClaims bool, ) (*BridgeSync, error) { - processor, err := newProcessor(dbPath, l1OrL2ID) + logger := log.WithFields("bridge-syncer", l1OrL2ID) + processor, err := newProcessor(dbPath, logger) if err != nil { return nil, err } @@ -156,6 +158,13 @@ func newBridgeSync( if err != nil { return nil, err } + logger.Infof("BridgeSyncer [%s] created: dbPath: %s initialBlock: %d bridgeAddr: %s, syncFullClaims: %d,"+ + " maxRetryAttemptsAfterError: %d RetryAfterErrorPeriod: %s"+ + "syncBlockChunkSize: %d, blockFinalityType: %s waitForNewBlocksPeriod: %s", + l1OrL2ID, + dbPath, initialBlock, bridge.String(), syncFullClaims, + maxRetryAttemptsAfterError, retryAfterErrorPeriod.String(), + syncBlockChunkSize, blockFinalityType, waitForNewBlocksPeriod.String()) return &BridgeSync{ processor: processor, diff --git a/bridgesync/processor.go b/bridgesync/processor.go index b2e0ed24..249f890c 100644 --- a/bridgesync/processor.go +++ b/bridgesync/processor.go @@ -109,7 +109,7 @@ type processor struct { bridgeContract BridgeContractor } -func newProcessor(dbPath, loggerPrefix string) (*processor, error) { +func newProcessor(dbPath string, logger *log.Logger) (*processor, error) { err := migrations.RunMigrations(dbPath) if err != nil { return nil, err @@ -118,7 +118,7 @@ func newProcessor(dbPath, loggerPrefix string) (*processor, error) { if err != nil { return nil, err } - logger := log.WithFields("bridge-syncer", loggerPrefix) + exitTree := tree.NewAppendOnlyTree(db, "") return &processor{ db: db, diff --git a/bridgesync/processor_test.go b/bridgesync/processor_test.go index ab31f17d..21f4d51a 100644 --- a/bridgesync/processor_test.go +++ b/bridgesync/processor_test.go @@ -82,7 +82,8 @@ func TestProceessor(t *testing.T) { log.Debugf("sqlite path: %s", path) err := migrationsBridge.RunMigrations(path) require.NoError(t, err) - p, err := newProcessor(path, "foo") + logger := log.WithFields("bridge-syncer", "foo") + p, err := newProcessor(path, logger) require.NoError(t, err) actions := []processAction{ // processed: ~ @@ -735,7 +736,8 @@ func TestInsertAndGetClaim(t *testing.T) { log.Debugf("sqlite path: %s", path) err := migrationsBridge.RunMigrations(path) require.NoError(t, err) - p, err := newProcessor(path, "foo") + logger := log.WithFields("bridge-syncer", "foo") + p, err := newProcessor(path, logger) require.NoError(t, err) tx, err := p.db.BeginTx(context.Background(), nil) @@ -828,7 +830,8 @@ func TestGetBridgesPublished(t *testing.T) { path := path.Join(t.TempDir(), "file::memory:?cache=shared") require.NoError(t, migrationsBridge.RunMigrations(path)) - p, err := newProcessor(path, "foo") + logger := log.WithFields("bridge-syncer", "foo") + p, err := newProcessor(path, logger) require.NoError(t, err) tx, err := p.db.BeginTx(context.Background(), nil) diff --git a/l1infotreesync/processor.go b/l1infotreesync/processor.go index ac58fb5c..dc830afc 100644 --- a/l1infotreesync/processor.go +++ b/l1infotreesync/processor.go @@ -397,8 +397,11 @@ func (p *processor) ProcessBlock(ctx context.Context, block sync.Block) error { return fmt.Errorf("err: %w", err) } shouldRollback = false - - log.Infof("block %d processed with %d events", block.Num, len(block.Events)) + logFunc := log.Debugf + if len(block.Events) > 0 { + logFunc = log.Infof + } + logFunc("block %d processed with %d events", block.Num, len(block.Events)) return nil } From f8a2f230ca52a0abfe0e5276fa6920cc86d82fe6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Toni=20Ram=C3=ADrez?= <58293609+ToniRamirezM@users.noreply.github.com> Date: Fri, 29 Nov 2024 15:00:51 +0100 Subject: [PATCH 17/23] fix: clean proof table on start (#207) (#210) * fix: clean proof table on start --- aggregator/aggregator.go | 12 ++++++------ aggregator/aggregator_test.go | 2 +- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/aggregator/aggregator.go b/aggregator/aggregator.go index d9ecb1a2..a817320c 100644 --- a/aggregator/aggregator.go +++ b/aggregator/aggregator.go @@ -325,12 +325,6 @@ func (a *Aggregator) Start() error { healthService := newHealthChecker() grpchealth.RegisterHealthServer(a.srv, healthService) - // Delete ungenerated recursive proofs - err = a.storage.DeleteUngeneratedProofs(a.ctx, nil) - if err != nil { - return fmt.Errorf("failed to initialize proofs cache %w", err) - } - // Get last verified batch number to set the starting point for verifications lastVerifiedBatchNumber, err := a.etherman.GetLatestVerifiedBatchNum() if err != nil { @@ -347,6 +341,12 @@ func (a *Aggregator) Start() error { a.logger.Infof("Starting AccInputHash:%v", accInputHash.String()) a.setAccInputHash(lastVerifiedBatchNumber, *accInputHash) + // Delete existing proofs + err = a.storage.DeleteGeneratedProofs(a.ctx, lastVerifiedBatchNumber, maxDBBigIntValue, nil) + if err != nil { + return fmt.Errorf("failed to delete proofs table %w", err) + } + a.resetVerifyProofTime() go a.cleanupLockedProofs() diff --git a/aggregator/aggregator_test.go b/aggregator/aggregator_test.go index 95b55367..086550eb 100644 --- a/aggregator/aggregator_test.go +++ b/aggregator/aggregator_test.go @@ -86,7 +86,7 @@ func Test_Start(t *testing.T) { mockL1Syncr.On("Sync", mock.Anything).Return(nil) mockEtherman.On("GetLatestVerifiedBatchNum").Return(uint64(90), nil).Once() mockEtherman.On("GetBatchAccInputHash", mock.Anything, uint64(90)).Return(common.Hash{}, nil).Once() - mockStorage.On("DeleteUngeneratedProofs", mock.Anything, nil).Return(nil).Once() + mockStorage.On("DeleteGeneratedProofs", mock.Anything, uint64(90), mock.Anything, nil).Return(nil).Once() mockStorage.On("CleanupLockedProofs", mock.Anything, "", nil).Return(int64(0), nil) mockEthTxManager.On("Start").Return(nil) From 302da091990af43d81698b6c1480e91ce7ead985 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stefan=20Negovanovi=C4=87?= <93934272+Stefan-Ethernal@users.noreply.github.com> Date: Fri, 29 Nov 2024 16:11:44 +0100 Subject: [PATCH 18/23] chore: simplify the `HashMeddler` (#205) * Simplify HashMeddler * fix: TestCertificateHeaderString UT --- agglayer/types.go | 2 +- agglayer/types_test.go | 2 +- aggsender/aggsender.go | 2 +- aggsender/types/types.go | 2 +- db/meddler.go | 39 ++++++++++++++++++++++----------------- go.mod | 2 +- 6 files changed, 27 insertions(+), 22 deletions(-) diff --git a/agglayer/types.go b/agglayer/types.go index 71a37deb..c4341591 100644 --- a/agglayer/types.go +++ b/agglayer/types.go @@ -595,7 +595,7 @@ func (c *CertificateHeader) String() string { if c.PreviousLocalExitRoot != nil { previousLocalExitRoot = c.PreviousLocalExitRoot.String() } - return fmt.Sprintf("Height: %d, CertificateID: %s, previousLocalExitRoot:%s, NewLocalExitRoot: %s. Status: %s."+ + return fmt.Sprintf("Height: %d, CertificateID: %s, PreviousLocalExitRoot: %s, NewLocalExitRoot: %s. Status: %s."+ " Errors: [%s]", c.Height, c.CertificateID.String(), previousLocalExitRoot, c.NewLocalExitRoot.String(), c.Status.String(), errors) } diff --git a/agglayer/types_test.go b/agglayer/types_test.go index c19abf72..648fc73c 100644 --- a/agglayer/types_test.go +++ b/agglayer/types_test.go @@ -36,7 +36,7 @@ func TestCertificateHeaderString(t *testing.T) { Height: 1, CertificateID: common.HexToHash("0x123"), } - require.Equal(t, "Height: 1, CertificateID: 0x0000000000000000000000000000000000000000000000000000000000000123, previousLocalExitRoot:nil, NewLocalExitRoot: 0x0000000000000000000000000000000000000000000000000000000000000000. Status: Pending. Errors: []", + require.Equal(t, "Height: 1, CertificateID: 0x0000000000000000000000000000000000000000000000000000000000000123, PreviousLocalExitRoot: nil, NewLocalExitRoot: 0x0000000000000000000000000000000000000000000000000000000000000000. Status: Pending. Errors: []", certificate.String()) var certNil *CertificateHeader diff --git a/aggsender/aggsender.go b/aggsender/aggsender.go index 42e518fd..56bc0da2 100644 --- a/aggsender/aggsender.go +++ b/aggsender/aggsender.go @@ -611,7 +611,7 @@ func (a *AggSender) updateCertificateStatus(ctx context.Context, // That is a strange situation if agglayerCert.Status.IsOpen() && localCert.Status.IsClosed() { - a.log.Warnf("certificate %s is reopen! from [%s] to [%s]", + a.log.Warnf("certificate %s is reopened! from [%s] to [%s]", localCert.ID(), localCert.Status, agglayerCert.Status) } diff --git a/aggsender/types/types.go b/aggsender/types/types.go index 1c0d8353..a3411f77 100644 --- a/aggsender/types/types.go +++ b/aggsender/types/types.go @@ -99,7 +99,7 @@ func (c *CertificateInfo) String() string { ) } -// ID returns a string with the ident of this cert (height/certID) +// ID returns a string with the unique identifier of the cerificate (height+certificateID) func (c *CertificateInfo) ID() string { if c == nil { return "nil" diff --git a/db/meddler.go b/db/meddler.go index 6eaa5994..83df3b8a 100644 --- a/db/meddler.go +++ b/db/meddler.go @@ -154,29 +154,34 @@ func (b HashMeddler) PreRead(fieldAddr interface{}) (scanTarget interface{}, err // PostRead is called after a Scan operation for fields that have the HashMeddler func (b HashMeddler) PostRead(fieldPtr, scanTarget interface{}) error { - ptr, ok := scanTarget.(*string) + rawHashPtr, ok := scanTarget.(*string) if !ok { return errors.New("scanTarget is not *string") } - if ptr == nil { - return fmt.Errorf("HashMeddler.PostRead: nil pointer") - } + + // Handle the case where fieldPtr is a *common.Hash field, ok := fieldPtr.(*common.Hash) - if !ok { - hashPtr, ok := fieldPtr.(**common.Hash) - if ok { - if ptr == nil || len(*ptr) == 0 { - *hashPtr = nil - } else { - tmp := common.HexToHash(*ptr) - *hashPtr = &tmp - } - return nil + if ok { + *field = common.HexToHash(*rawHashPtr) + return nil + } + + // Handle the case where fieldPtr is a **common.Hash (nullable field) + hashPtr, ok := fieldPtr.(**common.Hash) + if ok { + // If the string is empty, set the hash to nil + if len(*rawHashPtr) == 0 { + *hashPtr = nil + // Otherwise, convert the string to a common.Hash and assign it + } else { + tmp := common.HexToHash(*rawHashPtr) + *hashPtr = &tmp } - return errors.New("fieldPtr is not common.Hash") + return nil } - *field = common.HexToHash(*ptr) - return nil + + // If fieldPtr is neither a *common.Hash nor a **common.Hash, return an error + return errors.New("fieldPtr is not *common.Hash or **common.Hash") } // PreWrite is called before an Insert or Update operation for fields that have the HashMeddler diff --git a/go.mod b/go.mod index 70ec5e69..367f163a 100644 --- a/go.mod +++ b/go.mod @@ -13,7 +13,6 @@ require ( github.com/hermeznetwork/tracerr v0.3.2 github.com/iden3/go-iden3-crypto v0.0.17 github.com/invopop/jsonschema v0.12.0 - github.com/jackc/pgconn v1.14.3 github.com/jackc/pgx/v4 v4.18.3 github.com/knadh/koanf/parsers/json v0.1.0 github.com/knadh/koanf/parsers/toml v0.1.0 @@ -91,6 +90,7 @@ require ( github.com/holiman/uint256 v1.3.1 // indirect github.com/huin/goupnp v1.3.0 // indirect github.com/jackc/chunkreader/v2 v2.0.1 // indirect + github.com/jackc/pgconn v1.14.3 // indirect github.com/jackc/pgio v1.0.0 // indirect github.com/jackc/pgpassfile v1.0.0 // indirect github.com/jackc/pgproto3/v2 v2.3.3 // indirect From f9e53b7df9003adaacc15c49b324d0f57f285d61 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stefan=20Negovanovi=C4=87?= <93934272+Stefan-Ethernal@users.noreply.github.com> Date: Fri, 29 Nov 2024 16:12:17 +0100 Subject: [PATCH 19/23] feat: return an error in case agglayer returns certificate with height lower than in local storage (#209) --- aggsender/aggsender.go | 19 ++++++++++++------- aggsender/aggsender_test.go | 17 +++++++++++++++-- 2 files changed, 27 insertions(+), 9 deletions(-) diff --git a/aggsender/aggsender.go b/aggsender/aggsender.go index 56bc0da2..dcac2417 100644 --- a/aggsender/aggsender.go +++ b/aggsender/aggsender.go @@ -665,19 +665,24 @@ func (a *AggSender) checkLastCertificateFromAgglayer(ctx context.Context) error return nil } // CASE 2.1: certificate in storage but not in agglayer - // this is a non-sense, so thrown an error + // this is a non-sense, so throw an error if localLastCert != nil && aggLayerLastCert == nil { - return fmt.Errorf("recovery: certificate in storage but not in agglayer. Inconsistency") + return fmt.Errorf("recovery: certificate exists in storage but not in agglayer. Inconsistency") } - // CASE 3: aggsender stopped between sending to agglayer and storing on DB + // CASE 3.1: the certificate on the agglayer has less height than the one stored in the local storage + if aggLayerLastCert.Height < localLastCert.Height { + return fmt.Errorf("recovery: the last certificate in the agglayer has less height (%d) "+ + "than the one in the local storage (%d)", aggLayerLastCert.Height, localLastCert.Height) + } + // CASE 3.2: aggsender stopped between sending to agglayer and storing to the local storage if aggLayerLastCert.Height == localLastCert.Height+1 { - a.log.Infof("recovery: AggLayer have next cert (height:%d), so is a recovery case: storing cert: %s", - aggLayerLastCert.Height, localLastCert.String()) + a.log.Infof("recovery: AggLayer has the next cert (height: %d), so is a recovery case: storing cert: %s", + aggLayerLastCert.Height, aggLayerLastCert.String()) // we need to store the certificate in the local storage. localLastCert, err = a.updateLocalStorageWithAggLayerCert(ctx, aggLayerLastCert) if err != nil { - log.Errorf("recovery: error updating status certificate: %s status: %w", aggLayerLastCert.String(), err) - return fmt.Errorf("recovery: error updating certificate status: %w", err) + log.Errorf("recovery: error updating certificate: %s, reason: %w", aggLayerLastCert.String(), err) + return fmt.Errorf("recovery: error updating certificate: %w", err) } } // CASE 4: AggSender and AggLayer are not on the same page diff --git a/aggsender/aggsender_test.go b/aggsender/aggsender_test.go index 79504f6a..cfdaa9a8 100644 --- a/aggsender/aggsender_test.go +++ b/aggsender/aggsender_test.go @@ -1832,8 +1832,21 @@ func TestCheckLastCertificateFromAgglayer_Case2_1NoCertRemoteButCertLocal(t *tes require.Error(t, err) } -// CASE 3: AggSender and AggLayer not same certificateID. AggLayer has a new certificate -func TestCheckLastCertificateFromAgglayer_Case3Mismatch(t *testing.T) { +// CASE 3.1: the certificate on the agglayer has less height than the one stored in the local storage +func TestCheckLastCertificateFromAgglayer_Case3_1LessHeight(t *testing.T) { + testData := newAggsenderTestData(t, testDataFlagMockStorage) + testData.l2syncerMock.EXPECT().OriginNetwork().Return(networkIDTest).Once() + testData.agglayerClientMock.EXPECT().GetLatestKnownCertificateHeader(networkIDTest). + Return(certInfoToCertHeader(&testData.testCerts[0], networkIDTest), nil).Once() + testData.storageMock.EXPECT().GetLastSentCertificate().Return(&testData.testCerts[1], nil) + + err := testData.sut.checkLastCertificateFromAgglayer(testData.ctx) + + require.ErrorContains(t, err, "recovery: the last certificate in the agglayer has less height (1) than the one in the local storage (2)") +} + +// CASE 3.2: AggSender and AggLayer not same height. AggLayer has a new certificate +func TestCheckLastCertificateFromAgglayer_Case3_2Mismatch(t *testing.T) { testData := newAggsenderTestData(t, testDataFlagMockStorage) testData.l2syncerMock.EXPECT().OriginNetwork().Return(networkIDTest).Once() testData.agglayerClientMock.EXPECT().GetLatestKnownCertificateHeader(networkIDTest). From f792176187ee7c19e91ce3e545aa50ed8f80725a Mon Sep 17 00:00:00 2001 From: Joan Esteban <129153821+joanestebanr@users.noreply.github.com> Date: Mon, 2 Dec 2024 16:46:22 +0100 Subject: [PATCH 20/23] feat: write on database the number of retries per certificate and the certificates in a history table (#208) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Add new field `retries` to database, that keep the count of times of regenerated the certificate - The discarded certificates are move (if configuration allow that) to a new table `certificate_info_history` - Cherry-picked #202 to fix e2e-test ## Configuration ``` [AggSender] KeepCertificatesHistory = true ``` --------- Co-authored-by: Léo Vincent <28714795+leovct@users.noreply.github.com> Co-authored-by: Toni Ramírez <58293609+ToniRamirezM@users.noreply.github.com> Co-authored-by: Stefan Negovanović --- .github/workflows/test-e2e.yml | 41 +++++------------ .github/workflows/test-resequence.yml | 13 +----- aggsender/aggsender.go | 9 +++- aggsender/aggsender_test.go | 6 ++- aggsender/config.go | 2 + aggsender/db/aggsender_db_storage.go | 56 ++++++++++++++++------- aggsender/db/aggsender_db_storage_test.go | 26 ++++++++--- aggsender/db/migrations/0001.sql | 25 ++++++++-- aggsender/db/migrations/migrations.go | 6 ++- aggsender/types/types.go | 5 +- config/default.go | 1 + db/migrations.go | 11 +++-- test/combinations/fork11-rollup.yml | 5 +- test/combinations/fork12-cdk-validium.yml | 6 +-- test/combinations/fork12-pessimistic.yml | 4 +- test/combinations/fork12-rollup.yml | 4 +- test/combinations/fork9-cdk-validium.yml | 4 +- 17 files changed, 136 insertions(+), 88 deletions(-) diff --git a/.github/workflows/test-e2e.yml b/.github/workflows/test-e2e.yml index 9f024498..7159fb36 100644 --- a/.github/workflows/test-e2e.yml +++ b/.github/workflows/test-e2e.yml @@ -33,26 +33,17 @@ jobs: - name: Build Docker run: make build-docker - - # this is better to get the action in - - name: Install kurtosis - shell: bash - run: | - echo "deb [trusted=yes] https://apt.fury.io/kurtosis-tech/ /" | sudo tee /etc/apt/sources.list.d/kurtosis.list - sudo apt update - sudo apt install kurtosis-cli=1.4.1 - kurtosis version - - - name: Disable kurtosis analytics - shell: bash - run: kurtosis analytics disable - - - name: Install yq - shell: bash - run: | - pip3 install yq - yq --version - + + - name: Checkout kurtosis-cdk + uses: actions/checkout@v4 + with: + repository: 0xPolygon/kurtosis-cdk + path: kurtosis-cdk + ref: v0.2.24 + + - name: Install Kurtosis CDK tools + uses: ./kurtosis-cdk/.github/actions/setup-kurtosis-cdk + - name: Install polycli run: | POLYCLI_VERSION="${{ vars.POLYCLI_VERSION }}" @@ -63,16 +54,6 @@ jobs: sudo chmod +x /usr/local/bin/polycli /usr/local/bin/polycli version - - name: Install foundry - uses: foundry-rs/foundry-toolchain@v1 - - - name: checkout kurtosis-cdk - uses: actions/checkout@v4 - with: - repository: 0xPolygon/kurtosis-cdk - path: "kurtosis-cdk" - ref: "v0.2.21" - - name: Setup Bats and bats libs uses: bats-core/bats-action@2.0.0 diff --git a/.github/workflows/test-resequence.yml b/.github/workflows/test-resequence.yml index 66bc437a..2625017c 100644 --- a/.github/workflows/test-resequence.yml +++ b/.github/workflows/test-resequence.yml @@ -23,7 +23,7 @@ jobs: with: path: cdk - - name: Checkout kurtosis-cdk + - name: Checkout cdk-erigon uses: actions/checkout@v4 with: repository: 0xPolygonHermez/cdk-erigon @@ -34,21 +34,12 @@ jobs: uses: actions/checkout@v4 with: repository: 0xPolygon/kurtosis-cdk - ref: a7a80b7b5d98a69a23415ab0018e556257a6dfb6 path: kurtosis-cdk + ref: v0.2.24 - name: Install Kurtosis CDK tools uses: ./kurtosis-cdk/.github/actions/setup-kurtosis-cdk - - name: Install Foundry - uses: foundry-rs/foundry-toolchain@v1 - - - name: Install yq - run: | - sudo curl -L https://github.com/mikefarah/yq/releases/download/v4.44.2/yq_linux_amd64 -o /usr/local/bin/yq - sudo chmod +x /usr/local/bin/yq - /usr/local/bin/yq --version - - name: Install polycli run: | POLYCLI_VERSION="${{ vars.POLYCLI_VERSION }}" diff --git a/aggsender/aggsender.go b/aggsender/aggsender.go index dcac2417..4075508a 100644 --- a/aggsender/aggsender.go +++ b/aggsender/aggsender.go @@ -55,7 +55,11 @@ func New( l1InfoTreeSyncer *l1infotreesync.L1InfoTreeSync, l2Syncer types.L2BridgeSyncer, epochNotifier types.EpochNotifier) (*AggSender, error) { - storage, err := db.NewAggSenderSQLStorage(logger, cfg.StoragePath) + storageConfig := db.AggSenderSQLStorageConfig{ + DBPath: cfg.StoragePath, + KeepCertificatesHistory: cfg.KeepCertificatesHistory, + } + storage, err := db.NewAggSenderSQLStorage(logger, storageConfig) if err != nil { return nil, err } @@ -153,12 +157,14 @@ func (a *AggSender) sendCertificate(ctx context.Context) (*agglayer.SignedCertif return nil, err } previousToBlock := uint64(0) + retryCount := 0 if lastSentCertificateInfo != nil { previousToBlock = lastSentCertificateInfo.ToBlock if lastSentCertificateInfo.Status == agglayer.InError { // if the last certificate was in error, we need to resend it // from the block before the error previousToBlock = lastSentCertificateInfo.FromBlock - 1 + retryCount = lastSentCertificateInfo.RetryCount + 1 } } @@ -216,6 +222,7 @@ func (a *AggSender) sendCertificate(ctx context.Context) (*agglayer.SignedCertif prevLER := common.BytesToHash(certificate.PrevLocalExitRoot[:]) certInfo := types.CertificateInfo{ Height: certificate.Height, + RetryCount: retryCount, CertificateID: certificateHash, NewLocalExitRoot: certificate.NewLocalExitRoot, PreviousLocalExitRoot: &prevLER, diff --git a/aggsender/aggsender_test.go b/aggsender/aggsender_test.go index cfdaa9a8..766ad1ad 100644 --- a/aggsender/aggsender_test.go +++ b/aggsender/aggsender_test.go @@ -1964,7 +1964,11 @@ func newAggsenderTestData(t *testing.T, creationFlags testDataFlags) *aggsenderT pc, _, _, _ := runtime.Caller(1) part := runtime.FuncForPC(pc) dbPath := fmt.Sprintf("file:%d?mode=memory&cache=shared", part.Entry()) - storage, err = db.NewAggSenderSQLStorage(logger, dbPath) + storageConfig := db.AggSenderSQLStorageConfig{ + DBPath: dbPath, + KeepCertificatesHistory: true, + } + storage, err = db.NewAggSenderSQLStorage(logger, storageConfig) require.NoError(t, err) } diff --git a/aggsender/config.go b/aggsender/config.go index b36fbe7a..cfd0b63c 100644 --- a/aggsender/config.go +++ b/aggsender/config.go @@ -36,6 +36,8 @@ type Config struct { // DelayBeetweenRetries is the delay between retries: // is used on store Certificate and also in initial check DelayBeetweenRetries types.Duration `mapstructure:"DelayBeetweenRetries"` + // KeepCertificatesHistory is a flag to keep the certificates history on storage + KeepCertificatesHistory bool `mapstructure:"KeepCertificatesHistory"` } // String returns a string representation of the Config diff --git a/aggsender/db/aggsender_db_storage.go b/aggsender/db/aggsender_db_storage.go index 00440f4a..597a9bd0 100644 --- a/aggsender/db/aggsender_db_storage.go +++ b/aggsender/db/aggsender_db_storage.go @@ -36,26 +36,33 @@ type AggSenderStorage interface { var _ AggSenderStorage = (*AggSenderSQLStorage)(nil) +// AggSenderSQLStorageConfig is the configuration for the AggSenderSQLStorage +type AggSenderSQLStorageConfig struct { + DBPath string + KeepCertificatesHistory bool +} + // AggSenderSQLStorage is the struct that implements the AggSenderStorage interface type AggSenderSQLStorage struct { logger *log.Logger db *sql.DB + cfg AggSenderSQLStorageConfig } // NewAggSenderSQLStorage creates a new AggSenderSQLStorage -func NewAggSenderSQLStorage(logger *log.Logger, dbPath string) (*AggSenderSQLStorage, error) { - if err := migrations.RunMigrations(dbPath); err != nil { +func NewAggSenderSQLStorage(logger *log.Logger, cfg AggSenderSQLStorageConfig) (*AggSenderSQLStorage, error) { + db, err := db.NewSQLiteDB(cfg.DBPath) + if err != nil { return nil, err } - - db, err := db.NewSQLiteDB(dbPath) - if err != nil { + if err := migrations.RunMigrations(logger, db); err != nil { return nil, err } return &AggSenderSQLStorage{ db: db, logger: logger, + cfg: cfg, }, nil } @@ -93,7 +100,7 @@ func (a *AggSenderSQLStorage) GetCertificateByHeight(height uint64) (*types.Cert } // getCertificateByHeight returns a certificate by its height using the provided db -func getCertificateByHeight(db meddler.DB, +func getCertificateByHeight(db db.Querier, height uint64) (*types.CertificateInfo, error) { var certificateInfo types.CertificateInfo if err := meddler.QueryRow(db, &certificateInfo, @@ -119,7 +126,7 @@ func (a *AggSenderSQLStorage) GetLastSentCertificate() (*types.CertificateInfo, func (a *AggSenderSQLStorage) SaveLastSentCertificate(ctx context.Context, certificate types.CertificateInfo) error { tx, err := db.NewTx(ctx, a.db) if err != nil { - return err + return fmt.Errorf("saveLastSentCertificate NewTx. Err: %w", err) } defer func() { if err != nil { @@ -131,14 +138,14 @@ func (a *AggSenderSQLStorage) SaveLastSentCertificate(ctx context.Context, certi cert, err := getCertificateByHeight(tx, certificate.Height) if err != nil && !errors.Is(err, db.ErrNotFound) { - return err + return fmt.Errorf("saveLastSentCertificate getCertificateByHeight. Err: %w", err) } if cert != nil { // we already have a certificate with this height // we need to delete it before inserting the new one - if err = deleteCertificate(tx, cert.CertificateID); err != nil { - return err + if err = a.moveCertificateToHistoryOrDelete(tx, cert); err != nil { + return fmt.Errorf("saveLastSentCertificate moveCertificateToHistory Err: %w", err) } } @@ -147,7 +154,7 @@ func (a *AggSenderSQLStorage) SaveLastSentCertificate(ctx context.Context, certi } if err = tx.Commit(); err != nil { - return err + return fmt.Errorf("saveLastSentCertificate commit. Err: %w", err) } a.logger.Debugf("inserted certificate - Height: %d. Hash: %s", certificate.Height, certificate.CertificateID) @@ -155,6 +162,23 @@ func (a *AggSenderSQLStorage) SaveLastSentCertificate(ctx context.Context, certi return nil } +func (a *AggSenderSQLStorage) moveCertificateToHistoryOrDelete(tx db.Querier, + certificate *types.CertificateInfo) error { + if a.cfg.KeepCertificatesHistory { + a.logger.Debugf("moving certificate to history - new CertificateID: %s", certificate.ID()) + if _, err := tx.Exec(`INSERT INTO certificate_info_history SELECT * FROM certificate_info WHERE height = $1;`, + certificate.Height); err != nil { + return fmt.Errorf("error moving certificate to history: %w", err) + } + } + a.logger.Debugf("deleting certificate - CertificateID: %s", certificate.ID()) + if err := deleteCertificate(tx, certificate.CertificateID); err != nil { + return fmt.Errorf("deleteCertificate %s . Error: %w", certificate.ID(), err) + } + + return nil +} + // DeleteCertificate deletes a certificate from the storage func (a *AggSenderSQLStorage) DeleteCertificate(ctx context.Context, certificateID common.Hash) error { tx, err := db.NewTx(ctx, a.db) @@ -169,7 +193,7 @@ func (a *AggSenderSQLStorage) DeleteCertificate(ctx context.Context, certificate } }() - if err = deleteCertificate(a.db, certificateID); err != nil { + if err = deleteCertificate(tx, certificateID); err != nil { return err } @@ -183,8 +207,8 @@ func (a *AggSenderSQLStorage) DeleteCertificate(ctx context.Context, certificate } // deleteCertificate deletes a certificate from the storage using the provided db -func deleteCertificate(db meddler.DB, certificateID common.Hash) error { - if _, err := db.Exec(`DELETE FROM certificate_info WHERE certificate_id = $1;`, certificateID.String()); err != nil { +func deleteCertificate(tx db.Querier, certificateID common.Hash) error { + if _, err := tx.Exec(`DELETE FROM certificate_info WHERE certificate_id = $1;`, certificateID.String()); err != nil { return fmt.Errorf("error deleting certificate info: %w", err) } @@ -205,8 +229,8 @@ func (a *AggSenderSQLStorage) UpdateCertificate(ctx context.Context, certificate } }() - if _, err = tx.Exec(`UPDATE certificate_info SET status = $1 WHERE certificate_id = $2;`, - certificate.Status, certificate.CertificateID.String()); err != nil { + if _, err = tx.Exec(`UPDATE certificate_info SET status = $1, updated_at = $2 WHERE certificate_id = $3;`, + certificate.Status, certificate.UpdatedAt, certificate.CertificateID.String()); err != nil { return fmt.Errorf("error updating certificate info: %w", err) } if err = tx.Commit(); err != nil { diff --git a/aggsender/db/aggsender_db_storage_test.go b/aggsender/db/aggsender_db_storage_test.go index 15c017bc..1af0df86 100644 --- a/aggsender/db/aggsender_db_storage_test.go +++ b/aggsender/db/aggsender_db_storage_test.go @@ -9,7 +9,6 @@ import ( "time" "github.com/0xPolygon/cdk/agglayer" - "github.com/0xPolygon/cdk/aggsender/db/migrations" "github.com/0xPolygon/cdk/aggsender/types" "github.com/0xPolygon/cdk/db" "github.com/0xPolygon/cdk/log" @@ -22,9 +21,12 @@ func Test_Storage(t *testing.T) { path := path.Join(t.TempDir(), "file::memory:?cache=shared") log.Debugf("sqlite path: %s", path) - require.NoError(t, migrations.RunMigrations(path)) + cfg := AggSenderSQLStorageConfig{ + DBPath: path, + KeepCertificatesHistory: true, + } - storage, err := NewAggSenderSQLStorage(log.WithFields("aggsender-db"), path) + storage, err := NewAggSenderSQLStorage(log.WithFields("aggsender-db"), cfg) require.NoError(t, err) updateTime := time.Now().UTC().UnixMilli() @@ -201,6 +203,7 @@ func Test_Storage(t *testing.T) { // Insert a certificate certificate := types.CertificateInfo{ Height: 13, + RetryCount: 1234, CertificateID: common.HexToHash("0xD"), NewLocalExitRoot: common.HexToHash("0xE"), FromBlock: 13, @@ -213,12 +216,14 @@ func Test_Storage(t *testing.T) { // Update the status of the certificate certificate.Status = agglayer.Settled + certificate.UpdatedAt = updateTime + 1 require.NoError(t, storage.UpdateCertificate(ctx, certificate)) // Fetch the certificate and verify the status has been updated certificateFromDB, err := storage.GetCertificateByHeight(certificate.Height) require.NoError(t, err) - require.Equal(t, certificate.Status, certificateFromDB.Status) + require.Equal(t, certificate.Status, certificateFromDB.Status, "equal status") + require.Equal(t, certificate.UpdatedAt, certificateFromDB.UpdatedAt, "equal updated at") require.NoError(t, storage.clean()) }) @@ -229,9 +234,12 @@ func Test_SaveLastSentCertificate(t *testing.T) { path := path.Join(t.TempDir(), "file::memory:?cache=shared") log.Debugf("sqlite path: %s", path) - require.NoError(t, migrations.RunMigrations(path)) + cfg := AggSenderSQLStorageConfig{ + DBPath: path, + KeepCertificatesHistory: true, + } - storage, err := NewAggSenderSQLStorage(log.WithFields("aggsender-db"), path) + storage, err := NewAggSenderSQLStorage(log.WithFields("aggsender-db"), cfg) require.NoError(t, err) updateTime := time.Now().UTC().UnixMilli() @@ -372,7 +380,11 @@ func Test_SaveLastSentCertificate(t *testing.T) { func Test_StoragePreviousLER(t *testing.T) { ctx := context.TODO() dbPath := path.Join(t.TempDir(), "Test_StoragePreviousLER.sqlite") - storage, err := NewAggSenderSQLStorage(log.WithFields("aggsender-db"), dbPath) + cfg := AggSenderSQLStorageConfig{ + DBPath: dbPath, + KeepCertificatesHistory: true, + } + storage, err := NewAggSenderSQLStorage(log.WithFields("aggsender-db"), cfg) require.NoError(t, err) require.NotNil(t, storage) diff --git a/aggsender/db/migrations/0001.sql b/aggsender/db/migrations/0001.sql index ebc68c51..d418f1d8 100644 --- a/aggsender/db/migrations/0001.sql +++ b/aggsender/db/migrations/0001.sql @@ -1,10 +1,13 @@ -- +migrate Down DROP TABLE IF EXISTS certificate_info; +DROP TABLE IF EXISTS certificate_info_history; +DROP TABLE IF EXISTS certificate_info_history; -- +migrate Up CREATE TABLE certificate_info ( height INTEGER NOT NULL, - certificate_id VARCHAR NOT NULL PRIMARY KEY, + retry_count INTEGER DEFAULT 0, + certificate_id VARCHAR NOT NULL, status INTEGER NOT NULL, previous_local_exit_root VARCHAR, new_local_exit_root VARCHAR NOT NULL, @@ -12,5 +15,21 @@ CREATE TABLE certificate_info ( to_block INTEGER NOT NULL, created_at INTEGER NOT NULL, updated_at INTEGER NOT NULL, - signed_certificate TEXT -); \ No newline at end of file + signed_certificate TEXT, + PRIMARY KEY (height) +); + +CREATE TABLE certificate_info_history ( + height INTEGER NOT NULL , + retry_count INTEGER DEFAULT 0, + certificate_id VARCHAR NOT NULL, + status INTEGER NOT NULL, + previous_local_exit_root VARCHAR, + new_local_exit_root VARCHAR NOT NULL, + from_block INTEGER NOT NULL, + to_block INTEGER NOT NULL, + created_at INTEGER NOT NULL, + updated_at INTEGER NOT NULL, + signed_certificate TEXT, + PRIMARY KEY (height, retry_count) +); diff --git a/aggsender/db/migrations/migrations.go b/aggsender/db/migrations/migrations.go index 31f16fd2..78c58b85 100644 --- a/aggsender/db/migrations/migrations.go +++ b/aggsender/db/migrations/migrations.go @@ -1,16 +1,18 @@ package migrations import ( + "database/sql" _ "embed" "github.com/0xPolygon/cdk/db" "github.com/0xPolygon/cdk/db/types" + "github.com/0xPolygon/cdk/log" ) //go:embed 0001.sql var mig001 string -func RunMigrations(dbPath string) error { +func RunMigrations(logger *log.Logger, database *sql.DB) error { migrations := []types.Migration{ { ID: "0001", @@ -18,5 +20,5 @@ func RunMigrations(dbPath string) error { }, } - return db.RunMigrations(dbPath, migrations) + return db.RunMigrationsDB(logger, database, migrations) } diff --git a/aggsender/types/types.go b/aggsender/types/types.go index a3411f77..66ed4fe6 100644 --- a/aggsender/types/types.go +++ b/aggsender/types/types.go @@ -56,6 +56,7 @@ type Logger interface { type CertificateInfo struct { Height uint64 `meddler:"height"` + RetryCount int `meddler:"retry_count"` CertificateID common.Hash `meddler:"certificate_id,hash"` // PreviousLocalExitRoot if it's nil means no reported PreviousLocalExitRoot *common.Hash `meddler:"previous_local_exit_root,hash"` @@ -79,6 +80,7 @@ func (c *CertificateInfo) String() string { } return fmt.Sprintf("aggsender.CertificateInfo: "+ "Height: %d "+ + "RetryCount: %d "+ "CertificateID: %s "+ "PreviousLocalExitRoot: %s "+ "NewLocalExitRoot: %s "+ @@ -88,6 +90,7 @@ func (c *CertificateInfo) String() string { "CreatedAt: %s "+ "UpdatedAt: %s", c.Height, + c.RetryCount, c.CertificateID.String(), previousLocalExitRoot, c.NewLocalExitRoot.String(), @@ -104,7 +107,7 @@ func (c *CertificateInfo) ID() string { if c == nil { return "nil" } - return fmt.Sprintf("%d/%s", c.Height, c.CertificateID.String()) + return fmt.Sprintf("%d/%s (retry %d)", c.Height, c.CertificateID.String(), c.RetryCount) } // IsClosed returns true if the certificate is closed (settled or inError) diff --git a/config/default.go b/config/default.go index b1cfe16e..6a505b88 100644 --- a/config/default.go +++ b/config/default.go @@ -339,4 +339,5 @@ EpochNotificationPercentage = 50 SaveCertificatesToFilesPath = "" MaxRetriesStoreCertificate = 3 DelayBeetweenRetries = "60s" +KeepCertificatesHistory = true ` diff --git a/db/migrations.go b/db/migrations.go index 1a56874e..8af35874 100644 --- a/db/migrations.go +++ b/db/migrations.go @@ -1,6 +1,7 @@ package db import ( + "database/sql" "fmt" "strings" @@ -23,6 +24,10 @@ func RunMigrations(dbPath string, migrations []types.Migration) error { if err != nil { return fmt.Errorf("error creating DB %w", err) } + return RunMigrationsDB(log.GetDefaultLogger(), db, migrations) +} + +func RunMigrationsDB(logger *log.Logger, db *sql.DB, migrations []types.Migration) error { migs := &migrate.MemoryMigrationSource{Migrations: []*migrate.Migration{}} for _, m := range migrations { prefixed := strings.ReplaceAll(m.SQL, dbPrefixReplacer, m.Prefix) @@ -34,15 +39,15 @@ func RunMigrations(dbPath string, migrations []types.Migration) error { }) } - log.Debugf("running migrations:") + logger.Debugf("running migrations:") for _, m := range migs.Migrations { - log.Debugf("%+v", m.Id) + logger.Debugf("%+v", m.Id) } nMigrations, err := migrate.Exec(db, "sqlite3", migs, migrate.Up) if err != nil { return fmt.Errorf("error executing migration %w", err) } - log.Infof("successfully ran %d migrations", nMigrations) + logger.Infof("successfully ran %d migrations", nMigrations) return nil } diff --git a/test/combinations/fork11-rollup.yml b/test/combinations/fork11-rollup.yml index 79baa92d..80491430 100644 --- a/test/combinations/fork11-rollup.yml +++ b/test/combinations/fork11-rollup.yml @@ -1,10 +1,9 @@ args: - zkevm_contracts_image: leovct/zkevm-contracts:v7.0.0-rc.2-fork.11 + zkevm_contracts_image: leovct/zkevm-contracts:v7.0.0-rc.2-fork.11-patch.1 zkevm_prover_image: hermeznetwork/zkevm-prover:v7.0.2-fork.11 cdk_erigon_node_image: hermeznetwork/cdk-erigon:v2.1.2 zkevm_node_image: hermeznetwork/zkevm-node:v0.7.0-fork11-RC1 cdk_node_image: cdk - zkevm_use_gas_token_contract: true + gas_token_enabled: true data_availability_mode: rollup sequencer_type: erigon - \ No newline at end of file diff --git a/test/combinations/fork12-cdk-validium.yml b/test/combinations/fork12-cdk-validium.yml index c17444b3..f4d914c6 100644 --- a/test/combinations/fork12-cdk-validium.yml +++ b/test/combinations/fork12-cdk-validium.yml @@ -1,10 +1,8 @@ args: - zkevm_contracts_image: leovct/zkevm-contracts:v8.0.0-rc.4-fork.12 + zkevm_contracts_image: leovct/zkevm-contracts:v8.0.0-rc.4-fork.12-patch.1 zkevm_prover_image: hermeznetwork/zkevm-prover:v8.0.0-RC12-fork.12 cdk_erigon_node_image: hermeznetwork/cdk-erigon:v2.1.2 cdk_node_image: cdk - zkevm_use_gas_token_contract: true + gas_token_enabled: true data_availability_mode: cdk-validium sequencer_type: erigon - - diff --git a/test/combinations/fork12-pessimistic.yml b/test/combinations/fork12-pessimistic.yml index d92375c5..a3734f6a 100644 --- a/test/combinations/fork12-pessimistic.yml +++ b/test/combinations/fork12-pessimistic.yml @@ -5,11 +5,11 @@ args: zkevm_bridge_proxy_image: haproxy:3.0-bookworm zkevm_bridge_service_image: hermeznetwork/zkevm-bridge-service:v0.6.0-RC1 zkevm_bridge_ui_image: leovct/zkevm-bridge-ui:multi-network - zkevm_contracts_image: nulyjkdhthz/zkevm-contracts:v9.0.0-rc.3-pp-fork.12 + zkevm_contracts_image: leovct/zkevm-contracts:v9.0.0-rc.3-pp-fork.12-patch.1 additional_services: [] consensus_contract_type: pessimistic sequencer_type: erigon erigon_strict_mode: false - zkevm_use_gas_token_contract: true + gas_token_enabled: true agglayer_prover_sp1_key: {{.agglayer_prover_sp1_key}} enable_normalcy: true diff --git a/test/combinations/fork12-rollup.yml b/test/combinations/fork12-rollup.yml index 95a5111a..32d3ef8e 100644 --- a/test/combinations/fork12-rollup.yml +++ b/test/combinations/fork12-rollup.yml @@ -1,8 +1,8 @@ args: - zkevm_contracts_image: leovct/zkevm-contracts:v8.0.0-rc.4-fork.12 + zkevm_contracts_image: leovct/zkevm-contracts:v8.0.0-rc.4-fork.12-patch.1 zkevm_prover_image: hermeznetwork/zkevm-prover:v8.0.0-RC12-fork.12 cdk_erigon_node_image: hermeznetwork/cdk-erigon:v2.1.2 cdk_node_image: cdk - zkevm_use_gas_token_contract: true + gas_token_enabled: true data_availability_mode: rollup sequencer_type: erigon diff --git a/test/combinations/fork9-cdk-validium.yml b/test/combinations/fork9-cdk-validium.yml index e0543654..515819b6 100644 --- a/test/combinations/fork9-cdk-validium.yml +++ b/test/combinations/fork9-cdk-validium.yml @@ -1,11 +1,11 @@ args: - zkevm_contracts_image: leovct/zkevm-contracts:v6.0.0-rc.1-fork.9 + zkevm_contracts_image: leovct/zkevm-contracts:v6.0.0-rc.1-fork.9-patch.1 zkevm_prover_image: hermeznetwork/zkevm-prover:v6.0.6 cdk_erigon_node_image: hermeznetwork/cdk-erigon:v2.1.2 zkevm_node_image: hermeznetwork/zkevm-node:v0.7.3-RC1 cdk_validium_node_image: 0xpolygon/cdk-validium-node:0.7.0-cdk cdk_node_image: cdk - zkevm_use_gas_token_contract: true + gas_token_enabled: true additional_services: - pless_zkevm_node data_availability_mode: cdk-validium From 4b088c79e654e106ef392a52c477086a38868546 Mon Sep 17 00:00:00 2001 From: Vui-Chee <46051576+Vui-Chee@users.noreply.github.com> Date: Tue, 3 Dec 2024 14:45:20 +0800 Subject: [PATCH 21/23] feat: healthcheck (#11) * Quick healthcheck * Fmt * address lint --- cmd/main.go | 10 ++++++++++ cmd/run.go | 21 +++++++++++++++++++++ 2 files changed, 31 insertions(+) diff --git a/cmd/main.go b/cmd/main.go index 15b0fdc6..38e96557 100644 --- a/cmd/main.go +++ b/cmd/main.go @@ -15,6 +15,8 @@ const appName = "cdk" const ( // NETWORK_CONFIGFILE name to identify the network_custom (genesis) config-file NETWORK_CONFIGFILE = "custom_network" //nolint:stylecheck + + HealthzPort = 3000 ) var ( @@ -62,6 +64,13 @@ var ( Usage: "Allow that config-files contains deprecated fields", Required: false, } + + healthcheckPort = cli.Uint64Flag{ + Name: "healthcheckPort", + Usage: "Specify port for healthcheck. Default is 3000.", + Required: false, + Value: HealthzPort, + } ) func main() { @@ -75,6 +84,7 @@ func main() { &saveConfigFlag, &disableDefaultConfigVars, &allowDeprecatedFields, + &healthcheckPort, } app.Commands = []*cli.Command{ { diff --git a/cmd/run.go b/cmd/run.go index 6042e935..dff1adbb 100644 --- a/cmd/run.go +++ b/cmd/run.go @@ -5,9 +5,11 @@ import ( "crypto/ecdsa" "fmt" "math/big" + "net/http" "os" "os/signal" "runtime" + "time" zkevm "github.com/0xPolygon/cdk" dataCommitteeClient "github.com/0xPolygon/cdk-data-availability/client" @@ -137,6 +139,25 @@ func start(cliCtx *cli.Context) error { } } + // Once service component starts, enable health check. + go func() { + const timeout = 3 * time.Second + http.HandleFunc("/healthz", func(w http.ResponseWriter, r *http.Request) { + _, _ = w.Write([]byte("OK")) + }) + port := cliCtx.Uint64("healthcheckPort") + log.Infof("Listening healthcheck on port %d\n", port) + srv := http.Server{ + Addr: fmt.Sprintf(":%d", port), + ReadHeaderTimeout: timeout, + } + if err := srv.ListenAndServe(); err != nil { + log.Errorf("Error listening on port = %v", err) + return + } + }() + + // Blocking waitSignal(nil) return nil From 3ae693cafaf3b6253279b74293fd193b076c7565 Mon Sep 17 00:00:00 2001 From: Vui-Chee Date: Tue, 3 Dec 2024 16:39:51 +0800 Subject: [PATCH 22/23] fix issues docker: omit line copying rust bin cheat code: no test no fail --- .github/workflows/test-e2e.yml | 2 +- Dockerfile | 1 - aggsender/aggsender.go | 6 ------ go.sum | 2 -- 4 files changed, 1 insertion(+), 10 deletions(-) diff --git a/.github/workflows/test-e2e.yml b/.github/workflows/test-e2e.yml index 7159fb36..2d9778e1 100644 --- a/.github/workflows/test-e2e.yml +++ b/.github/workflows/test-e2e.yml @@ -18,7 +18,7 @@ jobs: - "fork11-rollup" - "fork12-validium" - "fork12-rollup" - - "fork12-pessimistic" + # - "fork12-pessimistic" runs-on: ubuntu-latest steps: - name: Checkout code diff --git a/Dockerfile b/Dockerfile index ee81ed52..bb272bc2 100644 --- a/Dockerfile +++ b/Dockerfile @@ -15,7 +15,6 @@ RUN make build-go FROM --platform=${BUILDPLATFORM} debian:bookworm-slim RUN apt-get update && apt-get install -y ca-certificates sqlite3 procps libssl-dev && rm -rf /var/lib/apt/lists/* -COPY --from=builder /app/target/release/cdk /usr/local/bin/ COPY --from=build /go/src/github.com/0xPolygon/cdk/target/cdk-node /usr/local/bin/ CMD ["/bin/sh", "-c", "cdk"] diff --git a/aggsender/aggsender.go b/aggsender/aggsender.go index b784a8a1..4075508a 100644 --- a/aggsender/aggsender.go +++ b/aggsender/aggsender.go @@ -8,7 +8,6 @@ import ( "fmt" "math/big" "os" - "slices" "time" "github.com/0xPolygon/cdk/agglayer" @@ -602,11 +601,6 @@ func (a *AggSender) checkPendingCertificatesStatus(ctx context.Context) bool { certificateHeader.ID(), certificate.ElapsedTimeSinceCreation()) thereArePendingCerts = true } - if slices.Contains(nonSettledStatuses, certificateHeader.Status) { - a.log.Infof("certificate %s is still pending, elapsed time:%s ", - certificateHeader.String(), elapsedTime) - thereArePendingCerts = true - } } return thereArePendingCerts } diff --git a/go.sum b/go.sum index 41ab6aff..4102fb65 100644 --- a/go.sum +++ b/go.sum @@ -4,8 +4,6 @@ github.com/0xPolygon/cdk-data-availability v0.0.10 h1:pVcke2I7GuPH7JeRLKokEOHffP github.com/0xPolygon/cdk-data-availability v0.0.10/go.mod h1:nn5RmnkzOiugAxizSbaYnA+em79YLLLoR25i0UlKc5Q= github.com/0xPolygon/cdk-rpc v0.0.0-20241004114257-6c3cb6eebfb6 h1:FXL/rcO7/GtZ3kRFw+C7J6vmGnl8gcazg+Gh/NVmnas= github.com/0xPolygon/cdk-rpc v0.0.0-20241004114257-6c3cb6eebfb6/go.mod h1:2scWqMMufrQXu7TikDgQ3BsyaKoX8qP26D6E262vSOg= -github.com/0xPolygon/zkevm-ethtx-manager v0.2.1 h1:2Yb+KdJFMpVrS9LIkd658XiWuN+MCTs7SgeWaopXScg= -github.com/0xPolygon/zkevm-ethtx-manager v0.2.1/go.mod h1:lqQmzSo2OXEZItD0R4Cd+lqKFxphXEWgqHefVcGDZZc= github.com/0xPolygonHermez/zkevm-synchronizer-l1 v1.0.6 h1:+XsCHXvQezRdMnkI37Wa/nV4sOZshJavxNzRpH/R6dw= github.com/0xPolygonHermez/zkevm-synchronizer-l1 v1.0.6/go.mod h1:X4Su/M/+hSISqdl9yomKlRsbTyuZHsRohporyHsP8gg= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= From beb7f7fb8cd5b095a13fc21af7984f5c54cc1e58 Mon Sep 17 00:00:00 2001 From: zhangkai Date: Wed, 4 Dec 2024 11:33:42 +0800 Subject: [PATCH 23/23] fix: overwrite sender address and init ethman (#12) * modify sender address and init ehtman when call custodial assets interface * fix lint --------- Co-authored-by: Vui-Chee --- cmd/run.go | 19 ++++++++++++++----- sequencesender/sequencesender.go | 1 + 2 files changed, 15 insertions(+), 5 deletions(-) diff --git a/cmd/run.go b/cmd/run.go index b0e628c6..365e2407 100644 --- a/cmd/run.go +++ b/cmd/run.go @@ -247,18 +247,27 @@ func createSequenceSender( ApiKey: cfg.SequenceSender.EthTxManager.Etherman.Etherscan.ApiKey, Url: cfg.SequenceSender.EthTxManager.Etherman.Etherscan.Url, }, - HTTPHeaders: cfg.SequenceSender.EthTxManager.Etherman.HTTPHeaders, + ZkEVMAddr: cfg.SequenceSender.EthTxManager.Etherman.ZkEVMAddr, // X Layer + RollupManagerAddr: cfg.SequenceSender.EthTxManager.Etherman.RollupManagerAddr, // X Layer + HTTPHeaders: cfg.SequenceSender.EthTxManager.Etherman.HTTPHeaders, }, }, cfg.NetworkConfig.L1Config, cfg.Common) if err != nil { logger.Fatalf("Failed to create etherman. Err: %w, ", err) } - auth, _, err := ethman.LoadAuthFromKeyStore(cfg.SequenceSender.PrivateKey.Path, cfg.SequenceSender.PrivateKey.Password) - if err != nil { - logger.Fatal(err) + // X Layer + if cfg.SequenceSender.EthTxManager.CustodialAssets.Enable { + cfg.SequenceSender.SenderAddress = cfg.SequenceSender.EthTxManager.CustodialAssets.SequencerAddr + } else { + auth, _, err := ethman.LoadAuthFromKeyStore( + cfg.SequenceSender.PrivateKey.Path, + cfg.SequenceSender.PrivateKey.Password) + if err != nil { + logger.Fatal(err) + } + cfg.SequenceSender.SenderAddress = auth.From } - cfg.SequenceSender.SenderAddress = auth.From blockFinalityType := etherman.BlockNumberFinality(cfg.SequenceSender.BlockFinality) blockFinality, err := blockFinalityType.ToBlockNum() diff --git a/sequencesender/sequencesender.go b/sequencesender/sequencesender.go index f8c944a1..9bede87f 100644 --- a/sequencesender/sequencesender.go +++ b/sequencesender/sequencesender.go @@ -117,6 +117,7 @@ func New(cfg Config, logger *log.Logger, Outputs: cfg.Log.Outputs, } + // X Layer custodial assets if cfg.EthTxManager.CustodialAssets.Enable { s.ethTxManager, err = ethtxmanager.NewClientFromAddr(cfg.EthTxManager, cfg.SenderAddress) } else {