diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index f6205e61..a988441f 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -39,6 +39,7 @@ jobs: uses: github/codeql-action/autobuild@v2 - name: Perform CodeQL Analysis + if: false # Disable CodeQL analysis for X Layer uses: github/codeql-action/analyze@v2 with: category: "/language:${{ matrix.language }}" diff --git a/.github/workflows/test-e2e.yml b/.github/workflows/test-e2e.yml index 7fdb5a2b..8994d8e6 100644 --- a/.github/workflows/test-e2e.yml +++ b/.github/workflows/test-e2e.yml @@ -39,7 +39,7 @@ jobs: 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.3.0 + sudo apt install kurtosis-cli=1.4.1 kurtosis version - name: Disable kurtosis analytics @@ -70,7 +70,7 @@ jobs: with: repository: 0xPolygon/kurtosis-cdk path: "kurtosis-cdk" - ref: "v0.2.15" + ref: "v0.2.19" - 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 71ebc7d7..66bc437a 100644 --- a/.github/workflows/test-resequence.yml +++ b/.github/workflows/test-resequence.yml @@ -86,22 +86,20 @@ jobs: working-directory: ./cdk-erigon run: .github/scripts/test_resequence.sh - - name: Prepare logs - if: always() - working-directory: ./kurtosis-cdk + - name: Dump enclave logs + if: failure() + run: kurtosis dump ./dump + + - name: Generate archive name + if: failure() run: | - mkdir -p ci_logs - cd ci_logs - kurtosis service logs cdk-v1 cdk-erigon-node-001 --all > cdk-erigon-node-001.log - kurtosis service logs cdk-v1 cdk-erigon-sequencer-001 --all > cdk-erigon-sequencer-001.log - kurtosis service logs cdk-v1 zkevm-agglayer-001 --all > zkevm-agglayer-001.log - kurtosis service logs cdk-v1 zkevm-prover-001 --all > zkevm-prover-001.log - kurtosis service logs cdk-v1 cdk-node-001 --all > cdk-node-001.log - kurtosis service logs cdk-v1 zkevm-bridge-service-001 --all > zkevm-bridge-service-001.log + archive_name="dump_run_with_args_${{matrix.e2e-group}}_${{ github.run_id }}" + echo "ARCHIVE_NAME=${archive_name}" >> "$GITHUB_ENV" + echo "Generated archive name: ${archive_name}" - name: Upload logs - if: always() + if: failure() uses: actions/upload-artifact@v4 with: - name: logs_${{ github.run_id }} - path: ./kurtosis-cdk/ci_logs + name: ${{ env.ARCHIVE_NAME }} + path: ./dump diff --git a/.github/workflows/test-unit.yml b/.github/workflows/test-unit.yml index 66cfc010..625f7636 100644 --- a/.github/workflows/test-unit.yml +++ b/.github/workflows/test-unit.yml @@ -17,6 +17,7 @@ jobs: go-version: [1.22.4] goarch: ["amd64"] runs-on: ubuntu-latest + if: false # Disable SonarCloud analysis for X Layer steps: - name: Checkout code uses: actions/checkout@v4 diff --git a/.golangci.yml b/.golangci.yml index 98197d74..00f17235 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -49,6 +49,8 @@ linters-settings: gocritic: enabled-checks: - ruleguard + disabled-checks: + - ifElseChain revive: rules: - name: exported diff --git a/Cargo.lock b/Cargo.lock index b9956840..07f6da60 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -61,9 +61,9 @@ dependencies = [ [[package]] name = "alloy-json-rpc" -version = "0.4.2" +version = "0.5.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8fa8a1a3c4cbd221f2b8e3693aeb328fca79a757fe556ed08e47bbbc2a70db7" +checksum = "af5979e0d5a7bf9c7eb79749121e8256e59021af611322aee56e77e20776b4b3" dependencies = [ "alloy-primitives", "alloy-sol-types", @@ -112,9 +112,9 @@ dependencies = [ [[package]] name = "alloy-rpc-client" -version = "0.4.2" +version = "0.5.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "370143ed581aace6e663342d21d209c6b2e34ee6142f7d6675adb518deeaf0dc" +checksum = "7fc2bd1e7403463a5f2c61e955bcc9d3072b63aa177442b0f9aa6a6d22a941e3" dependencies = [ "alloy-json-rpc", "alloy-primitives", @@ -130,6 +130,7 @@ dependencies = [ "tower 0.5.1", "tracing", "url", + "wasmtimer", ] [[package]] @@ -192,9 +193,9 @@ dependencies = [ [[package]] name = "alloy-transport" -version = "0.4.2" +version = "0.5.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2ac3e97dad3d31770db0fc89bd6a63b789fbae78963086733f960cf32c483904" +checksum = "be77579633ebbc1266ae6fd7694f75c408beb1aeb6865d0b18f22893c265a061" dependencies = [ "alloy-json-rpc", "base64 0.22.1", @@ -207,13 +208,14 @@ dependencies = [ "tower 0.5.1", "tracing", "url", + "wasmtimer", ] [[package]] name = "alloy-transport-http" -version = "0.4.2" +version = "0.5.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b367dcccada5b28987c2296717ee04b9a5637aacd78eacb1726ef211678b5212" +checksum = "91fd1a5d0827939847983b46f2f79510361f901dc82f8e3c38ac7397af142c6e" dependencies = [ "alloy-json-rpc", "alloy-transport", @@ -691,6 +693,7 @@ dependencies = [ "colored", "dotenvy", "execute", + "regex", "reqwest 0.12.8", "serde", "serde_json", @@ -2685,7 +2688,7 @@ dependencies = [ "lalrpop-util", "petgraph", "regex", - "regex-syntax 0.8.4", + "regex-syntax 0.8.5", "string_cache", "term", "tiny-keccak", @@ -2699,7 +2702,7 @@ version = "0.20.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "507460a910eb7b32ee961886ff48539633b788a36b65692b95f225b844c82553" dependencies = [ - "regex-automata 0.4.7", + "regex-automata 0.4.8", ] [[package]] @@ -3334,7 +3337,7 @@ dependencies = [ "rand", "rand_chacha", "rand_xorshift", - "regex-syntax 0.8.4", + "regex-syntax 0.8.5", "rusty-fork", "tempfile", "unarray", @@ -3443,14 +3446,14 @@ dependencies = [ [[package]] name = "regex" -version = "1.10.5" +version = "1.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b91213439dad192326a0d7c6ee3955910425f441d7038e0d6933b0aec5c4517f" +checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191" dependencies = [ "aho-corasick", "memchr", - "regex-automata 0.4.7", - "regex-syntax 0.8.4", + "regex-automata 0.4.8", + "regex-syntax 0.8.5", ] [[package]] @@ -3464,13 +3467,13 @@ dependencies = [ [[package]] name = "regex-automata" -version = "0.4.7" +version = "0.4.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38caf58cc5ef2fed281f89292ef23f6365465ed9a41b7a7754eb4e26496c92df" +checksum = "368758f23274712b504848e9d5a6f010445cc8b87a7cdb4d7cbee666c1288da3" dependencies = [ "aho-corasick", "memchr", - "regex-syntax 0.8.4", + "regex-syntax 0.8.5", ] [[package]] @@ -3481,9 +3484,9 @@ checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1" [[package]] name = "regex-syntax" -version = "0.8.4" +version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a66a03ae7c801facd77a29370b4faec201768915ac14a721ba36f20bc9c209b" +checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" [[package]] name = "reqwest" @@ -3535,6 +3538,7 @@ dependencies = [ "base64 0.22.1", "bytes", "encoding_rs", + "futures-channel", "futures-core", "futures-util", "h2 0.4.5", @@ -5066,6 +5070,20 @@ version = "0.2.92" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "af190c94f2773fdb3729c55b007a722abb5384da03bc0986df4c289bf5567e96" +[[package]] +name = "wasmtimer" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7ed9d8b15c7fb594d72bfb4b5a276f3d2029333cd93a932f376f5937f6f80ee" +dependencies = [ + "futures", + "js-sys", + "parking_lot", + "pin-utils", + "slab", + "wasm-bindgen", +] + [[package]] name = "web-sys" version = "0.3.69" diff --git a/Dockerfile b/Dockerfile index ac5e759b..694cff7d 100644 --- a/Dockerfile +++ b/Dockerfile @@ -11,39 +11,10 @@ RUN go mod download COPY . . RUN make build-go -# BUILD RUST BIN -FROM --platform=${BUILDPLATFORM} rust:slim-bookworm AS chef -USER root -RUN apt-get update && apt-get install -y openssl pkg-config libssl-dev -RUN cargo install cargo-chef -WORKDIR /app - -FROM chef AS planner - -COPY --link crates crates -COPY --link Cargo.toml Cargo.toml -COPY --link Cargo.lock Cargo.lock - -RUN cargo chef prepare --recipe-path recipe.json --bin cdk - -FROM chef AS builder - -COPY --from=planner /app/recipe.json recipe.json -# Notice that we are specifying the --target flag! -RUN cargo chef cook --release --recipe-path recipe.json - -COPY --link crates crates -COPY --link Cargo.toml Cargo.toml -COPY --link Cargo.lock Cargo.lock - -ENV BUILD_SCRIPT_DISABLED=1 -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/* -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/agglayer/client.go b/agglayer/client.go index 132c2716..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 @@ -91,7 +99,9 @@ func (c *AggLayerClient) WaitTxToBeMined(hash common.Hash, ctx context.Context) // SendCertificate sends a certificate to the AggLayer func (c *AggLayerClient) SendCertificate(certificate *SignedCertificate) (common.Hash, error) { - response, err := rpc.JSONRPCCall(c.url, "interop_sendCertificate", certificate) + certificateToSend := certificate.CopyWithDefaulting() + + response, err := rpc.JSONRPCCall(c.url, "interop_sendCertificate", certificateToSend) if err != nil { return common.Hash{}, err } @@ -128,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/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/mock_agglayer_client.go b/agglayer/mock_agglayer_client.go index 43100a2e..b7f70ee8 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. 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,91 @@ 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() + + 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 +} + +// 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) @@ -75,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) @@ -105,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) @@ -123,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/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/l1_info_root_incorrect_error.json b/agglayer/testdata/type_conversion_errors/l1_info_root_incorrect_error.json new file mode 100644 index 00000000..dc74e325 --- /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\"}}}}}}" + } +] \ 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..3d75658f --- /dev/null +++ b/agglayer/type_conversion_error.go @@ -0,0 +1,306 @@ +package agglayer + +import ( + "errors" + "fmt" + + "github.com/ethereum/go-ethereum/common" +) + +const ( + MultipleL1InfoRootErrorType = "MultipleL1InfoRoot" + MismatchNewLocalExitRootErrorType = "MismatchNewLocalExitRoot" + BalanceOverflowErrorType = "BalanceOverflow" + BalanceUnderflowErrorType = "BalanceUnderflow" + BalanceProofGenerationFailedErrorType = "BalanceProofGenerationFailed" + NullifierPathGenerationFailedErrorType = "NullifierPathGenerationFailed" + L1InfoRootIncorrectErrorType = "L1InfoRootIncorrect" +) + +// 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 + 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) + } + + 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) +} + +// 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 +} diff --git a/agglayer/types.go b/agglayer/types.go index 825c9db2..aece93f0 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 { @@ -86,6 +84,30 @@ 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()) + } + } + + return res +} + // Hash returns a hash that uniquely identifies the certificate func (c *Certificate) Hash() common.Hash { bridgeExitsHashes := make([][]byte, len(c.BridgeExits)) @@ -131,6 +153,33 @@ 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()) +} + +// CopyWithDefaulting returns a shallow copy of the signed certificate +func (s *SignedCertificate) CopyWithDefaulting() *SignedCertificate { + certificateCopy := *s.Certificate + + if certificateCopy.BridgeExits == nil { + certificateCopy.BridgeExits = make([]*BridgeExit, 0) + } + + if certificateCopy.ImportedBridgeExits == nil { + certificateCopy.ImportedBridgeExits = make([]*ImportedBridgeExit, 0) + } + + signature := s.Signature + if signature == nil { + signature = &Signature{} + } + + return &SignedCertificate{ + Certificate: &certificateCopy, + Signature: signature, + } +} + // Signature is the data structure that will hold the signature of the given certificate type Signature struct { R common.Hash `json:"r"` @@ -138,12 +187,21 @@ type Signature struct { OddParity bool `json:"odd_y_parity"` } +func (s *Signature) String() string { + return fmt.Sprintf("R: %s, S: %s, OddParity: %t", s.R.String(), s.S.String(), s.OddParity) +} + // TokenInfo encapsulates the information to uniquely identify a token on the origin network. type TokenInfo struct { OriginNetwork uint32 `json:"origin_network"` 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()) +} + // GlobalIndex represents the global index of an imported bridge exit type GlobalIndex struct { MainnetFlag bool `json:"mainnet_flag"` @@ -151,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( @@ -159,6 +222,29 @@ func (g *GlobalIndex) Hash() common.Hash { ) } +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 type BridgeExit struct { LeafType LeafType `json:"leaf_type"` @@ -169,6 +255,20 @@ type BridgeExit struct { Metadata []byte `json:"metadata"` } +func (b *BridgeExit) String() string { + res := fmt.Sprintf("LeafType: %s, DestinationNetwork: %d, DestinationAddress: %s, Amount: %s, Metadata: %s", + b.LeafType.String(), b.DestinationNetwork, b.DestinationAddress.String(), + b.Amount.String(), common.Bytes2Hex(b.Metadata)) + + if b.TokenInfo == nil { + res += ", TokenInfo: nil" + } else { + res += fmt.Sprintf(", TokenInfo: %s", b.TokenInfo.String()) + } + + return res +} + // Hash returns a hash that uniquely identifies the bridge exit func (b *BridgeExit) Hash() common.Hash { if b.Amount == nil { @@ -252,6 +352,10 @@ func (m *MerkleProof) Hash() common.Hash { ) } +func (m *MerkleProof) String() string { + return fmt.Sprintf("Root: %s, Proof: %v", m.Root.String(), m.Proof) +} + // L1InfoTreeLeafInner represents the inner part of the L1 info tree leaf type L1InfoTreeLeafInner struct { GlobalExitRoot common.Hash `json:"global_exit_root"` @@ -281,6 +385,11 @@ func (l *L1InfoTreeLeafInner) MarshalJSON() ([]byte, error) { }) } +func (l *L1InfoTreeLeafInner) String() string { + return fmt.Sprintf("GlobalExitRoot: %s, BlockHash: %s, Timestamp: %d", + l.GlobalExitRoot.String(), l.BlockHash.String(), l.Timestamp) +} + // L1InfoTreeLeaf represents the leaf of the L1 info tree type L1InfoTreeLeaf struct { L1InfoTreeIndex uint32 `json:"l1_info_tree_index"` @@ -294,11 +403,21 @@ func (l *L1InfoTreeLeaf) Hash() common.Hash { return l.Inner.Hash() } +func (l *L1InfoTreeLeaf) String() string { + return fmt.Sprintf("L1InfoTreeIndex: %d, RollupExitRoot: %s, MainnetExitRoot: %s, Inner: %s", + l.L1InfoTreeIndex, + common.Bytes2Hex(l.RollupExitRoot[:]), + common.Bytes2Hex(l.MainnetExitRoot[:]), + l.Inner.String(), + ) +} + // Claim is the interface that will be implemented by the different types of claims type Claim interface { Type() string Hash() common.Hash MarshalJSON() ([]byte, error) + String() string } // ClaimFromMainnnet represents a claim originating from the mainnet @@ -335,6 +454,11 @@ func (c *ClaimFromMainnnet) Hash() common.Hash { ) } +func (c *ClaimFromMainnnet) String() string { + return fmt.Sprintf("ProofLeafMER: %s, ProofGERToL1Root: %s, L1Leaf: %s", + c.ProofLeafMER.String(), c.ProofGERToL1Root.String(), c.L1Leaf.String()) +} + // ClaimFromRollup represents a claim originating from a rollup type ClaimFromRollup struct { ProofLeafLER *MerkleProof `json:"proof_leaf_ler"` @@ -372,6 +496,11 @@ func (c *ClaimFromRollup) Hash() common.Hash { ) } +func (c *ClaimFromRollup) String() string { + return fmt.Sprintf("ProofLeafLER: %s, ProofLERToRER: %s, ProofGERToL1Root: %s, L1Leaf: %s", + c.ProofLeafLER.String(), c.ProofLERToRER.String(), c.ProofGERToL1Root.String(), c.L1Leaf.String()) +} + // ImportedBridgeExit represents a token bridge exit originating on another network but claimed on the current network. type ImportedBridgeExit struct { BridgeExit *BridgeExit `json:"bridge_exit"` @@ -379,6 +508,26 @@ type ImportedBridgeExit struct { GlobalIndex *GlobalIndex `json:"global_index"` } +func (c *ImportedBridgeExit) String() string { + var res string + + if c.BridgeExit == nil { + res = "BridgeExit: nil" + } else { + res = fmt.Sprintf("BridgeExit: %s", c.BridgeExit.String()) + } + + if c.GlobalIndex == nil { + res += ", GlobalIndex: nil" + } else { + res += fmt.Sprintf(", GlobalIndex: %s", c.GlobalIndex.String()) + } + + res += fmt.Sprintf("ClaimData: %s", c.ClaimData.String()) + + return res +} + // Hash returns a hash that uniquely identifies the imported bridge exit func (c *ImportedBridgeExit) Hash() common.Hash { return crypto.Keccak256Hash( @@ -398,9 +547,96 @@ 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 +} + +// 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/agglayer/types_test.go b/agglayer/types_test.go index 325c0b88..f2133923 100644 --- a/agglayer/types_test.go +++ b/agglayer/types_test.go @@ -64,3 +64,190 @@ func TestMarshalJSON(t *testing.T) { log.Info(string(data)) require.Equal(t, expectedSignedCertificateyMetadataJSON, string(data)) } + +func TestSignedCertificate_Copy(t *testing.T) { + t.Parallel() + + t.Run("copy with non-nil fields", func(t *testing.T) { + t.Parallel() + + original := &SignedCertificate{ + Certificate: &Certificate{ + NetworkID: 1, + Height: 100, + PrevLocalExitRoot: [32]byte{0x01}, + NewLocalExitRoot: [32]byte{0x02}, + BridgeExits: []*BridgeExit{ + { + LeafType: LeafTypeAsset, + TokenInfo: &TokenInfo{OriginNetwork: 1, OriginTokenAddress: common.HexToAddress("0x123")}, + DestinationNetwork: 2, + DestinationAddress: common.HexToAddress("0x456"), + Amount: big.NewInt(1000), + Metadata: []byte{0x01, 0x02}, + }, + }, + ImportedBridgeExits: []*ImportedBridgeExit{ + { + BridgeExit: &BridgeExit{ + LeafType: LeafTypeMessage, + TokenInfo: &TokenInfo{OriginNetwork: 1, OriginTokenAddress: common.HexToAddress("0x789")}, + DestinationNetwork: 3, + DestinationAddress: common.HexToAddress("0xabc"), + Amount: big.NewInt(2000), + Metadata: []byte{0x03, 0x04}, + }, + ClaimData: &ClaimFromMainnnet{}, + GlobalIndex: &GlobalIndex{MainnetFlag: true, RollupIndex: 1, LeafIndex: 2}, + }, + }, + Metadata: common.HexToHash("0xdef"), + }, + Signature: &Signature{ + R: common.HexToHash("0x111"), + S: common.HexToHash("0x222"), + OddParity: true, + }, + } + + certificateCopy := original.CopyWithDefaulting() + + require.NotNil(t, certificateCopy) + require.NotSame(t, original, certificateCopy) + require.NotSame(t, original.Certificate, certificateCopy.Certificate) + require.Same(t, original.Signature, certificateCopy.Signature) + require.Equal(t, original, certificateCopy) + }) + + t.Run("copy with nil BridgeExits, ImportedBridgeExits and Signature", func(t *testing.T) { + t.Parallel() + + original := &SignedCertificate{ + Certificate: &Certificate{ + NetworkID: 1, + Height: 100, + PrevLocalExitRoot: [32]byte{0x01}, + NewLocalExitRoot: [32]byte{0x02}, + BridgeExits: nil, + ImportedBridgeExits: nil, + Metadata: common.HexToHash("0xdef"), + }, + Signature: nil, + } + + certificateCopy := original.CopyWithDefaulting() + + require.NotNil(t, certificateCopy) + require.NotSame(t, original, certificateCopy) + require.NotSame(t, original.Certificate, certificateCopy.Certificate) + require.NotNil(t, certificateCopy.Signature) + require.Equal(t, original.NetworkID, certificateCopy.NetworkID) + require.Equal(t, original.Height, certificateCopy.Height) + require.Equal(t, original.PrevLocalExitRoot, certificateCopy.PrevLocalExitRoot) + require.Equal(t, original.NewLocalExitRoot, certificateCopy.NewLocalExitRoot) + require.Equal(t, original.Metadata, certificateCopy.Metadata) + require.NotNil(t, certificateCopy.BridgeExits) + require.NotNil(t, certificateCopy.ImportedBridgeExits) + require.Empty(t, certificateCopy.BridgeExits) + 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/aggregator/aggregator.go b/aggregator/aggregator.go index 64f667ad..58e97402 100644 --- a/aggregator/aggregator.go +++ b/aggregator/aggregator.go @@ -757,7 +757,6 @@ func (a *Aggregator) validateEligibleFinalProof( batchNumberToVerify := lastVerifiedBatchNum + 1 if proof.BatchNumber != batchNumberToVerify { - //nolint:gocritic if proof.BatchNumber < batchNumberToVerify && proof.BatchNumberFinal >= batchNumberToVerify { // We have a proof that contains some batches below the last batch verified, anyway can be eligible as final proof @@ -971,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())) @@ -1122,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 } @@ -1139,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 } @@ -1164,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())) @@ -1197,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, @@ -1326,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())) @@ -1338,7 +1359,8 @@ func (a *Aggregator) tryGenerateBatchProof(ctx context.Context, prover ProverInt // 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]", + 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) @@ -1347,6 +1369,20 @@ func (a *Aggregator) tryGenerateBatchProof(ctx context.Context, prover ProverInt 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 a.cfg.BatchProofSanityCheckEnabled && (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) + } + proof.Proof = resGetProof // NOTE(pg): the defer func is useless from now on, use a different variable @@ -1506,10 +1542,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..ff788190 100644 --- a/aggregator/aggregator_test.go +++ b/aggregator/aggregator_test.go @@ -6,7 +6,6 @@ import ( "crypto/ecdsa" "crypto/elliptic" "crypto/rand" - "encoding/hex" "encoding/json" "errors" "fmt" @@ -1114,7 +1113,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 +1171,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 +1219,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 +1279,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() @@ -1343,7 +1342,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, @@ -1432,534 +1431,6 @@ func Test_tryAggregateProofs(t *testing.T) { } } -func Test_tryGenerateBatchProof(t *testing.T) { - require := require.New(t) - assert := assert.New(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), - ForkId: uint64(12), - } - lastVerifiedBatchNum := uint64(22) - - batchNum := uint64(23) - - batchToProve := state.Batch{ - BatchNumber: batchNum, - } - - proofID := "proofId" - - proverName := "proverName" - proverID := "proverID" - recursiveProof := "recursiveProof" - errTest := errors.New("test error") - 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 } - fixedTimestamp := time.Date(2023, 10, 13, 15, 0, 0, 0, time.UTC) - - l1InfoTreeLeaf := []synchronizer.L1InfoTreeLeaf{ - { - GlobalExitRoot: common.Hash{}, - PreviousBlockHash: common.Hash{}, - Timestamp: fixedTimestamp, - }, - { - GlobalExitRoot: common.Hash{}, - PreviousBlockHash: common.Hash{}, - Timestamp: fixedTimestamp, - }, - { - GlobalExitRoot: common.Hash{}, - PreviousBlockHash: common.Hash{}, - Timestamp: fixedTimestamp, - }, - { - GlobalExitRoot: common.Hash{}, - PreviousBlockHash: common.Hash{}, - Timestamp: fixedTimestamp, - }, - } - - testCases := []struct { - name string - setup func(mox, *Aggregator) - asserts func(bool, *Aggregator, error) - }{ - { - name: "getAndLockBatchToProve returns generic 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") - m.etherman.On("GetLatestVerifiedBatchNum").Return(uint64(0), errTest).Once() - }, - asserts: func(result bool, a *Aggregator, err error) { - assert.False(result) - assert.ErrorIs(err, errTest) - }, - }, - { - name: "getAndLockBatchToProve returns ErrNotFound", - 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") - m.etherman.On("GetLatestVerifiedBatchNum").Return(uint64(0), state.ErrNotFound).Once() - }, - asserts: func(result bool, a *Aggregator, err error) { - assert.False(result) - assert.NoError(err) - }, - }, - { - name: "BatchProof 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").Twice() - - batchL2Data, err := hex.DecodeString(codedL2Block1) - require.NoError(err) - l1InfoRoot := common.HexToHash("0x057e9950fbd39b002e323f37c2330d0c096e66919e24cc96fb4b2dfa8f4af782") - - virtualBatch := synchronizer.VirtualBatch{ - BatchNumber: lastVerifiedBatchNum + 1, - BatchL2Data: batchL2Data, - L1InfoRoot: &l1InfoRoot, - } - - m.synchronizerMock.On("GetVirtualBatchByBatchNumber", mock.Anything, lastVerifiedBatchNum).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("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() - - 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.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) - m.synchronizerMock.On("GetL1InfoTreeLeaves", mock.Anything, mock.Anything).Return(map[uint32]synchronizer.L1InfoTreeLeaf{ - 1: { - BlockNumber: uint64(35), - }, - }, 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() - }, - 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").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), - } - - virtualBatch := synchronizer.VirtualBatch{ - BatchNumber: lastVerifiedBatchNum + 1, - BatchL2Data: batchL2Data, - L1InfoRoot: &l1InfoRoot, - } - - 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() - sequence := synchronizer.SequencedBatches{ - FromBatchNumber: uint64(10), - 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("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( - 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() - - 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() - }, - asserts: func(result bool, a *Aggregator, err error) { - assert.False(result) - assert.ErrorIs(err, errTest) - }, - }, - { - name: "DeleteBatchProofs 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("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() - sequence := synchronizer.SequencedBatches{ - FromBatchNumber: uint64(10), - 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) { - 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.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() - - 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() - }, - asserts: func(result bool, a *Aggregator, err error) { - assert.False(result) - assert.ErrorIs(err, errTest) - }, - }, - { - name: "not time to send final ok", - 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) - - 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() - - 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() - - 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() - - 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() - - 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() - }, - asserts: func(result bool, a *Aggregator, err error) { - assert.True(result) - assert.NoError(err) - }, - }, - } - - for _, tc := range testCases { - t.Run(tc.name, func(t *testing.T) { - stateMock := mocks.NewStateInterfaceMock(t) - ethTxManager := mocks.NewEthTxManagerClientMock(t) - etherman := mocks.NewEthermanMock(t) - proverMock := mocks.NewProverInterfaceMock(t) - synchronizerMock := mocks.NewSynchronizerInterfaceMock(t) - mockRPC := mocks.NewRPCInterfaceMock(t) - - a := Aggregator{ - cfg: cfg, - state: stateMock, - etherman: etherman, - ethTxManager: ethTxManager, - logger: log.GetDefaultLogger(), - stateDBMutex: &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), - accInputHashesMutex: &sync.Mutex{}, - } - aggregatorCtx := context.WithValue(context.Background(), "owner", ownerAggregator) //nolint:staticcheck - a.ctx, a.exit = context.WithCancel(aggregatorCtx) - - m := mox{ - stateMock: stateMock, - ethTxManager: ethTxManager, - etherman: etherman, - proverMock: proverMock, - synchronizerMock: synchronizerMock, - rpcMock: mockRPC, - } - if tc.setup != nil { - tc.setup(m, &a) - } - a.resetVerifyProofTime() - - result, err := a.tryGenerateBatchProof(proverCtx, proverMock) - - if tc.asserts != nil { - tc.asserts(result, &a, err) - } - }) - } -} - func Test_accInputHashFunctions(t *testing.T) { aggregator := Aggregator{ accInputHashes: make(map[uint64]common.Hash), 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/prover.go b/aggregator/prover/prover.go index 8cb13b1d..a5f7e9eb 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..ee12c3ac 100644 --- a/aggregator/prover/prover_test.go +++ b/aggregator/prover/prover_test.go @@ -11,7 +11,9 @@ import ( ) const ( - dir = "../../test/vectors/proofs" + dir = "../../test/vectors/proofs" + stateRootStartIndex = 19 + stateRootFinalIndex = stateRootStartIndex + 8 ) type TestStateRoot struct { @@ -40,7 +42,7 @@ 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), stateRootStartIndex, stateRootFinalIndex) require.NoError(t, err) // Get the expected state root diff --git a/aggsender/aggsender.go b/aggsender/aggsender.go index f1df20ff..08730572 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" @@ -27,15 +28,17 @@ var ( errNoBridgesAndClaims = errors.New("no bridges and claims to build certificate") errInvalidSignatureSize = errors.New("invalid signature size") - zeroLER = common.HexToHash("0x27ae5ba08d7291c96c8cbddcc148bf48a6d68c7974b94356f53754ef6171d757") + zeroLER = common.HexToHash("0x27ae5ba08d7291c96c8cbddcc148bf48a6d68c7974b94356f53754ef6171d757") + nonSettledStatuses = []agglayer.CertificateStatus{agglayer.Pending, agglayer.Candidate, agglayer.Proven} ) // 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 @@ -52,7 +55,8 @@ func New( cfg Config, aggLayerClient agglayer.AgglayerClientInterface, l1InfoTreeSyncer *l1infotreesync.L1InfoTreeSync, - l2Syncer *bridgesync.BridgeSync) (*AggSender, error) { + l2Syncer types.L2BridgeSyncer, + epochNotifier types.EpochNotifier) (*AggSender, error) { storage, err := db.NewAggSenderSQLStorage(logger, cfg.StoragePath) if err != nil { return nil, err @@ -63,6 +67,8 @@ func New( return nil, err } + logger.Infof("Aggsender Config: %s.", cfg.String()) + return &AggSender{ cfg: cfg, log: logger, @@ -71,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 := a.checkPendingCertificatesStatus(ctx) + if !thereArePendingCerts { + if _, err := a.sendCertificate(ctx); err != nil { + log.Error(err) + } + } else { + log.Infof("Skipping epoch %s because there are pending certificates", + epoch.String()) } case <-ctx.Done(): a.log.Info("AggSender stopped") @@ -98,27 +110,27 @@ func (a *AggSender) sendCertificates(ctx context.Context) { } // sendCertificate sends certificate for a network -func (a *AggSender) sendCertificate(ctx context.Context) error { +func (a *AggSender) sendCertificate(ctx context.Context) (*agglayer.SignedCertificate, error) { a.log.Infof("trying to send a new certificate...") - shouldSend, err := a.shouldSendCertificate(ctx) + shouldSend, err := a.shouldSendCertificate() if err != nil { - return err + return nil, err } if !shouldSend { a.log.Infof("waiting for pending certificates to be settled") - return nil + return nil, nil } lasL2BlockSynced, err := a.l2Syncer.GetLastProcessedBlock(ctx) if err != nil { - return fmt.Errorf("error getting last processed block from l2: %w", err) + return nil, fmt.Errorf("error getting last processed block from l2: %w", err) } - lastSentCertificateInfo, err := a.storage.GetLastSentCertificate(ctx) + lastSentCertificateInfo, err := a.storage.GetLastSentCertificate() if err != nil { - return err + return nil, err } previousToBlock := lastSentCertificateInfo.ToBlock @@ -131,7 +143,7 @@ func (a *AggSender) sendCertificate(ctx context.Context) error { if previousToBlock >= lasL2BlockSynced { a.log.Infof("no new blocks to send a certificate, last certificate block: %d, last L2 block: %d", previousToBlock, lasL2BlockSynced) - return nil + return nil, nil } fromBlock := previousToBlock + 1 @@ -139,64 +151,77 @@ func (a *AggSender) sendCertificate(ctx context.Context) error { bridges, err := a.l2Syncer.GetBridgesPublished(ctx, fromBlock, toBlock) if err != nil { - return fmt.Errorf("error getting bridges: %w", err) + return nil, fmt.Errorf("error getting bridges: %w", err) } if len(bridges) == 0 { a.log.Infof("no bridges consumed, no need to send a certificate from block: %d to block: %d", fromBlock, toBlock) - return nil + return nil, nil } claims, err := a.l2Syncer.GetClaims(ctx, fromBlock, toBlock) if err != nil { - return fmt.Errorf("error getting claims: %w", err) + return nil, fmt.Errorf("error getting claims: %w", err) } a.log.Infof("building certificate for block: %d to block: %d", fromBlock, toBlock) certificate, err := a.buildCertificate(ctx, bridges, claims, lastSentCertificateInfo, toBlock) if err != nil { - return fmt.Errorf("error building certificate: %w", err) + return nil, fmt.Errorf("error building certificate: %w", err) } signedCertificate, err := a.signCertificate(certificate) if err != nil { - return fmt.Errorf("error signing certificate: %w", err) + return nil, fmt.Errorf("error signing certificate: %w", err) } 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 fmt.Errorf("error sending certificate: %w", err) + return nil, fmt.Errorf("error sending certificate: %w", err) + } + + a.log.Debugf("certificate send: Height: %d hash: %s", signedCertificate.Height, certificateHash.String()) + + raw, err := json.Marshal(signedCertificate) + if err != nil { + return nil, fmt.Errorf("error marshalling signed certificate: %w", err) + } + + createdTime := time.Now().UTC().UnixMilli() + certInfo := types.CertificateInfo{ + Height: certificate.Height, + CertificateID: certificateHash, + NewLocalExitRoot: certificate.NewLocalExitRoot, + FromBlock: fromBlock, + ToBlock: toBlock, + CreatedAt: createdTime, + UpdatedAt: createdTime, + SignedCertificate: string(raw), } - log.Infof("certificate send: Height: %d hash: %s", signedCertificate.Height, certificateHash.String()) - if err := a.storage.SaveLastSentCertificate(ctx, aggsendertypes.CertificateInfo{ - Height: certificate.Height, - CertificateID: certificateHash, - NewLocalExitRoot: certificate.NewLocalExitRoot, - FromBlock: fromBlock, - ToBlock: toBlock, - }); err != nil { - return fmt.Errorf("error saving last sent certificate in db: %w", err) + if err := a.storage.SaveLastSentCertificate(ctx, certInfo); err != nil { + 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)", - certificateHash, fromBlock, toBlock) + 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()) - return nil + return signedCertificate, nil } // saveCertificate saves the certificate to a tmp file func (a *AggSender) saveCertificateToFile(signedCertificate *agglayer.SignedCertificate) { - if signedCertificate == nil || !a.cfg.SaveCertificatesToFiles { + if signedCertificate == nil || a.cfg.SaveCertificatesToFilesPath == "" { return } - - fn := fmt.Sprintf("/tmp/certificate_%04d.json", signedCertificate.Height) + fn := fmt.Sprintf("%s/certificate_%04d-%07d.json", + a.cfg.SaveCertificatesToFilesPath, signedCertificate.Height, time.Now().Unix()) a.log.Infof("saving certificate to file: %s", fn) - jsonData, err := json.Marshal(signedCertificate) + jsonData, err := json.MarshalIndent(signedCertificate, "", " ") if err != nil { a.log.Errorf("error marshalling certificate: %w", err) } @@ -206,11 +231,32 @@ 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 == agglayer.InError { + // 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 + } + + previousLER := lastSentCertificateInfo.NewLocalExitRoot + if lastSentCertificateInfo.NewLocalExitRoot == (common.Hash{}) { + // meaning this is the first certificate + height = 0 + previousLER = zeroLER + } + + return height, previousLER +} + // buildCertificate builds a certificate from the bridge events 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 @@ -223,6 +269,7 @@ func (a *AggSender) buildCertificate(ctx context.Context, } var depositCount uint32 + if len(bridges) > 0 { depositCount = bridges[len(bridges)-1].DepositCount } @@ -232,13 +279,7 @@ func (a *AggSender) buildCertificate(ctx context.Context, return nil, fmt.Errorf("error getting exit root by index: %d. Error: %w", depositCount, err) } - height := lastSentCertificateInfo.Height + 1 - previousLER := lastSentCertificateInfo.NewLocalExitRoot - if lastSentCertificateInfo.NewLocalExitRoot == (common.Hash{}) { - // meaning this is the first certificate - height = 0 - previousLER = zeroLER - } + height, previousLER := a.getNextHeightAndPreviousLER(&lastSentCertificateInfo) return &agglayer.Certificate{ NetworkID: a.l2Syncer.OriginNetwork(), @@ -312,7 +353,7 @@ func (a *AggSender) getImportedBridgeExits( ) ([]*agglayer.ImportedBridgeExit, error) { if len(claims) == 0 { // no claims to convert - return nil, nil + return []*agglayer.ImportedBridgeExit{}, nil } var ( @@ -443,52 +484,59 @@ 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) { - pendingCertificates, err := a.storage.GetCertificatesByStatus(ctx, []agglayer.CertificateStatus{agglayer.Pending}) +// It returns: +// bool -> if there are pending certificates +func (a *AggSender) checkPendingCertificatesStatus(ctx context.Context) bool { + pendingCertificates, err := a.storage.GetCertificatesByStatus(nonSettledStatuses) if err != nil { - a.log.Errorf("error getting pending certificates: %w", err) + err = fmt.Errorf("error getting pending certificates: %w", err) + a.log.Error(err) + return true } - + thereArePendingCerts := 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 header of certificate %s with height: %d from agglayer: %w", - certificate.CertificateID, certificate.Height, 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 } + 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 != agglayer.Pending { - certificate.Status = certificateHeader.Status + 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) - a.log.Infof("certificate %s changed status to %s", certificateHeader.String(), certificate.Status) + certificate.Status = certificateHeader.Status + certificate.UpdatedAt = time.Now().UTC().UnixMilli() if err := a.storage.UpdateCertificateStatus(ctx, *certificate); err != nil { - a.log.Errorf("error updating certificate status in storage: %w", err) - continue + err = fmt.Errorf("error updating certificate %s status in storage: %w", certificateHeader.String(), err) + a.log.Error(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 thereArePendingCerts } // 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(ctx context.Context) (bool, error) { - pendingCertificates, err := a.storage.GetCertificatesByStatus(ctx, []agglayer.CertificateStatus{agglayer.Pending}) +func (a *AggSender) shouldSendCertificate() (bool, error) { + pendingCertificates, err := a.storage.GetCertificatesByStatus(nonSettledStatuses) if err != nil { return false, fmt.Errorf("error getting pending certificates: %w", err) } diff --git a/aggsender/aggsender_test.go b/aggsender/aggsender_test.go index 71878679..b9242bdf 100644 --- a/aggsender/aggsender_test.go +++ b/aggsender/aggsender_test.go @@ -27,12 +27,46 @@ 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{ + StoragePath: "/path/to/storage", + AggLayerURL: "http://agglayer.url", + BlockGetInterval: types.Duration{Duration: 10 * time.Second}, + 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", + } + + expected := "StoragePath: /path/to/storage\n" + + "AggLayerURL: http://agglayer.url\n" + + "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" + + "SaveCertificatesToFilesPath: /path/to/certificates\n" + + require.Equal(t, expected, config.String()) +} func TestConvertClaimToImportedBridgeExit(t *testing.T) { t.Parallel() @@ -246,12 +280,77 @@ 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() mockProof := generateTestProof(t) - mockL1InfoTreeSyncer := mocks.NewL1InfoTreeSyncerMock(t) + + mockL1InfoTreeSyncer := mocks.NewL1InfoTreeSyncer(t) mockL1InfoTreeSyncer.On("GetInfoByGlobalExitRoot", mock.Anything).Return(&l1infotreesync.L1InfoTreeLeaf{ L1InfoTreeIndex: 1, Timestamp: 123456789, @@ -456,7 +555,7 @@ func TestGetImportedBridgeExits(t *testing.T) { name: "No claims", claims: []bridgesync.Claim{}, expectedError: false, - expectedExits: nil, + expectedExits: []*agglayer.ImportedBridgeExit{}, }, } @@ -484,8 +583,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 { @@ -715,8 +814,6 @@ func generateTestProof(t *testing.T) treeTypes.Proof { } func TestCheckIfCertificatesAreSettled(t *testing.T) { - t.Parallel() - tests := []struct { name string pendingCertificates []*aggsendertypes.CertificateInfo @@ -726,6 +823,7 @@ func TestCheckIfCertificatesAreSettled(t *testing.T) { updateDBError error expectedErrorLogMessages []string expectedInfoMessages []string + expectedError bool }{ { name: "All certificates settled - update successful", @@ -761,6 +859,7 @@ func TestCheckIfCertificatesAreSettled(t *testing.T) { expectedErrorLogMessages: []string{ "error getting pending certificates: %w", }, + expectedError: true, }, { name: "Error getting certificate header", @@ -774,6 +873,7 @@ func TestCheckIfCertificatesAreSettled(t *testing.T) { expectedErrorLogMessages: []string{ "error getting header of certificate %s with height: %d from agglayer: %w", }, + expectedError: true, }, { name: "Error updating certificate status", @@ -790,6 +890,7 @@ func TestCheckIfCertificatesAreSettled(t *testing.T) { expectedInfoMessages: []string{ "certificate %s changed status to %s", }, + expectedError: true, }, } @@ -797,13 +898,12 @@ 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 := mocks.NewLoggerMock(t) + mockLogger := log.WithFields("test", "unittest") - mockStorage.On("GetCertificatesByStatus", mock.Anything, []agglayer.CertificateStatus{agglayer.Pending}).Return(tt.pendingCertificates, tt.getFromDBError) + mockStorage.On("GetCertificatesByStatus", nonSettledStatuses).Return( + tt.pendingCertificates, tt.getFromDBError) for certID, header := range tt.certificateHeaders { mockAggLayerClient.On("GetCertificateHeader", certID).Return(header, tt.clientError) } @@ -813,20 +913,6 @@ func TestCheckIfCertificatesAreSettled(t *testing.T) { mockStorage.On("UpdateCertificateStatus", mock.Anything, mock.Anything).Return(nil) } - if tt.clientError != nil { - for _, msg := range tt.expectedErrorLogMessages { - mockLogger.On("Errorf", msg, mock.Anything, mock.Anything, mock.Anything).Return() - } - } else { - for _, msg := range tt.expectedErrorLogMessages { - mockLogger.On("Errorf", msg, mock.Anything).Return() - } - - for _, msg := range tt.expectedInfoMessages { - mockLogger.On("Infof", msg, mock.Anything, mock.Anything).Return() - } - } - aggSender := &AggSender{ log: mockLogger, storage: mockStorage, @@ -837,15 +923,9 @@ func TestCheckIfCertificatesAreSettled(t *testing.T) { }, } - ctx, cancel := context.WithCancel(context.Background()) - defer cancel() - - go aggSender.checkIfCertificatesAreSettled(ctx) - - time.Sleep(2 * time.Second) - cancel() - - mockLogger.AssertExpectations(t) + ctx := context.TODO() + thereArePendingCerts := aggSender.checkPendingCertificatesStatus(ctx) + require.Equal(t, tt.expectedError, thereArePendingCerts) mockAggLayerClient.AssertExpectations(t) mockStorage.AssertExpectations(t) }) @@ -876,30 +956,30 @@ 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.On("GetCertificatesByStatus", mock.Anything, []agglayer.CertificateStatus{agglayer.Pending}). + mockStorage = mocks.NewAggSenderStorage(t) + mockStorage.On("GetCertificatesByStatus", nonSettledStatuses). Return(cfg.shouldSendCertificate...).Once() aggsender.storage = mockStorage if cfg.getLastSentCertificate != nil { - mockStorage.On("GetLastSentCertificate", mock.Anything).Return(cfg.getLastSentCertificate...).Once() + mockStorage.On("GetLastSentCertificate").Return(cfg.getLastSentCertificate...).Once() } if cfg.saveLastSentCertificate != nil { @@ -909,7 +989,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() @@ -941,7 +1021,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 { @@ -1235,7 +1315,7 @@ func TestSendCertificate(t *testing.T) { aggsender, mockStorage, mockL2Syncer, mockAggLayerClient, mockL1InfoTreeSyncer := setupTest(tt) - err := aggsender.sendCertificate(context.Background()) + _, err := aggsender.sendCertificate(context.Background()) if tt.expectedError != "" { require.ErrorContains(t, err, tt.expectedError) @@ -1408,3 +1488,126 @@ func TestExploratoryGenerateCert(t *testing.T) { encoder.SetIndent("", " ") require.NoError(t, encoder.Encode(certificate)) } + +func TestGetNextHeightAndPreviousLER(t *testing.T) { + t.Parallel() + + tests := []struct { + name string + lastSentCertificateInfo aggsendertypes.CertificateInfo + expectedHeight uint64 + expectedPreviousLER common.Hash + }{ + { + name: "Normal case", + lastSentCertificateInfo: aggsendertypes.CertificateInfo{ + Height: 10, + NewLocalExitRoot: common.HexToHash("0x123"), + Status: agglayer.Settled, + }, + expectedHeight: 11, + expectedPreviousLER: common.HexToHash("0x123"), + }, + { + name: "Previous certificate in error", + lastSentCertificateInfo: aggsendertypes.CertificateInfo{ + Height: 10, + NewLocalExitRoot: common.HexToHash("0x123"), + Status: agglayer.InError, + }, + expectedHeight: 10, + expectedPreviousLER: common.HexToHash("0x123"), + }, + { + name: "First certificate", + lastSentCertificateInfo: aggsendertypes.CertificateInfo{ + Height: 0, + NewLocalExitRoot: common.Hash{}, + Status: agglayer.Settled, + }, + expectedHeight: 0, + expectedPreviousLER: zeroLER, + }, + } + + for _, tt := range tests { + tt := tt + + 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) + }) + } +} + +func TestSendCertificate_NoClaims(t *testing.T) { + t.Parallel() + + privateKey, err := crypto.GenerateKey() + require.NoError(t, err) + + ctx := context.Background() + mockStorage := mocks.NewAggSenderStorage(t) + mockL2Syncer := mocks.NewL2BridgeSyncer(t) + mockAggLayerClient := agglayer.NewAgglayerClientMock(t) + mockL1InfoTreeSyncer := mocks.NewL1InfoTreeSyncer(t) + + aggSender := &AggSender{ + log: log.WithFields("aggsender-test", "no claims test"), + storage: mockStorage, + l2Syncer: mockL2Syncer, + aggLayerClient: mockAggLayerClient, + l1infoTreeSyncer: mockL1InfoTreeSyncer, + sequencerKey: privateKey, + cfg: Config{ + BlockGetInterval: types.Duration{Duration: time.Second}, + CheckSettledInterval: types.Duration{Duration: time.Second}, + }, + } + + mockStorage.On("GetCertificatesByStatus", nonSettledStatuses).Return([]*aggsendertypes.CertificateInfo{}, nil).Once() + mockStorage.On("GetLastSentCertificate").Return(aggsendertypes.CertificateInfo{ + NewLocalExitRoot: common.HexToHash("0x123"), + Height: 1, + FromBlock: 0, + ToBlock: 10, + }, nil).Once() + mockStorage.On("SaveLastSentCertificate", mock.Anything, mock.Anything).Return(nil).Once() + mockL2Syncer.On("GetLastProcessedBlock", mock.Anything).Return(uint64(50), nil) + mockL2Syncer.On("GetBridgesPublished", mock.Anything, uint64(11), uint64(50)).Return([]bridgesync.Bridge{ + { + BlockNum: 30, + BlockPos: 0, + LeafType: agglayer.LeafTypeAsset.Uint8(), + OriginNetwork: 1, + OriginAddress: common.HexToAddress("0x1"), + DestinationNetwork: 2, + DestinationAddress: common.HexToAddress("0x2"), + Amount: big.NewInt(100), + Metadata: []byte("metadata"), + DepositCount: 1, + }, + }, nil).Once() + mockL2Syncer.On("GetClaims", mock.Anything, uint64(11), uint64(50)).Return([]bridgesync.Claim{}, nil).Once() + mockL2Syncer.On("GetExitRootByIndex", mock.Anything, uint32(1)).Return(treeTypes.Root{}, nil).Once() + mockL2Syncer.On("OriginNetwork").Return(uint32(1), nil).Once() + mockAggLayerClient.On("SendCertificate", mock.Anything).Return(common.Hash{}, nil).Once() + + signedCertificate, err := aggSender.sendCertificate(ctx) + require.NoError(t, err) + require.NotNil(t, signedCertificate) + require.NotNil(t, signedCertificate.Signature) + require.NotNil(t, signedCertificate.Certificate) + require.NotNil(t, signedCertificate.Certificate.ImportedBridgeExits) + require.Len(t, signedCertificate.Certificate.BridgeExits, 1) + + mockStorage.AssertExpectations(t) + mockL2Syncer.AssertExpectations(t) + mockAggLayerClient.AssertExpectations(t) + mockL1InfoTreeSyncer.AssertExpectations(t) +} 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 506b4e9a..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,27 @@ type Config struct { AggsenderPrivateKey types.KeystoreFileConfig `mapstructure:"AggsenderPrivateKey"` // URLRPCL2 is the URL of the L2 RPC node URLRPCL2 string `mapstructure:"URLRPCL2"` - // SaveCertificatesToFiles is a flag which tells the AggSender to save the certificates to a file - SaveCertificatesToFiles bool `mapstructure:"SaveCertificatesToFiles"` + // 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"` +} + +// String returns a string representation of the Config +func (c Config) String() string { + return "StoragePath: " + c.StoragePath + "\n" + + "AggLayerURL: " + c.AggLayerURL + "\n" + + "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" + + "SaveCertificatesToFilesPath: " + c.SaveCertificatesToFilesPath + "\n" } diff --git a/aggsender/db/aggsender_db_storage.go b/aggsender/db/aggsender_db_storage.go index 25b31392..15866c29 100644 --- a/aggsender/db/aggsender_db_storage.go +++ b/aggsender/db/aggsender_db_storage.go @@ -21,15 +21,15 @@ 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(ctx context.Context, height uint64) (types.CertificateInfo, error) + GetCertificateByHeight(height uint64) (types.CertificateInfo, error) // GetLastSentCertificate returns the last certificate sent to the aggLayer - GetLastSentCertificate(ctx context.Context) (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(ctx context.Context, status []agglayer.CertificateStatus) ([]*types.CertificateInfo, error) + GetCertificatesByStatus(status []agglayer.CertificateStatus) ([]*types.CertificateInfo, error) // UpdateCertificateStatus updates the status of a certificate UpdateCertificateStatus(ctx context.Context, certificate types.CertificateInfo) error } @@ -59,7 +59,7 @@ func NewAggSenderSQLStorage(logger *log.Logger, dbPath string) (*AggSenderSQLSto }, nil } -func (a *AggSenderSQLStorage) GetCertificatesByStatus(ctx context.Context, +func (a *AggSenderSQLStorage) GetCertificatesByStatus( statuses []agglayer.CertificateStatus) ([]*types.CertificateInfo, error) { query := "SELECT * FROM certificate_info" args := make([]interface{}, len(statuses)) @@ -88,10 +88,15 @@ func (a *AggSenderSQLStorage) GetCertificatesByStatus(ctx context.Context, } // GetCertificateByHeight returns a certificate by its height -func (a *AggSenderSQLStorage) GetCertificateByHeight(ctx context.Context, +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) { var certificateInfo types.CertificateInfo - if err := meddler.QueryRow(a.db, &certificateInfo, + if err := meddler.QueryRow(db, &certificateInfo, "SELECT * FROM certificate_info WHERE height = $1;", height); err != nil { return types.CertificateInfo{}, getSelectQueryError(height, err) } @@ -100,7 +105,7 @@ func (a *AggSenderSQLStorage) GetCertificateByHeight(ctx context.Context, } // GetLastSentCertificate returns the last certificate sent to the aggLayer -func (a *AggSenderSQLStorage) GetLastSentCertificate(ctx context.Context) (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 { @@ -124,10 +129,24 @@ func (a *AggSenderSQLStorage) SaveLastSentCertificate(ctx context.Context, certi } }() - if err := meddler.Insert(tx, "certificate_info", &certificate); err != nil { + cert, err := getCertificateByHeight(tx, certificate.Height) + if err != nil && !errors.Is(err, db.ErrNotFound) { + return err + } + + if cert.CertificateID != (common.Hash{}) { + // 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 = meddler.Insert(tx, "certificate_info", &certificate); err != nil { return fmt.Errorf("error inserting certificate info: %w", err) } - if err := tx.Commit(); err != nil { + + if err = tx.Commit(); err != nil { return err } @@ -150,10 +169,11 @@ func (a *AggSenderSQLStorage) DeleteCertificate(ctx context.Context, certificate } }() - if _, err := tx.Exec(`DELETE FROM certificate_info WHERE certificate_id = $1;`, certificateID); err != nil { - return fmt.Errorf("error deleting certificate info: %w", err) + if err = deleteCertificate(a.db, certificateID); err != nil { + return err } - if err := tx.Commit(); err != nil { + + if err = tx.Commit(); err != nil { return err } @@ -162,6 +182,15 @@ func (a *AggSenderSQLStorage) DeleteCertificate(ctx context.Context, certificate return nil } +// 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 { + return fmt.Errorf("error deleting certificate info: %w", err) + } + + return nil +} + // UpdateCertificateStatus updates the status of a certificate func (a *AggSenderSQLStorage) UpdateCertificateStatus(ctx context.Context, certificate types.CertificateInfo) error { tx, err := db.NewTx(ctx, a.db) @@ -176,11 +205,11 @@ func (a *AggSenderSQLStorage) UpdateCertificateStatus(ctx context.Context, certi } }() - if _, err := tx.Exec(`UPDATE certificate_info SET status = $1 WHERE certificate_id = $2;`, - certificate.Status, certificate.CertificateID); err != nil { + if _, err = tx.Exec(`UPDATE certificate_info SET status = $1 WHERE certificate_id = $2;`, + certificate.Status, certificate.CertificateID.String()); err != nil { return fmt.Errorf("error updating certificate info: %w", err) } - if err := tx.Commit(); err != nil { + if err = tx.Commit(); err != nil { return err } diff --git a/aggsender/db/aggsender_db_storage_test.go b/aggsender/db/aggsender_db_storage_test.go index cfb7af7c..a0a20894 100644 --- a/aggsender/db/aggsender_db_storage_test.go +++ b/aggsender/db/aggsender_db_storage_test.go @@ -2,8 +2,11 @@ package db import ( "context" + "encoding/json" + "math/big" "path" "testing" + "time" "github.com/0xPolygon/cdk/agglayer" "github.com/0xPolygon/cdk/aggsender/db/migrations" @@ -24,6 +27,8 @@ func Test_Storage(t *testing.T) { storage, err := NewAggSenderSQLStorage(log.WithFields("aggsender-db"), path) require.NoError(t, err) + updateTime := time.Now().UTC().UnixMilli() + t.Run("SaveLastSentCertificate", func(t *testing.T) { certificate := types.CertificateInfo{ Height: 1, @@ -32,10 +37,12 @@ func Test_Storage(t *testing.T) { FromBlock: 1, ToBlock: 2, Status: agglayer.Settled, + CreatedAt: updateTime, + UpdatedAt: updateTime, } require.NoError(t, storage.SaveLastSentCertificate(ctx, certificate)) - certificateFromDB, err := storage.GetCertificateByHeight(ctx, certificate.Height) + certificateFromDB, err := storage.GetCertificateByHeight(certificate.Height) require.NoError(t, err) require.Equal(t, certificate, certificateFromDB) @@ -50,12 +57,14 @@ func Test_Storage(t *testing.T) { FromBlock: 3, ToBlock: 4, Status: agglayer.Settled, + CreatedAt: updateTime, + UpdatedAt: updateTime, } require.NoError(t, storage.SaveLastSentCertificate(ctx, certificate)) require.NoError(t, storage.DeleteCertificate(ctx, certificate.CertificateID)) - certificateFromDB, err := storage.GetCertificateByHeight(ctx, certificate.Height) + certificateFromDB, err := storage.GetCertificateByHeight(certificate.Height) require.ErrorIs(t, err, db.ErrNotFound) require.Equal(t, types.CertificateInfo{}, certificateFromDB) require.NoError(t, storage.clean()) @@ -63,7 +72,7 @@ func Test_Storage(t *testing.T) { t.Run("GetLastSentCertificate", func(t *testing.T) { // try getting a certificate that doesn't exist - certificateFromDB, err := storage.GetLastSentCertificate(ctx) + certificateFromDB, err := storage.GetLastSentCertificate() require.NoError(t, err) require.Equal(t, types.CertificateInfo{}, certificateFromDB) @@ -75,10 +84,12 @@ func Test_Storage(t *testing.T) { FromBlock: 5, ToBlock: 6, Status: agglayer.Pending, + CreatedAt: updateTime, + UpdatedAt: updateTime, } require.NoError(t, storage.SaveLastSentCertificate(ctx, certificate)) - certificateFromDB, err = storage.GetLastSentCertificate(ctx) + certificateFromDB, err = storage.GetLastSentCertificate() require.NoError(t, err) require.Equal(t, certificate, certificateFromDB) @@ -87,12 +98,12 @@ func Test_Storage(t *testing.T) { t.Run("GetCertificateByHeight", func(t *testing.T) { // try getting height 0 - certificateFromDB, err := storage.GetCertificateByHeight(ctx, 0) + certificateFromDB, err := storage.GetCertificateByHeight(0) require.NoError(t, err) require.Equal(t, types.CertificateInfo{}, certificateFromDB) // try getting a certificate that doesn't exist - certificateFromDB, err = storage.GetCertificateByHeight(ctx, 4) + certificateFromDB, err = storage.GetCertificateByHeight(4) require.ErrorIs(t, err, db.ErrNotFound) require.Equal(t, types.CertificateInfo{}, certificateFromDB) @@ -104,10 +115,12 @@ func Test_Storage(t *testing.T) { FromBlock: 17, ToBlock: 18, Status: agglayer.Pending, + CreatedAt: updateTime, + UpdatedAt: updateTime, } require.NoError(t, storage.SaveLastSentCertificate(ctx, certificate)) - certificateFromDB, err = storage.GetCertificateByHeight(ctx, certificate.Height) + certificateFromDB, err = storage.GetCertificateByHeight(certificate.Height) require.NoError(t, err) require.Equal(t, certificate, certificateFromDB) @@ -124,6 +137,8 @@ func Test_Storage(t *testing.T) { FromBlock: 7, ToBlock: 8, Status: agglayer.Settled, + CreatedAt: updateTime, + UpdatedAt: updateTime, }, { Height: 9, @@ -132,6 +147,8 @@ func Test_Storage(t *testing.T) { FromBlock: 9, ToBlock: 10, Status: agglayer.Pending, + CreatedAt: updateTime, + UpdatedAt: updateTime, }, { Height: 11, @@ -140,6 +157,8 @@ func Test_Storage(t *testing.T) { FromBlock: 11, ToBlock: 12, Status: agglayer.InError, + CreatedAt: updateTime, + UpdatedAt: updateTime, }, } @@ -149,28 +168,28 @@ func Test_Storage(t *testing.T) { // Test fetching certificates with status Settled statuses := []agglayer.CertificateStatus{agglayer.Settled} - certificatesFromDB, err := storage.GetCertificatesByStatus(ctx, statuses) + certificatesFromDB, err := storage.GetCertificatesByStatus(statuses) require.NoError(t, err) require.Len(t, certificatesFromDB, 1) require.ElementsMatch(t, []*types.CertificateInfo{certificates[0]}, certificatesFromDB) // Test fetching certificates with status Pending statuses = []agglayer.CertificateStatus{agglayer.Pending} - certificatesFromDB, err = storage.GetCertificatesByStatus(ctx, statuses) + certificatesFromDB, err = storage.GetCertificatesByStatus(statuses) require.NoError(t, err) require.Len(t, certificatesFromDB, 1) require.ElementsMatch(t, []*types.CertificateInfo{certificates[1]}, certificatesFromDB) // Test fetching certificates with status InError statuses = []agglayer.CertificateStatus{agglayer.InError} - certificatesFromDB, err = storage.GetCertificatesByStatus(ctx, statuses) + certificatesFromDB, err = storage.GetCertificatesByStatus(statuses) require.NoError(t, err) require.Len(t, certificatesFromDB, 1) require.ElementsMatch(t, []*types.CertificateInfo{certificates[2]}, certificatesFromDB) // Test fetching certificates with status InError and Pending statuses = []agglayer.CertificateStatus{agglayer.InError, agglayer.Pending} - certificatesFromDB, err = storage.GetCertificatesByStatus(ctx, statuses) + certificatesFromDB, err = storage.GetCertificatesByStatus(statuses) require.NoError(t, err) require.Len(t, certificatesFromDB, 2) require.ElementsMatch(t, []*types.CertificateInfo{certificates[1], certificates[2]}, certificatesFromDB) @@ -187,6 +206,8 @@ func Test_Storage(t *testing.T) { FromBlock: 13, ToBlock: 14, Status: agglayer.Pending, + CreatedAt: updateTime, + UpdatedAt: updateTime, } require.NoError(t, storage.SaveLastSentCertificate(ctx, certificate)) @@ -195,10 +216,155 @@ func Test_Storage(t *testing.T) { require.NoError(t, storage.UpdateCertificateStatus(ctx, certificate)) // Fetch the certificate and verify the status has been updated - certificateFromDB, err := storage.GetCertificateByHeight(ctx, certificate.Height) + certificateFromDB, err := storage.GetCertificateByHeight(certificate.Height) require.NoError(t, err) require.Equal(t, certificate.Status, certificateFromDB.Status) require.NoError(t, storage.clean()) }) } + +func Test_SaveLastSentCertificate(t *testing.T) { + ctx := context.Background() + + path := path.Join(t.TempDir(), "file::memory:?cache=shared") + log.Debugf("sqlite path: %s", path) + require.NoError(t, migrations.RunMigrations(path)) + + storage, err := NewAggSenderSQLStorage(log.WithFields("aggsender-db"), path) + require.NoError(t, err) + + updateTime := time.Now().UTC().UnixMilli() + + t.Run("SaveNewCertificate", func(t *testing.T) { + certificate := types.CertificateInfo{ + Height: 1, + CertificateID: common.HexToHash("0x1"), + NewLocalExitRoot: common.HexToHash("0x2"), + FromBlock: 1, + ToBlock: 2, + Status: agglayer.Settled, + CreatedAt: updateTime, + UpdatedAt: updateTime, + } + require.NoError(t, storage.SaveLastSentCertificate(ctx, certificate)) + + certificateFromDB, err := storage.GetCertificateByHeight(certificate.Height) + require.NoError(t, err) + require.Equal(t, certificate, certificateFromDB) + require.NoError(t, storage.clean()) + }) + + t.Run("UpdateExistingCertificate", func(t *testing.T) { + certificate := types.CertificateInfo{ + Height: 2, + CertificateID: common.HexToHash("0x3"), + NewLocalExitRoot: common.HexToHash("0x4"), + FromBlock: 3, + ToBlock: 4, + Status: agglayer.InError, + CreatedAt: updateTime, + UpdatedAt: updateTime, + } + require.NoError(t, storage.SaveLastSentCertificate(ctx, certificate)) + + // Update the certificate with the same height + updatedCertificate := types.CertificateInfo{ + Height: 2, + CertificateID: common.HexToHash("0x5"), + NewLocalExitRoot: common.HexToHash("0x6"), + FromBlock: 3, + ToBlock: 6, + Status: agglayer.Pending, + } + require.NoError(t, storage.SaveLastSentCertificate(ctx, updatedCertificate)) + + certificateFromDB, err := storage.GetCertificateByHeight(updatedCertificate.Height) + require.NoError(t, err) + require.Equal(t, updatedCertificate, certificateFromDB) + require.NoError(t, storage.clean()) + }) + + t.Run("SaveCertificateWithRollback", func(t *testing.T) { + // Simulate an error during the transaction to trigger a rollback + certificate := types.CertificateInfo{ + Height: 3, + CertificateID: common.HexToHash("0x7"), + NewLocalExitRoot: common.HexToHash("0x8"), + FromBlock: 7, + ToBlock: 8, + Status: agglayer.Settled, + CreatedAt: updateTime, + UpdatedAt: updateTime, + } + + // Close the database to force an error + require.NoError(t, storage.db.Close()) + + err := storage.SaveLastSentCertificate(ctx, certificate) + require.Error(t, err) + + // Reopen the database and check that the certificate was not saved + storage.db, err = db.NewSQLiteDB(path) + require.NoError(t, err) + + certificateFromDB, err := storage.GetCertificateByHeight(certificate.Height) + require.ErrorIs(t, err, db.ErrNotFound) + require.Equal(t, types.CertificateInfo{}, certificateFromDB) + require.NoError(t, storage.clean()) + }) + + t.Run("SaveCertificate with raw data", func(t *testing.T) { + certfiicate := &agglayer.SignedCertificate{ + Certificate: &agglayer.Certificate{ + NetworkID: 1, + Height: 1, + PrevLocalExitRoot: common.HexToHash("0x1"), + NewLocalExitRoot: common.HexToHash("0x2"), + Metadata: common.HexToHash("0x3"), + BridgeExits: []*agglayer.BridgeExit{ + { + LeafType: agglayer.LeafTypeAsset, + TokenInfo: &agglayer.TokenInfo{ + OriginNetwork: 1, + OriginTokenAddress: common.HexToAddress("0x1"), + }, + DestinationNetwork: 2, + DestinationAddress: common.HexToAddress("0x2"), + Amount: big.NewInt(100), + Metadata: []byte("metadata"), + }, + }, + ImportedBridgeExits: []*agglayer.ImportedBridgeExit{}, + }, + Signature: &agglayer.Signature{ + R: common.HexToHash("0x4"), + S: common.HexToHash("0x5"), + OddParity: false, + }, + } + + raw, err := json.Marshal(certfiicate) + require.NoError(t, err) + + certificate := types.CertificateInfo{ + Height: 1, + CertificateID: common.HexToHash("0x9"), + NewLocalExitRoot: common.HexToHash("0x2"), + FromBlock: 1, + ToBlock: 10, + Status: agglayer.Pending, + CreatedAt: updateTime, + UpdatedAt: updateTime, + SignedCertificate: string(raw), + } + require.NoError(t, storage.SaveLastSentCertificate(ctx, certificate)) + + certificateFromDB, err := storage.GetCertificateByHeight(certificate.Height) + require.NoError(t, err) + require.Equal(t, certificate, certificateFromDB) + require.Equal(t, raw, []byte(certificateFromDB.SignedCertificate)) + + require.NoError(t, storage.clean()) + }) +} diff --git a/aggsender/db/migrations/0001.sql b/aggsender/db/migrations/0001.sql index 3ed7f997..b2d600b8 100644 --- a/aggsender/db/migrations/0001.sql +++ b/aggsender/db/migrations/0001.sql @@ -8,5 +8,8 @@ CREATE TABLE certificate_info ( status INTEGER NOT NULL, new_local_exit_root VARCHAR NOT NULL, from_block INTEGER NOT NULL, - to_block INTEGER NOT NULL + to_block INTEGER NOT NULL, + created_at INTEGER NOT NULL, + updated_at INTEGER NOT NULL, + signed_certificate TEXT ); \ No newline at end of file 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 a5f193fc..00000000 --- a/aggsender/mocks/mock_aggsender_storage.go +++ /dev/null @@ -1,354 +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: ctx, height -func (_m *AggSenderStorageMock) GetCertificateByHeight(ctx context.Context, height uint64) (types.CertificateInfo, error) { - ret := _m.Called(ctx, 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(context.Context, uint64) (types.CertificateInfo, error)); ok { - return rf(ctx, height) - } - if rf, ok := ret.Get(0).(func(context.Context, uint64) types.CertificateInfo); ok { - r0 = rf(ctx, height) - } else { - r0 = ret.Get(0).(types.CertificateInfo) - } - - if rf, ok := ret.Get(1).(func(context.Context, uint64) error); ok { - r1 = rf(ctx, 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 -// - ctx context.Context -// - height uint64 -func (_e *AggSenderStorageMock_Expecter) GetCertificateByHeight(ctx interface{}, height interface{}) *AggSenderStorageMock_GetCertificateByHeight_Call { - return &AggSenderStorageMock_GetCertificateByHeight_Call{Call: _e.mock.On("GetCertificateByHeight", ctx, height)} -} - -func (_c *AggSenderStorageMock_GetCertificateByHeight_Call) Run(run func(ctx context.Context, height uint64)) *AggSenderStorageMock_GetCertificateByHeight_Call { - _c.Call.Run(func(args mock.Arguments) { - run(args[0].(context.Context), args[1].(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(context.Context, uint64) (types.CertificateInfo, error)) *AggSenderStorageMock_GetCertificateByHeight_Call { - _c.Call.Return(run) - return _c -} - -// GetCertificatesByStatus provides a mock function with given fields: ctx, status -func (_m *AggSenderStorageMock) GetCertificatesByStatus(ctx context.Context, status []agglayer.CertificateStatus) ([]*types.CertificateInfo, error) { - ret := _m.Called(ctx, 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(context.Context, []agglayer.CertificateStatus) ([]*types.CertificateInfo, error)); ok { - return rf(ctx, status) - } - if rf, ok := ret.Get(0).(func(context.Context, []agglayer.CertificateStatus) []*types.CertificateInfo); ok { - r0 = rf(ctx, status) - } else { - if ret.Get(0) != nil { - r0 = ret.Get(0).([]*types.CertificateInfo) - } - } - - if rf, ok := ret.Get(1).(func(context.Context, []agglayer.CertificateStatus) error); ok { - r1 = rf(ctx, 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 -// - ctx context.Context -// - status []agglayer.CertificateStatus -func (_e *AggSenderStorageMock_Expecter) GetCertificatesByStatus(ctx interface{}, status interface{}) *AggSenderStorageMock_GetCertificatesByStatus_Call { - return &AggSenderStorageMock_GetCertificatesByStatus_Call{Call: _e.mock.On("GetCertificatesByStatus", ctx, status)} -} - -func (_c *AggSenderStorageMock_GetCertificatesByStatus_Call) Run(run func(ctx context.Context, status []agglayer.CertificateStatus)) *AggSenderStorageMock_GetCertificatesByStatus_Call { - _c.Call.Run(func(args mock.Arguments) { - run(args[0].(context.Context), args[1].([]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(context.Context, []agglayer.CertificateStatus) ([]*types.CertificateInfo, error)) *AggSenderStorageMock_GetCertificatesByStatus_Call { - _c.Call.Return(run) - return _c -} - -// GetLastSentCertificate provides a mock function with given fields: ctx -func (_m *AggSenderStorageMock) GetLastSentCertificate(ctx context.Context) (types.CertificateInfo, error) { - ret := _m.Called(ctx) - - 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(context.Context) (types.CertificateInfo, error)); ok { - return rf(ctx) - } - if rf, ok := ret.Get(0).(func(context.Context) types.CertificateInfo); ok { - r0 = rf(ctx) - } else { - r0 = ret.Get(0).(types.CertificateInfo) - } - - if rf, ok := ret.Get(1).(func(context.Context) error); ok { - r1 = rf(ctx) - } 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 -// - ctx context.Context -func (_e *AggSenderStorageMock_Expecter) GetLastSentCertificate(ctx interface{}) *AggSenderStorageMock_GetLastSentCertificate_Call { - return &AggSenderStorageMock_GetLastSentCertificate_Call{Call: _e.mock.On("GetLastSentCertificate", ctx)} -} - -func (_c *AggSenderStorageMock_GetLastSentCertificate_Call) Run(run func(ctx context.Context)) *AggSenderStorageMock_GetLastSentCertificate_Call { - _c.Call.Run(func(args mock.Arguments) { - run(args[0].(context.Context)) - }) - 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(context.Context) (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..045ba7ff --- /dev/null +++ b/aggsender/types/epoch_notifier.go @@ -0,0 +1,28 @@ +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 +} + +type BridgeL2Syncer interface { +} 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 d6421132..d9e0b2e7 100644 --- a/aggsender/types/types.go +++ b/aggsender/types/types.go @@ -4,6 +4,7 @@ import ( "context" "fmt" "math/big" + "time" "github.com/0xPolygon/cdk/agglayer" "github.com/0xPolygon/cdk/bridgesync" @@ -46,20 +47,41 @@ 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{}) } type CertificateInfo struct { - Height uint64 `meddler:"height"` - CertificateID common.Hash `meddler:"certificate_id"` - NewLocalExitRoot common.Hash `meddler:"new_local_exit_root"` - FromBlock uint64 `meddler:"from_block"` - ToBlock uint64 `meddler:"to_block"` - Status agglayer.CertificateStatus `meddler:"status"` + 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"` } func (c CertificateInfo) String() string { - return fmt.Sprintf("Height: %d, CertificateID: %s, FromBlock: %d, ToBlock: %d, NewLocalExitRoot: %s", - c.Height, c.CertificateID.String(), c.FromBlock, c.ToBlock, c.NewLocalExitRoot.String()) + 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", + c.Height, + c.CertificateID.String(), + c.FromBlock, + c.ToBlock, + c.NewLocalExitRoot.String(), + c.Status.String(), + time.UnixMilli(c.CreatedAt), + time.UnixMilli(c.UpdatedAt), + ) } diff --git a/bridgesync/claimcalldata_test.go b/bridgesync/claimcalldata_test.go index a4ab49de..ef2d60bd 100644 --- a/bridgesync/claimcalldata_test.go +++ b/bridgesync/claimcalldata_test.go @@ -127,6 +127,7 @@ func TestClaimCalldata(t *testing.T) { require.NoError(t, err) time.Sleep(1 * time.Second) r, err := client.TransactionReceipt(ctx, tx.Hash()) + require.NoError(t, err) testCases = append(testCases, testCase{ description: "direct call to claim asset", bridgeAddr: bridgeAddr, @@ -155,6 +156,7 @@ func TestClaimCalldata(t *testing.T) { require.NoError(t, err) time.Sleep(1 * time.Second) r, err = client.TransactionReceipt(ctx, tx.Hash()) + require.NoError(t, err) testCases = append(testCases, testCase{ description: "indirect call to claim asset", bridgeAddr: bridgeAddr, @@ -188,6 +190,7 @@ func TestClaimCalldata(t *testing.T) { require.NoError(t, err) time.Sleep(1 * time.Second) r, err = client.TransactionReceipt(ctx, tx.Hash()) + require.NoError(t, err) testCases = append(testCases, testCase{ description: "indirect call to claim asset bytes", bridgeAddr: bridgeAddr, @@ -215,6 +218,7 @@ func TestClaimCalldata(t *testing.T) { require.NoError(t, err) time.Sleep(1 * time.Second) r, err = client.TransactionReceipt(ctx, tx.Hash()) + require.NoError(t, err) testCases = append(testCases, testCase{ description: "direct call to claim message", bridgeAddr: bridgeAddr, @@ -243,6 +247,7 @@ func TestClaimCalldata(t *testing.T) { require.NoError(t, err) time.Sleep(1 * time.Second) r, err = client.TransactionReceipt(ctx, tx.Hash()) + require.NoError(t, err) testCases = append(testCases, testCase{ description: "indirect call to claim message", bridgeAddr: bridgeAddr, @@ -276,6 +281,7 @@ func TestClaimCalldata(t *testing.T) { require.NoError(t, err) time.Sleep(1 * time.Second) r, err = client.TransactionReceipt(ctx, tx.Hash()) + require.NoError(t, err) testCases = append(testCases, testCase{ description: "indirect call to claim message bytes", bridgeAddr: bridgeAddr, @@ -309,6 +315,7 @@ func TestClaimCalldata(t *testing.T) { require.NoError(t, err) time.Sleep(1 * time.Second) r, err = client.TransactionReceipt(ctx, tx.Hash()) + require.NoError(t, err) log.Infof("%+v", r.Logs) reverted := [2]bool{false, false} @@ -357,6 +364,7 @@ func TestClaimCalldata(t *testing.T) { require.NoError(t, err) time.Sleep(1 * time.Second) r, err = client.TransactionReceipt(ctx, tx.Hash()) + require.NoError(t, err) testCases = append(testCases, testCase{ description: "2 indirect call claim message 1 (same globalIndex)", bridgeAddr: bridgeAddr, @@ -414,6 +422,7 @@ func TestClaimCalldata(t *testing.T) { require.NoError(t, err) time.Sleep(1 * time.Second) r, err = client.TransactionReceipt(ctx, tx.Hash()) + require.NoError(t, err) testCases = append(testCases, testCase{ description: "2 indirect call claim message 1 (diff globalIndex)", bridgeAddr: bridgeAddr, @@ -473,6 +482,7 @@ func TestClaimCalldata(t *testing.T) { require.NoError(t, err) time.Sleep(1 * time.Second) r, err = client.TransactionReceipt(ctx, tx.Hash()) + require.NoError(t, err) testCases = append(testCases, testCase{ description: "2 indirect call claim message (same globalIndex) (1 ok, 1 reverted)", bridgeAddr: bridgeAddr, @@ -524,6 +534,7 @@ func TestClaimCalldata(t *testing.T) { require.NoError(t, err) time.Sleep(1 * time.Second) r, err = client.TransactionReceipt(ctx, tx.Hash()) + require.NoError(t, err) testCases = append(testCases, testCase{ description: "2 indirect call claim message (diff globalIndex) (1 ok, 1 reverted)", bridgeAddr: bridgeAddr, @@ -577,6 +588,7 @@ func TestClaimCalldata(t *testing.T) { require.NoError(t, err) time.Sleep(1 * time.Second) r, err = client.TransactionReceipt(ctx, tx.Hash()) + require.NoError(t, err) testCases = append(testCases, testCase{ description: "2 indirect call claim message (same globalIndex) (reverted,ok)", bridgeAddr: bridgeAddr, @@ -628,6 +640,7 @@ func TestClaimCalldata(t *testing.T) { require.NoError(t, err) time.Sleep(1 * time.Second) r, err = client.TransactionReceipt(ctx, tx.Hash()) + require.NoError(t, err) testCases = append(testCases, testCase{ description: "2 indirect call claim message (diff globalIndex) (reverted,ok)", bridgeAddr: bridgeAddr, @@ -681,6 +694,7 @@ func TestClaimCalldata(t *testing.T) { require.NoError(t, err) time.Sleep(1 * time.Second) r, err = client.TransactionReceipt(ctx, tx.Hash()) + require.NoError(t, err) testCases = append(testCases, testCase{ description: "2 indirect call claim asset 1 (same globalIndex)", bridgeAddr: bridgeAddr, @@ -738,6 +752,7 @@ func TestClaimCalldata(t *testing.T) { require.NoError(t, err) time.Sleep(1 * time.Second) r, err = client.TransactionReceipt(ctx, tx.Hash()) + require.NoError(t, err) testCases = append(testCases, testCase{ description: "2 indirect call claim asset 1 (diff globalIndex)", bridgeAddr: bridgeAddr, @@ -797,6 +812,7 @@ func TestClaimCalldata(t *testing.T) { require.NoError(t, err) time.Sleep(1 * time.Second) r, err = client.TransactionReceipt(ctx, tx.Hash()) + require.NoError(t, err) testCases = append(testCases, testCase{ description: "2 indirect call claim asset (same globalIndex) (1 ok, 1 reverted)", bridgeAddr: bridgeAddr, @@ -848,6 +864,7 @@ func TestClaimCalldata(t *testing.T) { require.NoError(t, err) time.Sleep(1 * time.Second) r, err = client.TransactionReceipt(ctx, tx.Hash()) + require.NoError(t, err) testCases = append(testCases, testCase{ description: "2 indirect call claim asset (diff globalIndex) (1 ok, 1 reverted)", bridgeAddr: bridgeAddr, @@ -901,6 +918,7 @@ func TestClaimCalldata(t *testing.T) { require.NoError(t, err) time.Sleep(1 * time.Second) r, err = client.TransactionReceipt(ctx, tx.Hash()) + require.NoError(t, err) testCases = append(testCases, testCase{ description: "2 indirect call claim asset (same globalIndex) (reverted,ok)", bridgeAddr: bridgeAddr, @@ -952,6 +970,7 @@ func TestClaimCalldata(t *testing.T) { require.NoError(t, err) time.Sleep(1 * time.Second) r, err = client.TransactionReceipt(ctx, tx.Hash()) + require.NoError(t, err) testCases = append(testCases, testCase{ description: "2 indirect call claim asset (diff globalIndex) (reverted,ok)", bridgeAddr: bridgeAddr, @@ -985,6 +1004,7 @@ func TestClaimCalldata(t *testing.T) { require.NoError(t, err) time.Sleep(1 * time.Second) r, err = client.TransactionReceipt(ctx, tx.Hash()) + require.NoError(t, err) testCases = append(testCases, testCase{ description: "indirect + indirect call to claim message bytes", bridgeAddr: bridgeAddr, @@ -1038,6 +1058,7 @@ func TestClaimCalldata(t *testing.T) { require.NoError(t, err) time.Sleep(1 * time.Second) r, err = client.TransactionReceipt(ctx, tx.Hash()) + require.NoError(t, err) testCases = append(testCases, testCase{ description: "2 indirect + indirect call claim message 1 (same globalIndex)", bridgeAddr: bridgeAddr, @@ -1115,6 +1136,7 @@ func TestClaimCalldata(t *testing.T) { require.NoError(t, err) time.Sleep(1 * time.Second) r, err = client.TransactionReceipt(ctx, tx.Hash()) + require.NoError(t, err) testCases = append(testCases, testCase{ description: "3 ok (indirectx2, indirect, indirectx2) call claim message 1 (same globalIndex)", bridgeAddr: bridgeAddr, @@ -1196,6 +1218,7 @@ func TestClaimCalldata(t *testing.T) { require.NoError(t, err) time.Sleep(1 * time.Second) r, err = client.TransactionReceipt(ctx, tx.Hash()) + require.NoError(t, err) testCases = append(testCases, testCase{ description: "3 ok (indirectx2, indirect, indirectx2) call claim message 1 (diff globalIndex)", bridgeAddr: bridgeAddr, @@ -1279,6 +1302,7 @@ func TestClaimCalldata(t *testing.T) { require.NoError(t, err) time.Sleep(1 * time.Second) r, err = client.TransactionReceipt(ctx, tx.Hash()) + require.NoError(t, err) testCases = append(testCases, testCase{ description: "1 ko 2 ok (indirectx2, indirect, indirectx2) call claim message 1 (diff globalIndex)", bridgeAddr: bridgeAddr, @@ -1356,6 +1380,7 @@ func TestClaimCalldata(t *testing.T) { require.NoError(t, err) time.Sleep(1 * time.Second) r, err = client.TransactionReceipt(ctx, tx.Hash()) + require.NoError(t, err) testCases = append(testCases, testCase{ description: "1 ko 2 ok (indirectx2, indirect, indirectx2) call claim message 1 (diff globalIndex)", bridgeAddr: bridgeAddr, @@ -1433,6 +1458,7 @@ func TestClaimCalldata(t *testing.T) { require.NoError(t, err) time.Sleep(1 * time.Second) r, err = client.TransactionReceipt(ctx, tx.Hash()) + require.NoError(t, err) testCases = append(testCases, testCase{ description: "1 ok 1 ok 1 ko (indirectx2, indirect, indirectx2) call claim message 1 (diff globalIndex)", bridgeAddr: bridgeAddr, @@ -1510,6 +1536,7 @@ func TestClaimCalldata(t *testing.T) { require.NoError(t, err) time.Sleep(1 * time.Second) r, err = client.TransactionReceipt(ctx, tx.Hash()) + require.NoError(t, err) testCases = append(testCases, testCase{ description: "1 ko 2 ok (indirectx2, indirect, indirectx2) call claim message 1 (same globalIndex)", bridgeAddr: bridgeAddr, @@ -1587,6 +1614,7 @@ func TestClaimCalldata(t *testing.T) { require.NoError(t, err) time.Sleep(1 * time.Second) r, err = client.TransactionReceipt(ctx, tx.Hash()) + require.NoError(t, err) testCases = append(testCases, testCase{ description: "1 ko 2 ok (indirectx2, indirect, indirectx2) call claim message 1 (same globalIndex)", bridgeAddr: bridgeAddr, @@ -1664,6 +1692,7 @@ func TestClaimCalldata(t *testing.T) { require.NoError(t, err) time.Sleep(1 * time.Second) r, err = client.TransactionReceipt(ctx, tx.Hash()) + require.NoError(t, err) testCases = append(testCases, testCase{ description: "1 ok 1 ok 1 ko (indirectx2, indirect, indirectx2) call claim message 1 (same globalIndex)", bridgeAddr: bridgeAddr, @@ -1741,6 +1770,7 @@ func TestClaimCalldata(t *testing.T) { require.NoError(t, err) time.Sleep(1 * time.Second) r, err = client.TransactionReceipt(ctx, tx.Hash()) + require.NoError(t, err) testCases = append(testCases, testCase{ description: "2 ko 1 ok (indirectx2, indirect, indirectx2) call claim message 1 (same globalIndex)", bridgeAddr: bridgeAddr, @@ -1812,6 +1842,7 @@ func TestClaimCalldata(t *testing.T) { require.NoError(t, err) time.Sleep(1 * time.Second) r, err = client.TransactionReceipt(ctx, tx.Hash()) + require.NoError(t, err) testCases = append(testCases, testCase{ description: "1 ok 2 ko (indirectx2, indirect, indirectx2) call claim message 1 (same globalIndex)", bridgeAddr: bridgeAddr, @@ -1883,6 +1914,7 @@ func TestClaimCalldata(t *testing.T) { require.NoError(t, err) time.Sleep(1 * time.Second) r, err = client.TransactionReceipt(ctx, tx.Hash()) + require.NoError(t, err) testCases = append(testCases, testCase{ description: "1 ko 1 ok 1 ko (indirectx2, indirect, indirectx2) call claim message 1 (same globalIndex)", bridgeAddr: bridgeAddr, diff --git a/bridgesync/e2e_test.go b/bridgesync/e2e_test.go index a8868ce1..6f1e10c4 100644 --- a/bridgesync/e2e_test.go +++ b/bridgesync/e2e_test.go @@ -20,7 +20,7 @@ import ( func TestBridgeEventE2E(t *testing.T) { ctx := context.Background() dbPathSyncer := path.Join(t.TempDir(), "file::memory:?cache=shared") - dbPathReorg := t.TempDir() + dbPathReorg := path.Join(t.TempDir(), "file::memory:?cache=shared") client, setup := helpers.SimulatedBackend(t, nil, 0) rd, err := reorgdetector.New(client.Client(), reorgdetector.Config{DBPath: dbPathReorg}) diff --git a/bridgesync/processor.go b/bridgesync/processor.go index e8a79c1f..b2e0ed24 100644 --- a/bridgesync/processor.go +++ b/bridgesync/processor.go @@ -269,8 +269,9 @@ func (p *processor) ProcessBlock(ctx context.Context, block sync.Block) error { if err != nil { return err } + shouldRollback := true defer func() { - if err != nil { + if shouldRollback { if errRllbck := tx.Rollback(); errRllbck != nil { log.Errorf("error while rolling back tx %v", errRllbck) } @@ -306,9 +307,9 @@ func (p *processor) ProcessBlock(ctx context.Context, block sync.Block) error { if err := tx.Commit(); err != nil { return err } + shouldRollback = false p.log.Debugf("processed %d events until block %d", len(block.Events), block.Num) - return nil } diff --git a/bridgesync/test_db_path b/bridgesync/test_db_path new file mode 100644 index 00000000..0de02ecf Binary files /dev/null and b/bridgesync/test_db_path differ diff --git a/bridgesync/test_db_path-shm b/bridgesync/test_db_path-shm new file mode 100644 index 00000000..cda70d28 Binary files /dev/null and b/bridgesync/test_db_path-shm differ diff --git a/bridgesync/test_db_path-wal b/bridgesync/test_db_path-wal new file mode 100644 index 00000000..7164c148 Binary files /dev/null and b/bridgesync/test_db_path-wal differ 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 096d98de..61b099c8 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 @@ -206,16 +207,16 @@ SyncModeOnlyEnabled = false NumRequests = 1000 Interval = "1s" [ReorgDetectorL1] -DBPath = "{{PathRWData}}/reorgdetectorl1" +DBPath = "{{PathRWData}}/reorgdetectorl1.sqlite" [ReorgDetectorL2] -DBPath = "{{PathRWData}}/reorgdetectorl2" +DBPath = "{{PathRWData}}/reorgdetectorl2.sqlite" [L1InfoTreeSync] DBPath = "{{PathRWData}}/L1InfoTreeSync.sqlite" GlobalExitRootAddr="{{NetworkConfig.L1.GlobalExitRootManagerAddr}}" RollupManagerAddr = "{{NetworkConfig.L1.RollupManagerAddr}}" -SyncBlockChunkSize=10 +SyncBlockChunkSize=100 BlockFinality="LatestBlock" URLRPCL1="{{L1URL}}" WaitForNewBlocksPeriod="100ms" @@ -316,7 +317,6 @@ WaitForNewBlocksPeriod = "3s" OriginNetwork=1 [LastGERSync] -# MDBX database path DBPath = "{{PathRWData}}/lastgersync.sqlite" BlockFinality = "LatestBlock" InitialBlockNum = 0 @@ -341,5 +341,7 @@ AggsenderPrivateKey = {Path = "{{SequencerPrivateKeyPath}}", Password = "{{Seque BlockGetInterval = "2s" URLRPCL2="{{L2URL}}" CheckSettledInterval = "2s" -SaveCertificatesToFiles = false +BlockFinality = "LatestBlock" +EpochNotificationPercentage = 50 +SaveCertificatesToFilesPath = "" ` diff --git a/crates/cdk/Cargo.toml b/crates/cdk/Cargo.toml index 0c1f8274..e6e9723b 100644 --- a/crates/cdk/Cargo.toml +++ b/crates/cdk/Cargo.toml @@ -15,14 +15,16 @@ tracing.workspace = true tracing-subscriber = { workspace = true, features = ["env-filter", "json"] } url = { workspace = true, features = ["serde"] } colored = "2.0" - - cdk-config = { path = "../cdk-config" } serde.workspace = true serde_json.workspace = true tempfile = "3.12.0" -alloy-rpc-client = "0.4.2" -alloy-transport-http = "0.4.2" +alloy-rpc-client = "0.5.4" +alloy-transport-http = "0.5.4" tokio = "1.40.0" -reqwest = "0.12.8" -alloy-json-rpc = "0.4.2" +alloy-json-rpc = "0.5.4" + +[build-dependencies] +reqwest = {version = "0.12.8", features = ["blocking"]} +serde_json.workspace = true +regex = "1.11.1" diff --git a/crates/cdk/build.rs b/crates/cdk/build.rs index 59fffda7..cceff95c 100644 --- a/crates/cdk/build.rs +++ b/crates/cdk/build.rs @@ -1,8 +1,15 @@ +use regex::Regex; +use reqwest::blocking::get; use std::env; +use std::fs::File; +use std::io::Write; +use std::path::Path; use std::path::PathBuf; use std::process::Command; fn main() { + let _ = build_versions(); + let build_script_disabled = env::var("BUILD_SCRIPT_DISABLED") .map(|v| v == "1") .unwrap_or(false); // run by default @@ -46,3 +53,50 @@ fn main() { // only when a specific file changes: // println!("cargo:rerun-if-changed=path/to/file"); } + +// build_versions retrieves the versions from the Starlark file and embeds them in the binary. +fn build_versions() -> std::io::Result<()> { + // Retrieve the contents of the file from the URL + let url = "https://raw.githubusercontent.com/0xPolygon/kurtosis-cdk/refs/heads/main/input_parser.star"; + let response = get(url).expect("Failed to send request"); + let content = response.text().expect("Failed to read response text"); + + // Write the contents to a file + let out_dir = std::env::var("OUT_DIR").unwrap(); + let dest_path = Path::new(&out_dir).join("input_parser.star"); + let mut file = File::create(&dest_path)?; + file.write_all(content.as_bytes())?; + + // Get the corresponding lines from the contents of the starlark file + let versions = content + .lines() + .skip(30) + .take(15) + .collect::>() + .join("\n"); + + // Replace the string DEFAULT_IMAGES = from the versions string + let versions = versions.replace("DEFAULT_IMAGES = ", ""); + + // Remove all comments to the end of the line using a regexp + let re = Regex::new(r"\s#\s.*\n").unwrap(); + let versions = re.replace_all(&versions, ""); + // Replace the trailing comma on the last line + let versions = versions.replace(", }", " }"); + + // The versions string is a JSON object we can parse + let versions_json: serde_json::Value = serde_json::from_str(&versions).unwrap(); + + // Write the versions to a file + let dest_path = Path::new(".").join("versions.json"); + let mut file = File::create(&dest_path)?; + file.write_all( + format!( + "{}\n", + serde_json::to_string_pretty(&versions_json).unwrap() + ) + .as_bytes(), + )?; + + Ok(()) +} diff --git a/crates/cdk/src/versions.rs b/crates/cdk/src/versions.rs index 77581452..3b148787 100644 --- a/crates/cdk/src/versions.rs +++ b/crates/cdk/src/versions.rs @@ -14,34 +14,26 @@ fn version() -> Result { } pub(crate) fn versions() { + // Load the versions from the versions.json file in the crate directory + // and parse it using serde_json. + let versions = include_str!("../versions.json"); + let versions_json: serde_json::Value = serde_json::from_str(versions).unwrap(); + + // Convert the JSON object to a HashMap. + let versions_map = versions_json.as_object().unwrap(); + // Get the version of the cdk-node binary. let output = version().unwrap(); let version = String::from_utf8(output.stdout).unwrap(); println!("{}", format!("{}", version.trim()).green()); - let versions = vec![ - ( - "zkEVM Contracts", - "https://github.com/0xPolygonHermez/zkevm-contracts/releases/tag/v8.0.0-rc.4-fork.12", - ), - ("zkEVM Prover", "v8.0.0-RC12"), - ("CDK Erigon", "hermeznetwork/cdk-erigon:0948e33"), - ( - "zkEVM Pool Manager", - "hermeznetwork/zkevm-pool-manager:v0.1.1", - ), - ( - "CDK Data Availability Node", - "0xpolygon/cdk-data-availability:0.0.10", - ), - ]; - // Multi-line string to print the versions with colors. - let formatted_versions: Vec = versions + let formatted_versions: Vec = versions_map .iter() - .map(|(key, value)| format!("{}: {}", key.green(), value.blue())) + .map(|(key, value)| format!("{}: {}", key.green(), value.to_string().blue())) .collect(); + println!("{}", "Supported up to fork12".yellow()); println!("{}", formatted_versions.join("\n")); } diff --git a/crates/cdk/versions.json b/crates/cdk/versions.json new file mode 100644 index 00000000..bafbd00b --- /dev/null +++ b/crates/cdk/versions.json @@ -0,0 +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_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_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_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/dataavailability/datacommittee/datacommittee.go b/dataavailability/datacommittee/datacommittee.go index 01b96a13..369fc0fe 100644 --- a/dataavailability/datacommittee/datacommittee.go +++ b/dataavailability/datacommittee/datacommittee.go @@ -105,61 +105,43 @@ 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) { - // TODO: optimize this on the DAC side by implementing a multi batch retrieve api) - batchData := make([][]byte, 0, len(hashes)) - for _, h := range hashes { - data, err := d.GetBatchL2Data(h) - if err != nil { - return nil, err - } - batchData = append(batchData, data) - } + initialMember := d.selectedCommitteeMember - return batchData, nil -} - -// GetBatchL2Data returns the data from the DAC. It checks that it matches with the expected hash -func (d *Backend) GetBatchL2Data(hash common.Hash) ([]byte, error) { - intialMember := d.selectedCommitteeMember - found := false - 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) - data, err := c.GetOffChainData(d.ctx, hash) + 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 } - actualTransactionsHash := crypto.Keccak256Hash(data) - 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, - ) - d.selectedCommitteeMember = (d.selectedCommitteeMember + 1) % len(d.committeeMembers) - if d.selectedCommitteeMember == intialMember { - break - } - continue + 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) + d.selectedCommitteeMember = (d.selectedCommitteeMember + 1) % len(d.committeeMembers) + if d.selectedCommitteeMember == initialMember { + break + } + continue + } + batchData = append(batchData, dataMap[hash]) } - - return data, nil + return batchData, nil } + if err := d.Init(); err != nil { return nil, fmt.Errorf("error loading data committee: %w", err) } diff --git a/go.mod b/go.mod index 4a3a983e..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 @@ -19,7 +19,6 @@ require ( github.com/knadh/koanf/parsers/toml v0.1.0 github.com/knadh/koanf/providers/rawbytes v0.1.0 github.com/knadh/koanf/v2 v2.1.1 - github.com/ledgerwatch/erigon-lib v1.0.0 github.com/mattn/go-sqlite3 v1.14.23 github.com/mitchellh/mapstructure v1.5.0 github.com/pelletier/go-toml/v2 v2.2.2 @@ -34,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 @@ -45,13 +44,11 @@ require ( github.com/Microsoft/go-winio v0.6.2 // indirect github.com/StackExchange/wmi v1.2.1 // indirect github.com/VictoriaMetrics/fastcache v1.12.2 // indirect - github.com/VictoriaMetrics/metrics v1.23.1 // indirect github.com/bahlo/generic-list-go v0.2.0 // indirect github.com/beorn7/perks v1.0.1 // indirect github.com/bits-and-blooms/bitset v1.14.2 // indirect github.com/btcsuite/btcd/btcec/v2 v2.3.4 // indirect github.com/buger/jsonparser v1.1.1 // indirect - github.com/c2h5oh/datasize v0.0.0-20220606134207-859f65c6625b // indirect github.com/cespare/xxhash/v2 v2.3.0 // indirect github.com/cockroachdb/errors v1.11.3 // indirect github.com/cockroachdb/fifo v0.0.0-20240816210425-c5d0cb0b6fc0 // indirect @@ -69,7 +66,6 @@ require ( github.com/decred/dcrd/dcrec/secp256k1/v4 v4.3.0 // indirect github.com/didip/tollbooth/v6 v6.1.2 // indirect github.com/dustin/go-humanize v1.0.1 // indirect - github.com/erigontech/mdbx-go v0.27.14 // indirect github.com/ethereum/c-kzg-4844 v1.0.3 // indirect github.com/ethereum/go-verkle v0.1.1-0.20240306133620-7d920df305f0 // indirect github.com/fsnotify/fsnotify v1.7.0 // indirect @@ -80,7 +76,6 @@ require ( github.com/go-logr/stdr v1.2.2 // indirect github.com/go-ole/go-ole v1.3.0 // indirect github.com/go-pkgz/expirable-cache v0.0.3 // indirect - github.com/go-stack/stack v1.8.1 // indirect github.com/go-viper/mapstructure/v2 v2.0.0-alpha.1 // indirect github.com/gofrs/flock v0.12.1 // indirect github.com/gogo/protobuf v1.3.2 // indirect @@ -108,7 +103,6 @@ require ( github.com/knadh/koanf/maps v0.1.1 // indirect github.com/kr/pretty v0.3.1 // indirect github.com/kr/text v0.2.0 // indirect - github.com/ledgerwatch/log/v3 v3.9.0 // indirect github.com/logrusorgru/aurora v2.0.3+incompatible // indirect github.com/magiconair/properties v1.8.7 // indirect github.com/mailru/easyjson v0.7.7 // indirect @@ -124,7 +118,6 @@ require ( github.com/ncruces/go-strftime v0.1.9 // indirect github.com/olekukonko/tablewriter v0.0.5 // indirect github.com/onsi/gomega v1.27.10 // indirect - github.com/pbnjay/memory v0.0.0-20210728143218-7b4eea64cf58 // indirect github.com/pelletier/go-toml v1.9.5 // indirect github.com/pkg/errors v0.9.1 // indirect github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect @@ -153,13 +146,11 @@ require ( github.com/tklauser/numcpus v0.6.1 // indirect github.com/tyler-smith/go-bip39 v1.1.0 // indirect github.com/valyala/bytebufferpool v1.0.0 // indirect - github.com/valyala/fastrand v1.1.0 // indirect - github.com/valyala/histogram v1.2.0 // indirect github.com/wk8/go-ordered-map/v2 v2.1.8 // indirect 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 28771a51..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= @@ -20,8 +20,6 @@ github.com/StackExchange/wmi v1.2.1 h1:VIkavFPXSjcnS+O8yTq7NI32k0R5Aj+v39y29VYDO github.com/StackExchange/wmi v1.2.1/go.mod h1:rcmrprowKIVzvc+NUiLncP2uuArMWLCbu9SBzvHz7e8= github.com/VictoriaMetrics/fastcache v1.12.2 h1:N0y9ASrJ0F6h0QaC3o6uJb3NIZ9VKLjCM7NQbSmF7WI= github.com/VictoriaMetrics/fastcache v1.12.2/go.mod h1:AmC+Nzz1+3G2eCPapF6UcsnkThDcMsQicp4xDukwJYI= -github.com/VictoriaMetrics/metrics v1.23.1 h1:/j8DzeJBxSpL2qSIdqnRFLvQQhbJyJbbEi22yMm7oL0= -github.com/VictoriaMetrics/metrics v1.23.1/go.mod h1:rAr/llLpEnAdTehiNlUxKgnjcOuROSzpw0GvjpEbvFc= github.com/allegro/bigcache v1.2.1-0.20190218064605-e24eb225f156/go.mod h1:Cb/ax3seSYIx7SuZdm2G2xzfwmv3TPSk2ucNfQESPXM= github.com/allegro/bigcache v1.2.1 h1:hg1sY1raCwic3Vnsvje6TT7/pnZba83LeFck5NrFKSc= github.com/allegro/bigcache v1.2.1/go.mod h1:Cb/ax3seSYIx7SuZdm2G2xzfwmv3TPSk2ucNfQESPXM= @@ -37,8 +35,6 @@ github.com/btcsuite/btcd/chaincfg/chainhash v1.0.1 h1:q0rUy8C/TYNBQS1+CGKw68tLOF github.com/btcsuite/btcd/chaincfg/chainhash v1.0.1/go.mod h1:7SFka0XMvUgj3hfZtydOrQY2mwhPclbT2snogU7SQQc= github.com/buger/jsonparser v1.1.1 h1:2PnMjfWD7wBILjqQbt530v576A/cAbQvEW9gGIpYMUs= github.com/buger/jsonparser v1.1.1/go.mod h1:6RYKKt7H4d4+iWqouImQ9R2FZql3VbhNgx27UK13J/0= -github.com/c2h5oh/datasize v0.0.0-20220606134207-859f65c6625b h1:6+ZFm0flnudZzdSE0JxlhR2hKnGPcNB35BjQf4RYQDY= -github.com/c2h5oh/datasize v0.0.0-20220606134207-859f65c6625b/go.mod h1:S/7n9copUssQ56c7aAgHqftWO4LTf4xY6CGWt8Bc+3M= github.com/cespare/cp v1.1.1 h1:nCb6ZLdB7NRaqsm91JtQTAme2SKJzXVsdPIPkyJr1MU= github.com/cespare/cp v1.1.1/go.mod h1:SOGHArjBr4JWaSDEVpWpo/hNg6RoKrls6Oh40hiwW+s= github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= @@ -91,8 +87,6 @@ github.com/didip/tollbooth/v6 v6.1.2 h1:Kdqxmqw9YTv0uKajBUiWQg+GURL/k4vy9gmLCL01 github.com/didip/tollbooth/v6 v6.1.2/go.mod h1:xjcse6CTHCLuOkzsWrEgdy9WPJFv+p/x6v+MyfP+O9s= github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY= github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto= -github.com/erigontech/mdbx-go v0.27.14 h1:IVVeQVCAjZRpAR8bThlP2ISxrOwdV35NZdGwAgotaRw= -github.com/erigontech/mdbx-go v0.27.14/go.mod h1:FAMxbOgqOnRDx51j8HjuJZIgznbDwjX7LItd+/UWyA4= github.com/ethereum/c-kzg-4844 v1.0.3 h1:IEnbOHwjixW2cTvKRUlAAUOeleV7nNM/umJR+qy4WDs= github.com/ethereum/c-kzg-4844 v1.0.3/go.mod h1:VewdlzQmpT5QSrVhbBuGoCdFJkpaJlO1aQputP83wc0= github.com/ethereum/go-ethereum v1.14.8 h1:NgOWvXS+lauK+zFukEvi85UmmsS/OkV0N23UZ1VTIig= @@ -130,8 +124,6 @@ github.com/go-sql-driver/mysql v1.4.0/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG github.com/go-sql-driver/mysql v1.6.0 h1:BCTh4TKNUYmOmMUcQ3IipzF5prigylS7XXjEkfCHuOE= github.com/go-sql-driver/mysql v1.6.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= -github.com/go-stack/stack v1.8.1 h1:ntEHSVwIt7PNXNpgPmVfMrNhLtgjlmnZha2kOpuRiDw= -github.com/go-stack/stack v1.8.1/go.mod h1:dcoOX6HbPZSZptuspn9bctJ+N/CnF5gGygcUP3XYfe4= github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE= github.com/go-viper/mapstructure/v2 v2.0.0-alpha.1 h1:TQcrn6Wq+sKGkpyPvppOz99zsMBaUOKXq6HSv655U1c= github.com/go-viper/mapstructure/v2 v2.0.0-alpha.1/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM= @@ -174,8 +166,6 @@ github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg= github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= -github.com/grpc-ecosystem/go-grpc-middleware v1.4.0 h1:UH//fgunKIs4JdUbpDl1VZCDaL56wXCB/5+wF6uHfaI= -github.com/grpc-ecosystem/go-grpc-middleware v1.4.0/go.mod h1:g5qyo/la0ALbONm6Vbp88Yd8NsDy6rZz+RcrMPxvld8= github.com/hashicorp/go-bexpr v0.1.10 h1:9kuI5PFotCboP3dkDYFr/wi0gg0QVbSNz5oFRpxn4uE= github.com/hashicorp/go-bexpr v0.1.10/go.mod h1:oxlubA2vC/gFVfX1A6JGp7ls7uCDlfJn732ehYYg+g0= github.com/hashicorp/golang-lru/v2 v2.0.7 h1:a+bsQ5rvGLjzHuww6tVxozPZFVghXaHOwFs4luLUK2k= @@ -280,10 +270,6 @@ github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0 github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= github.com/leanovate/gopter v0.2.9 h1:fQjYxZaynp97ozCzfOyOuAGOU4aU/z37zf/tOujFk7c= github.com/leanovate/gopter v0.2.9/go.mod h1:U2L/78B+KVFIx2VmW6onHJQzXtFb+p5y3y2Sh+Jxxv8= -github.com/ledgerwatch/erigon-lib v1.0.0 h1:2o7EfgB/6CyjXAaQ8+Dh7AmY5rWvwSKg0kGp/U9kwqE= -github.com/ledgerwatch/erigon-lib v1.0.0/go.mod h1:l1i6+H9MgizD+ObQ5cXsfA9S3egYTOCnnYGjbrJMqR4= -github.com/ledgerwatch/log/v3 v3.9.0 h1:iDwrXe0PVwBC68Dd94YSsHbMgQ3ufsgjzXtFNFVZFRk= -github.com/ledgerwatch/log/v3 v3.9.0/go.mod h1:EiAY6upmI/6LkNhOVxb4eVsmsP11HZCnZ3PlJMjYiqE= github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= github.com/lib/pq v1.1.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= @@ -305,7 +291,6 @@ github.com/mattn/go-isatty v0.0.5/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hd github.com/mattn/go-isatty v0.0.7/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= -github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= @@ -351,8 +336,6 @@ github.com/onsi/gomega v1.17.0/go.mod h1:HnhC7FXeEQY45zxNK3PPoIUhzk/80Xly9PcubAl github.com/onsi/gomega v1.19.0/go.mod h1:LY+I3pBVzYsTBU1AnDwOSxaYi9WoWiqgwooUqq9yPro= github.com/onsi/gomega v1.27.10 h1:naR28SdDFlqrG6kScpT8VWpu1xWY5nJRCF3XaYyBjhI= github.com/onsi/gomega v1.27.10/go.mod h1:RsS8tutOdbdgzbPtzzATp12yT7kM5I5aElG3evPbQ0M= -github.com/pbnjay/memory v0.0.0-20210728143218-7b4eea64cf58 h1:onHthvaw9LFnH4t2DcNVpwGmV9E1BkGknEliJkfwQj0= -github.com/pbnjay/memory v0.0.0-20210728143218-7b4eea64cf58/go.mod h1:DXv8WO4yhMYhSNPKjeNKa5WY9YCIEBRbNzFFPJbWO6Y= github.com/pelletier/go-toml v1.9.5 h1:4yBQzkHv+7BHq2PQUZF3Mx0IYxG7LsP222s7Agd3ve8= github.com/pelletier/go-toml v1.9.5/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c= github.com/pelletier/go-toml/v2 v2.2.2 h1:aYUidT7k73Pcl9nb2gScu7NSrKCSHIDE89b3+6Wq+LM= @@ -454,12 +437,8 @@ github.com/urfave/cli/v2 v2.27.4 h1:o1owoI+02Eb+K107p27wEX9Bb8eqIoZCfLXloLUSWJ8= github.com/urfave/cli/v2 v2.27.4/go.mod h1:m4QzxcD2qpra4z7WhzEGn74WZLViBnMpb1ToCAKdGRQ= github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw= github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= -github.com/valyala/fastrand v1.1.0 h1:f+5HkLW4rsgzdNoleUOB69hyT9IlD2ZQh9GyDMfb5G8= -github.com/valyala/fastrand v1.1.0/go.mod h1:HWqCzkrkg6QXT8V2EXWvXCoow7vLwOFN002oeRzjapQ= github.com/valyala/fasttemplate v1.2.2 h1:lxLXG0uE3Qnshl9QyaK6XJxMXlQZELvChBOCmQD0Loo= github.com/valyala/fasttemplate v1.2.2/go.mod h1:KHLXt3tVN2HBp8eijSv/kGJopbvo7S+qRAEEKiv+SiQ= -github.com/valyala/histogram v1.2.0 h1:wyYGAZZt3CpwUiIb9AU/Zbllg1llXyrtApRS815OLoQ= -github.com/valyala/histogram v1.2.0/go.mod h1:Hb4kBwb4UxsaNbbbh+RRz8ZR6pdodR57tzWUS3BUzXY= github.com/wk8/go-ordered-map/v2 v2.1.8 h1:5h/BUHu93oj4gIdvHHHGsScSTMijfx5PeYkE/fJgbpc= github.com/wk8/go-ordered-map/v2 v2.1.8/go.mod h1:5nJHM5DyteebpVlHnWMV0rPz6Zp7+xBAnxjb1X5vnTw= github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1 h1:gEOO8jv9F4OT7lGCjxCBTO/36wtF6j2nSip77qHd4x4= @@ -501,15 +480,15 @@ 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= 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= 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= @@ -529,8 +508,8 @@ 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= 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= @@ -590,8 +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/downloader.go b/l1infotreesync/downloader.go index ed3c7efb..9521c726 100644 --- a/l1infotreesync/downloader.go +++ b/l1infotreesync/downloader.go @@ -130,17 +130,20 @@ func buildAppender(client EthClienter, globalExitRoot, return nil } - // TODO: integrate this event to perform sanity checks - appender[updateL1InfoTreeSignatureV2] = func(b *sync.EVMBlock, l types.Log) error { //nolint:unparam - l1InfoTreeUpdate, err := ger.ParseUpdateL1InfoTreeV2(l) + appender[updateL1InfoTreeSignatureV2] = func(b *sync.EVMBlock, l types.Log) error { + l1InfoTreeUpdateV2, err := ger.ParseUpdateL1InfoTreeV2(l) if err != nil { return fmt.Errorf( "error parsing log %+v using ger.ParseUpdateL1InfoTreeV2: %w", l, err, ) } - log.Infof("updateL1InfoTreeSignatureV2: expected root: %s", - common.BytesToHash(l1InfoTreeUpdate.CurrentL1InfoRoot[:])) + b.Events = append(b.Events, Event{UpdateL1InfoTreeV2: &UpdateL1InfoTreeV2{ + CurrentL1InfoRoot: l1InfoTreeUpdateV2.CurrentL1InfoRoot, + LeafCount: l1InfoTreeUpdateV2.LeafCount, + Blockhash: common.BytesToHash(l1InfoTreeUpdateV2.Blockhash.Bytes()), + MinTimestamp: l1InfoTreeUpdateV2.MinTimestamp, + }}) return nil } diff --git a/l1infotreesync/e2e_test.go b/l1infotreesync/e2e_test.go index 70986cbf..132f563f 100644 --- a/l1infotreesync/e2e_test.go +++ b/l1infotreesync/e2e_test.go @@ -13,6 +13,8 @@ import ( cdktypes "github.com/0xPolygon/cdk/config/types" "github.com/0xPolygon/cdk/etherman" "github.com/0xPolygon/cdk/l1infotreesync" + mocks_l1infotreesync "github.com/0xPolygon/cdk/l1infotreesync/mocks" + "github.com/0xPolygon/cdk/log" "github.com/0xPolygon/cdk/reorgdetector" "github.com/0xPolygon/cdk/test/contracts/verifybatchesmock" "github.com/0xPolygon/cdk/test/helpers" @@ -59,7 +61,7 @@ func TestE2E(t *testing.T) { ctx, cancelCtx := context.WithCancel(context.Background()) dbPath := path.Join(t.TempDir(), "file::memory:?cache=shared") - rdm := l1infotreesync.NewReorgDetectorMock(t) + rdm := mocks_l1infotreesync.NewReorgDetectorMock(t) rdm.On("Subscribe", mock.Anything).Return(&reorgdetector.Subscription{}, nil) rdm.On("AddBlockToTrack", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(nil) @@ -154,7 +156,7 @@ func TestE2E(t *testing.T) { func TestWithReorgs(t *testing.T) { ctx := context.Background() dbPathSyncer := path.Join(t.TempDir(), "file::memory:?cache=shared") - dbPathReorg := t.TempDir() + dbPathReorg := path.Join(t.TempDir(), "file::memory:?cache=shared") client, auth, gerAddr, verifyAddr, gerSc, verifySC := newSimulatedClient(t) @@ -180,12 +182,12 @@ func TestWithReorgs(t *testing.T) { // Update L1 Info Tree + Rollup Exit Tree newLocalExitRoot := common.HexToHash(strconv.Itoa(i) + "ffff" + strconv.Itoa(1)) - _, err = verifySC.VerifyBatches(auth, rollupID, 0, newLocalExitRoot, common.Hash{}, true) + _, err = verifySC.VerifyBatchesTrustedAggregator(auth, rollupID, 0, newLocalExitRoot, common.Hash{}, true) require.NoError(t, err) // Update Rollup Exit Tree newLocalExitRoot = common.HexToHash(strconv.Itoa(i) + "ffff" + strconv.Itoa(2)) - _, err = verifySC.VerifyBatches(auth, rollupID, 0, newLocalExitRoot, common.Hash{}, false) + _, err = verifySC.VerifyBatchesTrustedAggregator(auth, rollupID, 0, newLocalExitRoot, common.Hash{}, false) require.NoError(t, err) } @@ -272,7 +274,7 @@ func TestStressAndReorgs(t *testing.T) { ctx := context.Background() dbPathSyncer := path.Join(t.TempDir(), "file:TestStressAndReorgs:memory:?cache=shared") - dbPathReorg := t.TempDir() + dbPathReorg := path.Join(t.TempDir(), "file::memory:?cache=shared") client, auth, gerAddr, verifyAddr, gerSc, verifySC := newSimulatedClient(t) @@ -310,6 +312,7 @@ func TestStressAndReorgs(t *testing.T) { require.NoError(t, err) block, err := client.Client().BlockByNumber(ctx, big.NewInt(int64(currentBlockNum-reorgSizeInBlocks))) + log.Debugf("reorging until block %d. Current block %d (before reorg)", block.NumberU64(), currentBlockNum) require.NoError(t, err) reorgFrom := block.Hash() err = client.Fork(reorgFrom) diff --git a/l1infotreesync/l1infotreesync.go b/l1infotreesync/l1infotreesync.go index a7e50128..9719fcd7 100644 --- a/l1infotreesync/l1infotreesync.go +++ b/l1infotreesync/l1infotreesync.go @@ -106,6 +106,9 @@ 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 { + return types.Proof{}, types.Root{}, sync.ErrInconsistentState + } return s.processor.GetL1InfoTreeMerkleProof(ctx, index) } @@ -115,6 +118,9 @@ func (s *L1InfoTreeSync) GetRollupExitTreeMerkleProof( networkID uint32, root common.Hash, ) (types.Proof, error) { + if s.processor.halted { + return types.Proof{}, sync.ErrInconsistentState + } if networkID == 0 { return tree.EmptyProof, nil } @@ -135,38 +141,59 @@ func translateError(err error) error { // - ErrBlockNotProcessed, // - ErrNotFound func (s *L1InfoTreeSync) GetLatestInfoUntilBlock(ctx context.Context, blockNum uint64) (*L1InfoTreeLeaf, error) { + if s.processor.halted { + return nil, sync.ErrInconsistentState + } leaf, err := s.processor.GetLatestInfoUntilBlock(ctx, blockNum) return leaf, translateError(err) } // 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 { + return nil, sync.ErrInconsistentState + } return s.processor.GetInfoByIndex(ctx, index) } // 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 { + return types.Root{}, sync.ErrInconsistentState + } return s.processor.l1InfoTree.GetRootByIndex(ctx, index) } // GetLastRollupExitRoot return the last rollup exit root processed func (s *L1InfoTreeSync) GetLastRollupExitRoot(ctx context.Context) (types.Root, error) { + if s.processor.halted { + return types.Root{}, sync.ErrInconsistentState + } return s.processor.rollupExitTree.GetLastRoot(nil) } // 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 { + return types.Root{}, sync.ErrInconsistentState + } return s.processor.l1InfoTree.GetLastRoot(nil) } // GetLastProcessedBlock return the last processed block func (s *L1InfoTreeSync) GetLastProcessedBlock(ctx context.Context) (uint64, error) { + if s.processor.halted { + return 0, sync.ErrInconsistentState + } return s.processor.GetLastProcessedBlock(ctx) } func (s *L1InfoTreeSync) GetLocalExitRoot( ctx context.Context, networkID uint32, rollupExitRoot common.Hash, ) (common.Hash, error) { + if s.processor.halted { + return common.Hash{}, sync.ErrInconsistentState + } if networkID == 0 { return common.Hash{}, errors.New("network 0 is not a rollup, and it's not part of the rollup exit tree") } @@ -175,34 +202,58 @@ func (s *L1InfoTreeSync) GetLocalExitRoot( } func (s *L1InfoTreeSync) GetLastVerifiedBatches(rollupID uint32) (*VerifyBatches, error) { + if s.processor.halted { + return nil, sync.ErrInconsistentState + } return s.processor.GetLastVerifiedBatches(rollupID) } func (s *L1InfoTreeSync) GetFirstVerifiedBatches(rollupID uint32) (*VerifyBatches, error) { + if s.processor.halted { + return nil, sync.ErrInconsistentState + } return s.processor.GetFirstVerifiedBatches(rollupID) } func (s *L1InfoTreeSync) GetFirstVerifiedBatchesAfterBlock(rollupID uint32, blockNum uint64) (*VerifyBatches, error) { + if s.processor.halted { + return nil, sync.ErrInconsistentState + } return s.processor.GetFirstVerifiedBatchesAfterBlock(rollupID, blockNum) } func (s *L1InfoTreeSync) GetFirstL1InfoWithRollupExitRoot(rollupExitRoot common.Hash) (*L1InfoTreeLeaf, error) { + if s.processor.halted { + return nil, sync.ErrInconsistentState + } return s.processor.GetFirstL1InfoWithRollupExitRoot(rollupExitRoot) } func (s *L1InfoTreeSync) GetLastInfo() (*L1InfoTreeLeaf, error) { + if s.processor.halted { + return nil, sync.ErrInconsistentState + } return s.processor.GetLastInfo() } func (s *L1InfoTreeSync) GetFirstInfo() (*L1InfoTreeLeaf, error) { + if s.processor.halted { + return nil, sync.ErrInconsistentState + } return s.processor.GetFirstInfo() } func (s *L1InfoTreeSync) GetFirstInfoAfterBlock(blockNum uint64) (*L1InfoTreeLeaf, error) { + if s.processor.halted { + return nil, sync.ErrInconsistentState + } return s.processor.GetFirstInfoAfterBlock(blockNum) } func (s *L1InfoTreeSync) GetInfoByGlobalExitRoot(ger common.Hash) (*L1InfoTreeLeaf, error) { + if s.processor.halted { + return nil, sync.ErrInconsistentState + } return s.processor.GetInfoByGlobalExitRoot(ger) } @@ -210,10 +261,16 @@ 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 { + return types.Proof{}, sync.ErrInconsistentState + } return s.processor.l1InfoTree.GetProof(ctx, index, root) } // 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 { + return nil, sync.ErrInconsistentState + } return s.processor.GetInitL1InfoRootMap(nil) } diff --git a/l1infotreesync/l1infotreesync_test.go b/l1infotreesync/l1infotreesync_test.go new file mode 100644 index 00000000..a6c5ef03 --- /dev/null +++ b/l1infotreesync/l1infotreesync_test.go @@ -0,0 +1,198 @@ +package l1infotreesync + +import ( + "context" + "errors" + "testing" + + "github.com/0xPolygon/cdk/sync" + "github.com/ethereum/go-ethereum/common" + "github.com/stretchr/testify/require" +) + +func TestGetL1InfoTreeMerkleProof(t *testing.T) { + s := L1InfoTreeSync{ + processor: &processor{ + halted: true, + }, + } + _, _, err := s.GetL1InfoTreeMerkleProof(context.Background(), 0) + require.Error(t, err) + require.True(t, errors.Is(err, sync.ErrInconsistentState)) +} + +func TestGetRollupExitTreeMerkleProof(t *testing.T) { + s := L1InfoTreeSync{ + processor: &processor{ + halted: true, + }, + } + _, err := s.GetRollupExitTreeMerkleProof(context.Background(), 0, common.Hash{}) + require.Error(t, err) + require.True(t, errors.Is(err, sync.ErrInconsistentState)) +} + +func TestGetLatestInfoUntilBlock(t *testing.T) { + s := L1InfoTreeSync{ + processor: &processor{ + halted: true, + }, + } + _, err := s.GetLatestInfoUntilBlock(context.Background(), 0) + require.Error(t, err) + require.True(t, errors.Is(err, sync.ErrInconsistentState)) +} + +func TestGetInfoByIndex(t *testing.T) { + s := L1InfoTreeSync{ + processor: &processor{ + halted: true, + }, + } + _, err := s.GetInfoByIndex(context.Background(), 0) + require.Error(t, err) + require.True(t, errors.Is(err, sync.ErrInconsistentState)) +} + +func TestGetL1InfoTreeRootByIndex(t *testing.T) { + s := L1InfoTreeSync{ + processor: &processor{ + halted: true, + }, + } + _, err := s.GetL1InfoTreeRootByIndex(context.Background(), 0) + require.Error(t, err) + require.True(t, errors.Is(err, sync.ErrInconsistentState)) +} + +func TestGetLastRollupExitRoot(t *testing.T) { + s := L1InfoTreeSync{ + processor: &processor{ + halted: true, + }, + } + _, err := s.GetLastRollupExitRoot(context.Background()) + require.Error(t, err) + require.True(t, errors.Is(err, sync.ErrInconsistentState)) +} + +func TestGetLastL1InfoTreeRoot(t *testing.T) { + s := L1InfoTreeSync{ + processor: &processor{ + halted: true, + }, + } + _, err := s.GetLastL1InfoTreeRoot(context.Background()) + require.Error(t, err) + require.True(t, errors.Is(err, sync.ErrInconsistentState)) +} + +func TestGetLastProcessedBlock(t *testing.T) { + s := L1InfoTreeSync{ + processor: &processor{ + halted: true, + }, + } + _, err := s.GetLastProcessedBlock(context.Background()) + require.Error(t, err) + require.True(t, errors.Is(err, sync.ErrInconsistentState)) +} + +func TestGetLocalExitRoot(t *testing.T) { + s := L1InfoTreeSync{ + processor: &processor{ + halted: true, + }, + } + _, err := s.GetLocalExitRoot(context.Background(), 0, common.Hash{}) + require.Error(t, err) + require.True(t, errors.Is(err, sync.ErrInconsistentState)) +} + +func TestGetLastVerifiedBatches(t *testing.T) { + s := L1InfoTreeSync{ + processor: &processor{ + halted: true, + }, + } + _, err := s.GetLastVerifiedBatches(0) + require.Error(t, err) + require.True(t, errors.Is(err, sync.ErrInconsistentState)) +} + +func TestGetFirstVerifiedBatches(t *testing.T) { + s := L1InfoTreeSync{ + processor: &processor{ + halted: true, + }, + } + _, err := s.GetFirstVerifiedBatches(0) + require.Error(t, err) + require.True(t, errors.Is(err, sync.ErrInconsistentState)) +} + +func TestGetFirstVerifiedBatchesAfterBlock(t *testing.T) { + s := L1InfoTreeSync{ + processor: &processor{ + halted: true, + }, + } + _, err := s.GetFirstVerifiedBatchesAfterBlock(0, 0) + require.Error(t, err) + require.True(t, errors.Is(err, sync.ErrInconsistentState)) +} + +func TestGetFirstL1InfoWithRollupExitRoot(t *testing.T) { + s := L1InfoTreeSync{ + processor: &processor{ + halted: true, + }, + } + _, err := s.GetFirstL1InfoWithRollupExitRoot(common.Hash{}) + require.Error(t, err) + require.True(t, errors.Is(err, sync.ErrInconsistentState)) +} + +func TestGetLastInfo(t *testing.T) { + s := L1InfoTreeSync{ + processor: &processor{ + halted: true, + }, + } + _, err := s.GetLastInfo() + require.Error(t, err) + require.True(t, errors.Is(err, sync.ErrInconsistentState)) +} + +func TestGetFirstInfo(t *testing.T) { + s := L1InfoTreeSync{ + processor: &processor{ + halted: true, + }, + } + _, err := s.GetFirstInfo() + require.Error(t, err) + require.True(t, errors.Is(err, sync.ErrInconsistentState)) +} + +func TestGetFirstInfoAfterBlock(t *testing.T) { + s := L1InfoTreeSync{ + processor: &processor{ + halted: true, + }, + } + _, err := s.GetFirstInfoAfterBlock(0) + require.Error(t, err) + require.True(t, errors.Is(err, sync.ErrInconsistentState)) +} + +func TestGetL1InfoTreeMerkleProofFromIndexToRoot(t *testing.T) { + s := L1InfoTreeSync{ + processor: &processor{ + halted: true, + }, + } + _, err := s.GetL1InfoTreeMerkleProofFromIndexToRoot(context.Background(), 0, common.Hash{}) + require.Error(t, err) + require.True(t, errors.Is(err, sync.ErrInconsistentState)) +} diff --git a/l1infotreesync/mocks/eth_clienter.go b/l1infotreesync/mocks/eth_clienter.go index 270c40d9..3e5897f9 100644 --- a/l1infotreesync/mocks/eth_clienter.go +++ b/l1infotreesync/mocks/eth_clienter.go @@ -1083,4 +1083,4 @@ func NewEthClienter(t interface { t.Cleanup(func() { mock.AssertExpectations(t) }) return mock -} +} \ No newline at end of file diff --git a/l1infotreesync/mock_reorgdetector_test.go b/l1infotreesync/mocks/mock_reorgdetector.go similarity index 98% rename from l1infotreesync/mock_reorgdetector_test.go rename to l1infotreesync/mocks/mock_reorgdetector.go index 18ac7bc8..79c6232e 100644 --- a/l1infotreesync/mock_reorgdetector_test.go +++ b/l1infotreesync/mocks/mock_reorgdetector.go @@ -1,6 +1,6 @@ // Code generated by mockery v2.39.0. DO NOT EDIT. -package l1infotreesync +package mocks_l1infotreesync import ( context "context" diff --git a/l1infotreesync/processor.go b/l1infotreesync/processor.go index 2cd6190c..ee94e829 100644 --- a/l1infotreesync/processor.go +++ b/l1infotreesync/processor.go @@ -28,6 +28,8 @@ type processor struct { db *sql.DB l1InfoTree *tree.AppendOnlyTree rollupExitTree *tree.UpdatableTree + halted bool + haltedReason string } // UpdateL1InfoTree representation of the UpdateL1InfoTree event @@ -39,6 +41,13 @@ type UpdateL1InfoTree struct { Timestamp uint64 } +type UpdateL1InfoTreeV2 struct { + CurrentL1InfoRoot common.Hash + LeafCount uint32 + Blockhash common.Hash + MinTimestamp uint64 +} + // VerifyBatches representation of the VerifyBatches and VerifyBatchesTrustedAggregator events type VerifyBatches struct { BlockNumber uint64 `meddler:"block_num"` @@ -70,9 +79,10 @@ func (i *InitL1InfoRootMap) String() string { } type Event struct { - UpdateL1InfoTree *UpdateL1InfoTree - VerifyBatches *VerifyBatches - InitL1InfoRootMap *InitL1InfoRootMap + UpdateL1InfoTree *UpdateL1InfoTree + UpdateL1InfoTreeV2 *UpdateL1InfoTreeV2 + VerifyBatches *VerifyBatches + InitL1InfoRootMap *InitL1InfoRootMap } // L1InfoTreeLeaf representation of a leaf of the L1 Info tree @@ -227,15 +237,16 @@ func (p *processor) Reorg(ctx context.Context, firstReorgedBlock uint64) error { if err != nil { return err } + shouldRollback := true defer func() { - if err != nil { + if shouldRollback { if errRllbck := tx.Rollback(); errRllbck != nil { log.Errorf("error while rolling back tx %v", errRllbck) } } }() - _, err = tx.Exec(`DELETE FROM block WHERE num >= $1;`, firstReorgedBlock) + res, err := tx.Exec(`DELETE FROM block WHERE num >= $1;`, firstReorgedBlock) if err != nil { return err } @@ -247,19 +258,36 @@ func (p *processor) Reorg(ctx context.Context, firstReorgedBlock uint64) error { if err = p.rollupExitTree.Reorg(tx, firstReorgedBlock); err != nil { return err } + rowsAffected, err := res.RowsAffected() + if err != nil { + return err + } - return tx.Commit() + if err := tx.Commit(); err != nil { + return err + } + if rowsAffected > 0 { + p.halted = false + p.haltedReason = "" + } + shouldRollback = false + return nil } // 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 { + log.Errorf("processor is halted due to: %s", p.haltedReason) + return sync.ErrInconsistentState + } tx, err := db.NewTx(ctx, p.db) if err != nil { return err } + shouldRollback := true defer func() { - if err != nil { + if shouldRollback { if errRllbck := tx.Rollback(); errRllbck != nil { log.Errorf("error while rolling back tx %v", errRllbck) } @@ -277,7 +305,6 @@ func (p *processor) ProcessBlock(ctx context.Context, block sync.Block) error { switch { case errors.Is(err, db.ErrNotFound): initialL1InfoIndex = 0 - err = nil case err != nil: return fmt.Errorf("getLastIndex err: %w", err) default: @@ -316,6 +343,29 @@ func (p *processor) ProcessBlock(ctx context.Context, block sync.Block) error { log.Infof("inserted L1InfoTreeLeaf %s", info.String()) l1InfoLeavesAdded++ } + if event.UpdateL1InfoTreeV2 != nil { + root, err := p.l1InfoTree.GetLastRoot(tx) + if err != nil { + return fmt.Errorf("GetLastRoot(). err: %w", err) + } + // If the sanity check fails, halt the syncer and rollback. The sanity check could have + // failed due to a reorg. Hopefully, this is the case, eventually the reorg will get detected, + // and the syncer will get unhalted. Otherwise, this means that the syncer has an inconsistent state + // compared to the contracts, and this will need manual intervention. + if root.Hash != event.UpdateL1InfoTreeV2.CurrentL1InfoRoot || root.Index+1 != event.UpdateL1InfoTreeV2.LeafCount { + errStr := fmt.Sprintf( + "failed to check UpdateL1InfoTreeV2. Root: %s vs event:%s. "+ + "Index: : %d vs event.LeafCount:%d. Happened on block %d", + root.Hash, common.Bytes2Hex(event.UpdateL1InfoTreeV2.CurrentL1InfoRoot[:]), + root.Index, event.UpdateL1InfoTreeV2.LeafCount, + block.Num, + ) + log.Error(errStr) + p.haltedReason = errStr + p.halted = true + return sync.ErrInconsistentState + } + } if event.VerifyBatches != nil { log.Debugf("handle VerifyBatches event %s", event.VerifyBatches.String()) err = p.processVerifyBatches(tx, block.Num, event.VerifyBatches) @@ -340,6 +390,8 @@ func (p *processor) ProcessBlock(ctx context.Context, block sync.Block) error { if err := tx.Commit(); err != nil { return fmt.Errorf("err: %w", err) } + shouldRollback = false + log.Infof("block %d processed with %d events", block.Num, len(block.Events)) return nil } diff --git a/l1infotreesync/processor_test.go b/l1infotreesync/processor_test.go index 34c5daef..e76ebaa5 100644 --- a/l1infotreesync/processor_test.go +++ b/l1infotreesync/processor_test.go @@ -358,3 +358,26 @@ func createTestLeaves(t *testing.T, numOfLeaves int) []*L1InfoTreeLeaf { return leaves } + +func TestProcessBlockUpdateL1InfoTreeV2DontMatchTree(t *testing.T) { + sut, err := newProcessor("file:Test_processor_BlockUpdateL1InfoTreeV2?mode=memory&cache=shared") + require.NoError(t, err) + block := sync.Block{ + Num: 10, + Events: []interface{}{ + Event{UpdateL1InfoTree: &UpdateL1InfoTree{ + MainnetExitRoot: common.HexToHash("beef"), + RollupExitRoot: common.HexToHash("5ca1e"), + ParentHash: common.HexToHash("1010101"), + Timestamp: 420, + }}, + Event{UpdateL1InfoTreeV2: &UpdateL1InfoTreeV2{ + CurrentL1InfoRoot: common.HexToHash("beef"), + LeafCount: 1, + }}, + }, + } + err = sut.ProcessBlock(context.Background(), block) + require.ErrorIs(t, err, sync.ErrInconsistentState) + require.True(t, sut.halted) +} diff --git a/reorgdetector/migrations/migrations.go b/reorgdetector/migrations/migrations.go new file mode 100644 index 00000000..ba619cde --- /dev/null +++ b/reorgdetector/migrations/migrations.go @@ -0,0 +1,21 @@ +package migrations + +import ( + _ "embed" + + "github.com/0xPolygon/cdk/db" + "github.com/0xPolygon/cdk/db/types" +) + +//go:embed reorgdetector0001.sql +var mig001 string + +func RunMigrations(dbPath string) error { + migrations := []types.Migration{ + { + ID: "reorgdetector0001", + SQL: mig001, + }, + } + return db.RunMigrations(dbPath, migrations) +} diff --git a/reorgdetector/migrations/reorgdetector0001.sql b/reorgdetector/migrations/reorgdetector0001.sql new file mode 100644 index 00000000..8b5092ba --- /dev/null +++ b/reorgdetector/migrations/reorgdetector0001.sql @@ -0,0 +1,11 @@ +-- +migrate Down +DROP TABLE IF EXISTS block; +DROP TABLE IF EXISTS claim; +DROP TABLE IF EXISTS bridge; + +-- +migrate Up +CREATE TABLE tracked_block ( + subscriber_id VARCHAR NOT NULL, + num BIGINT NOT NULL, + hash VARCHAR NOT NULL +); \ No newline at end of file diff --git a/reorgdetector/reorgdetector.go b/reorgdetector/reorgdetector.go index 496a844c..91d21354 100644 --- a/reorgdetector/reorgdetector.go +++ b/reorgdetector/reorgdetector.go @@ -2,18 +2,19 @@ package reorgdetector import ( "context" + "database/sql" "fmt" "math/big" "sync" "time" + "github.com/0xPolygon/cdk/db" "github.com/0xPolygon/cdk/log" + "github.com/0xPolygon/cdk/reorgdetector/migrations" "github.com/ethereum/go-ethereum" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/rpc" - "github.com/ledgerwatch/erigon-lib/kv" - "github.com/ledgerwatch/erigon-lib/kv/mdbx" "golang.org/x/sync/errgroup" ) @@ -25,7 +26,7 @@ type EthClient interface { type ReorgDetector struct { client EthClient - db kv.RwDB + db *sql.DB checkReorgInterval time.Duration trackedBlocksLock sync.RWMutex @@ -36,12 +37,13 @@ type ReorgDetector struct { } func New(client EthClient, cfg Config) (*ReorgDetector, error) { - db, err := mdbx.NewMDBX(nil). - Path(cfg.DBPath). - WithTableCfg(tableCfgFunc). - Open() + err := migrations.RunMigrations(cfg.DBPath) if err != nil { - return nil, fmt.Errorf("failed to open db: %w", err) + return nil, err + } + db, err := db.NewSQLiteDB(cfg.DBPath) + if err != nil { + return nil, err } return &ReorgDetector{ @@ -56,7 +58,7 @@ func New(client EthClient, cfg Config) (*ReorgDetector, error) { // Start starts the reorg detector func (rd *ReorgDetector) Start(ctx context.Context) (err error) { // Load tracked blocks from the DB - if err = rd.loadTrackedHeaders(ctx); err != nil { + if err = rd.loadTrackedHeaders(); err != nil { return fmt.Errorf("failed to load tracked headers: %w", err) } @@ -96,7 +98,7 @@ func (rd *ReorgDetector) AddBlockToTrack(ctx context.Context, id string, num uin // Store the given header to the tracked list hdr := newHeader(num, hash) - if err := rd.saveTrackedBlock(ctx, id, hdr); err != nil { + if err := rd.saveTrackedBlock(id, hdr); err != nil { return fmt.Errorf("failed to save tracked block: %w", err) } @@ -157,6 +159,10 @@ func (rd *ReorgDetector) detectReorgInTrackedList(ctx context.Context) error { if hdr.Num <= lastFinalisedBlock.Number.Uint64() { hdrs.removeRange(hdr.Num, hdr.Num) } + if err := rd.removeTrackedBlockRange(id, hdr.Num, hdr.Num); err != nil { + return fmt.Errorf("error removing blocks from DB for subscriber %s between blocks %d and %d: %w", + id, hdr.Num, hdr.Num, err) + } continue } @@ -164,17 +170,16 @@ func (rd *ReorgDetector) detectReorgInTrackedList(ctx context.Context) error { // Notify the subscriber about the reorg rd.notifySubscriber(id, hdr) - // Remove the reorged block and all the following blocks + // Remove the reorged block and all the following blocks from DB + if err := rd.removeTrackedBlockRange(id, hdr.Num, headers[len(headers)-1].Num); err != nil { + return fmt.Errorf("error removing blocks from DB for subscriber %s between blocks %d and %d: %w", + id, hdr.Num, headers[len(headers)-1].Num, err) + } + // Remove the reorged block and all the following blocks from memory hdrs.removeRange(hdr.Num, headers[len(headers)-1].Num) break } - - // Update the tracked blocks in the DB - if err := rd.updateTrackedBlocksDB(ctx, id, hdrs); err != nil { - return fmt.Errorf("failed to update tracked blocks for subscriber %s: %w", id, err) - } - return nil }) } @@ -183,12 +188,12 @@ func (rd *ReorgDetector) detectReorgInTrackedList(ctx context.Context) error { } // loadTrackedHeaders loads tracked headers from the DB and stores them in memory -func (rd *ReorgDetector) loadTrackedHeaders(ctx context.Context) (err error) { +func (rd *ReorgDetector) loadTrackedHeaders() (err error) { rd.trackedBlocksLock.Lock() defer rd.trackedBlocksLock.Unlock() // Load tracked blocks for all subscribers from the DB - if rd.trackedBlocks, err = rd.getTrackedBlocks(ctx); err != nil { + if rd.trackedBlocks, err = rd.getTrackedBlocks(); err != nil { return fmt.Errorf("failed to get tracked blocks: %w", err) } diff --git a/reorgdetector/reorgdetector_db.go b/reorgdetector/reorgdetector_db.go index 79bd6cd4..3a066b7f 100644 --- a/reorgdetector/reorgdetector_db.go +++ b/reorgdetector/reorgdetector_db.go @@ -1,69 +1,57 @@ package reorgdetector import ( - "context" - "encoding/json" + "errors" + "fmt" - "github.com/ledgerwatch/erigon-lib/kv" + "github.com/0xPolygon/cdk/db" + "github.com/russross/meddler" ) -const ( - subscriberBlocks = "reorgdetector-subscriberBlocks" -) - -func tableCfgFunc(_ kv.TableCfg) kv.TableCfg { - return kv.TableCfg{ - subscriberBlocks: {}, - } -} - // getTrackedBlocks returns a list of tracked blocks for each subscriber from db -func (rd *ReorgDetector) getTrackedBlocks(ctx context.Context) (map[string]*headersList, error) { - tx, err := rd.db.BeginRo(ctx) +func (rd *ReorgDetector) getTrackedBlocks() (map[string]*headersList, error) { + trackedBlocks := make(map[string]*headersList, 0) + var headersWithID []*headerWithSubscriberID + err := meddler.QueryAll(rd.db, &headersWithID, "SELECT * FROM tracked_block ORDER BY subscriber_id;") if err != nil { - return nil, err + if errors.Is(err, db.ErrNotFound) { + return trackedBlocks, nil + } + return nil, fmt.Errorf("error queryng tracked_block: %w", err) } - - defer tx.Rollback() - - cursor, err := tx.Cursor(subscriberBlocks) - if err != nil { - return nil, err + if len(headersWithID) == 0 { + return trackedBlocks, nil } - - defer cursor.Close() - - trackedBlocks := make(map[string]*headersList, 0) - - for k, v, err := cursor.First(); k != nil; k, v, err = cursor.Next() { - if err != nil { - return nil, err + currentID := headersWithID[0].SubscriberID + currentHeaders := []header{} + for i := 0; i < len(headersWithID); i++ { + if i == len(headersWithID)-1 { + currentHeaders = append(currentHeaders, header{ + Num: headersWithID[i].Num, + Hash: headersWithID[i].Hash, + }) + trackedBlocks[currentID] = newHeadersList(currentHeaders...) + } else if headersWithID[i].SubscriberID != currentID { + trackedBlocks[currentID] = newHeadersList(currentHeaders...) + currentHeaders = []header{{ + Num: headersWithID[i].Num, + Hash: headersWithID[i].Hash, + }} + currentID = headersWithID[i].SubscriberID + } else { + currentHeaders = append(currentHeaders, header{ + Num: headersWithID[i].Num, + Hash: headersWithID[i].Hash, + }) } - - var headers []header - if err := json.Unmarshal(v, &headers); err != nil { - return nil, err - } - - trackedBlocks[string(k)] = newHeadersList(headers...) } return trackedBlocks, nil } // saveTrackedBlock saves the tracked block for a subscriber in db and in memory -func (rd *ReorgDetector) saveTrackedBlock(ctx context.Context, id string, b header) error { +func (rd *ReorgDetector) saveTrackedBlock(id string, b header) error { rd.trackedBlocksLock.Lock() - - // this has to go after the lock, because of a possible deadlock - // between AddBlocksToTrack and detectReorgInTrackedList - tx, err := rd.db.BeginRw(ctx) - if err != nil { - return err - } - - defer tx.Rollback() - hdrs, ok := rd.trackedBlocks[id] if !ok || hdrs.isEmpty() { hdrs = newHeadersList(b) @@ -72,32 +60,18 @@ func (rd *ReorgDetector) saveTrackedBlock(ctx context.Context, id string, b head hdrs.add(b) } rd.trackedBlocksLock.Unlock() - - raw, err := json.Marshal(hdrs.getSorted()) - if err != nil { - return err - } - - return tx.Put(subscriberBlocks, []byte(id), raw) + return meddler.Insert(rd.db, "tracked_block", &headerWithSubscriberID{ + SubscriberID: id, + Num: b.Num, + Hash: b.Hash, + }) } // updateTrackedBlocksDB updates the tracked blocks for a subscriber in db -func (rd *ReorgDetector) updateTrackedBlocksDB(ctx context.Context, id string, blocks *headersList) error { - tx, err := rd.db.BeginRw(ctx) - if err != nil { - return err - } - - defer tx.Rollback() - - raw, err := json.Marshal(blocks.getSorted()) - if err != nil { - return err - } - - if err = tx.Put(subscriberBlocks, []byte(id), raw); err != nil { - return err - } - - return nil +func (rd *ReorgDetector) removeTrackedBlockRange(id string, fromBlock, toBlock uint64) error { + _, err := rd.db.Exec( + "DELETE FROM tracked_block WHERE num >= $1 AND NUM <= 2 AND subscriber_id = $3;", + fromBlock, toBlock, id, + ) + return err } diff --git a/reorgdetector/reorgdetector_test.go b/reorgdetector/reorgdetector_test.go index c99bb484..a496d33f 100644 --- a/reorgdetector/reorgdetector_test.go +++ b/reorgdetector/reorgdetector_test.go @@ -2,11 +2,14 @@ package reorgdetector import ( "context" + "path" + "strings" "testing" "time" cdktypes "github.com/0xPolygon/cdk/config/types" "github.com/0xPolygon/cdk/test/helpers" + common "github.com/ethereum/go-ethereum/common" "github.com/stretchr/testify/require" ) @@ -19,7 +22,7 @@ func Test_ReorgDetector(t *testing.T) { clientL1, _ := helpers.SimulatedBackend(t, nil, 0) // Create test DB dir - testDir := t.TempDir() + testDir := path.Join(t.TempDir(), "file::memory:?cache=shared") reorgDetector, err := New(clientL1.Client(), Config{DBPath: testDir, CheckReorgsInterval: cdktypes.NewDuration(time.Millisecond * 100)}) require.NoError(t, err) @@ -69,3 +72,66 @@ func Test_ReorgDetector(t *testing.T) { require.Equal(t, 1, headersList.len()) // Only block 3 left require.Equal(t, remainingHeader.Hash(), headersList.get(4).Hash) } + +func TestGetTrackedBlocks(t *testing.T) { + clientL1, _ := helpers.SimulatedBackend(t, nil, 0) + testDir := path.Join(t.TempDir(), "file::memory:?cache=shared") + reorgDetector, err := New(clientL1.Client(), Config{DBPath: testDir, CheckReorgsInterval: cdktypes.NewDuration(time.Millisecond * 100)}) + require.NoError(t, err) + list, err := reorgDetector.getTrackedBlocks() + require.NoError(t, err) + require.Equal(t, len(list), 0) + + expectedList := make(map[string]*headersList) + headersMapFoo := make(map[uint64]header) + headerFoo2 := header{ + Num: 2, + Hash: common.HexToHash("foofoo"), + } + err = reorgDetector.saveTrackedBlock("foo", headerFoo2) + require.NoError(t, err) + headersMapFoo[2] = headerFoo2 + headerFoo3 := header{ + Num: 3, + Hash: common.HexToHash("foofoofoo"), + } + err = reorgDetector.saveTrackedBlock("foo", headerFoo3) + require.NoError(t, err) + headersMapFoo[3] = headerFoo3 + expectedList["foo"] = &headersList{ + headers: headersMapFoo, + } + list, err = reorgDetector.getTrackedBlocks() + require.NoError(t, err) + require.Equal(t, expectedList, list) + + headersMapBar := make(map[uint64]header) + headerBar2 := header{ + Num: 2, + Hash: common.HexToHash("BarBar"), + } + err = reorgDetector.saveTrackedBlock("Bar", headerBar2) + require.NoError(t, err) + headersMapBar[2] = headerBar2 + expectedList["Bar"] = &headersList{ + headers: headersMapBar, + } + list, err = reorgDetector.getTrackedBlocks() + require.NoError(t, err) + require.Equal(t, expectedList, list) + + require.NoError(t, reorgDetector.loadTrackedHeaders()) + _, ok := reorgDetector.subscriptions["foo"] + require.True(t, ok) + _, ok = reorgDetector.subscriptions["Bar"] + require.True(t, ok) +} + +func TestNotSubscribed(t *testing.T) { + clientL1, _ := helpers.SimulatedBackend(t, nil, 0) + testDir := path.Join(t.TempDir(), "file::memory:?cache=shared") + reorgDetector, err := New(clientL1.Client(), Config{DBPath: testDir, CheckReorgsInterval: cdktypes.NewDuration(time.Millisecond * 100)}) + require.NoError(t, err) + err = reorgDetector.AddBlockToTrack(context.Background(), "foo", 1, common.Hash{}) + require.True(t, strings.Contains(err.Error(), "is not subscribed")) +} diff --git a/reorgdetector/types.go b/reorgdetector/types.go index bee3eb44..20d4562c 100644 --- a/reorgdetector/types.go +++ b/reorgdetector/types.go @@ -8,8 +8,14 @@ import ( ) type header struct { - Num uint64 - Hash common.Hash + Num uint64 `meddler:"num"` + Hash common.Hash `meddler:"hash,hash"` +} + +type headerWithSubscriberID struct { + SubscriberID string `meddler:"subscriber_id"` + Num uint64 `meddler:"num"` + Hash common.Hash `meddler:"hash,hash"` } // newHeader returns a new instance of header diff --git a/rpc/bridge.go b/rpc/bridge.go index 7b52ed73..65d94971 100644 --- a/rpc/bridge.go +++ b/rpc/bridge.go @@ -292,7 +292,6 @@ func (b *BridgeEndpoints) getFirstL1InfoTreeIndexForL1Bridge(ctx context.Context if err != nil { return 0, err } - //nolint:gocritic // switch statement doesn't make sense here, I couldn't break if root.Index < depositCount { lowerLimit = targetBlock + 1 } else if root.Index == depositCount { @@ -346,7 +345,6 @@ func (b *BridgeEndpoints) getFirstL1InfoTreeIndexForL2Bridge(ctx context.Context if err != nil { return 0, err } - //nolint:gocritic // switch statement doesn't make sense here, I couldn't break if root.Index < depositCount { lowerLimit = targetBlock + 1 } else if root.Index == depositCount { diff --git a/scripts/local_config b/scripts/local_config index b65210ac..5830b6e6 100755 --- a/scripts/local_config +++ b/scripts/local_config @@ -29,6 +29,10 @@ function get_value_from_toml_file(){ local _KEY="$3" local _LINE local _inside_section=0 + if [ $_SECTION == "." ]; then + _SECTION="" + _inside_section=1 + fi local _return_next_line=0 local _TMP_FILE=$(mktemp) cat $_FILE > $_TMP_FILE @@ -72,29 +76,55 @@ function get_value_from_toml_file(){ } ############################################################################### function export_key_from_toml_file_or_fatal(){ + export_key_from_toml_file "$1" "$2" "$3" "$4" + if [ $? -ne 0 ]; then + local _EXPORTED_VAR_NAME="$1" + local _FILE="$2" + local _SECTION="$3" + local _KEY="$4" + log_fatal "$FUNCNAME: key [$_KEY] not found in section [$_SECTION] in file [$_FILE]" + fi +} + +############################################################################### +function export_key_from_toml_file(){ local _EXPORTED_VAR_NAME="$1" local _FILE="$2" local _SECTION="$3" local _KEY="$4" local _VALUE=$(get_value_from_toml_file $_FILE $_SECTION $_KEY) if [ -z "$_VALUE" ]; then - log_fatal "$FUNCNAME: key $_KEY not found in section $_SECTION in file $_FILE" + log_debug "$FUNCNAME: key [$_KEY] not found in section [$_SECTION] in file [$_FILE]" + return 1 fi export $_EXPORTED_VAR_NAME="$_VALUE" log_debug "$_EXPORTED_VAR_NAME=${!_EXPORTED_VAR_NAME} \t\t\t# file:$_FILE section:$_SECTION key:$_KEY" + return 0 } - ############################################################################### function export_obj_key_from_toml_file_or_fatal(){ + export_obj_key_from_toml_file $* + if [ $? -ne 0 ]; then + local _EXPORTED_VAR_NAME="$1" + local _FILE="$2" + local _SECTION="$3" + local _KEY="$4" + log_fatal "$FUNCNAME: obj_key [$_KEY] not found in section [$_SECTION] in file [$_FILE]" + fi +} + +############################################################################### +function export_obj_key_from_toml_file(){ local _EXPORTED_VAR_NAME="$1" local _FILE="$2" local _SECTION="$3" local _KEY="$4" local _OBJ_KEY="$5" - log_debug "export_obj_key_from_toml_file_or_fatal: $_EXPORTED_VAR_NAME $_FILE $_SECTION $_KEY $_OBJ_KEY" + log_debug "export_obj_key_from_toml_file: $_EXPORTED_VAR_NAME $_FILE $_SECTION $_KEY $_OBJ_KEY" local _VALUE=$(get_value_from_toml_file $_FILE $_SECTION $_KEY) if [ -z "$_VALUE" ]; then - log_fatal "export_obj_key_from_toml_file_or_fatal: obj_key $_KEY not found in section [$_SECTION]" + log_debug "export_obj_key_from_toml_file: obj_key $_KEY not found in section [$_SECTION]" + return 1 fi local _CLEAN_VALUE=$(echo $_VALUE | tr -d '{' | tr -d '}' | tr ',' '\n') while read -r _LINE; do @@ -113,7 +143,8 @@ function export_obj_key_from_toml_file_or_fatal(){ return 0 fi done <<< "$_CLEAN_VALUE" - log_fatal "export_obj_key_from_toml_file_or_fatal: obj_key $_OBJ_KEY not found in section $_SECTION/ $_KEY = $_VALUE" + log_debug "export_obj_key_from_toml_file: obj_key $_OBJ_KEY not found in section $_SECTION/ $_KEY = $_VALUE" + return 1 } ############################################################################### @@ -133,23 +164,55 @@ function export_values_of_genesis(){ ############################################################################### function export_values_of_cdk_node_config(){ local _CDK_CONFIG_FILE=$1 - export_key_from_toml_file_or_fatal zkevm_l2_sequencer_address $_CDK_CONFIG_FILE SequenceSender L2Coinbase - export_obj_key_from_toml_file_or_fatal zkevm_l2_sequencer_keystore_password $_CDK_CONFIG_FILE SequenceSender PrivateKey Password - export_key_from_toml_file_or_fatal l1_chain_id $_CDK_CONFIG_FILE SequenceSender.EthTxManager.Etherman L1ChainID - export_key_from_toml_file_or_fatal zkevm_is_validium $_CDK_CONFIG_FILE Common IsValidiumMode - export_key_from_toml_file_or_fatal zkevm_contract_versions $_CDK_CONFIG_FILE Common ContractVersions - export_key_from_toml_file_or_fatal l2_chain_id $_CDK_CONFIG_FILE Aggregator ChainID + export_key_from_toml_file zkevm_l2_sequencer_address $_CDK_CONFIG_FILE SequenceSender L2Coinbase + if [ $? -ne 0 ]; then + export_key_from_toml_file_or_fatal zkevm_l2_sequencer_address $_CDK_CONFIG_FILE "." L2Coinbase + fi + export_obj_key_from_toml_file zkevm_l2_sequencer_keystore_password $_CDK_CONFIG_FILE SequenceSender PrivateKey Password + if [ $? -ne 0 ]; then + export_key_from_toml_file_or_fatal zkevm_l2_sequencer_keystore_password $_CDK_CONFIG_FILE "." SequencerPrivateKeyPassword + fi + export_key_from_toml_file l1_chain_id $_CDK_CONFIG_FILE SequenceSender.EthTxManager.Etherman L1ChainID + if [ $? -ne 0 ]; then + export_key_from_toml_file_or_fatal l1_chain_id $_CDK_CONFIG_FILE L1Config chainId + fi + export_key_from_toml_file zkevm_is_validium $_CDK_CONFIG_FILE Common IsValidiumMode + if [ $? -ne 0 ]; then + export_key_from_toml_file_or_fatal zkevm_is_validium $_CDK_CONFIG_FILE "." IsValidiumMode + fi + export_key_from_toml_file zkevm_contract_versions $_CDK_CONFIG_FILE Common ContractVersions + if [ $? -ne 0 ]; then + export_key_from_toml_file_or_fatal zkevm_contract_versions $_CDK_CONFIG_FILE "." ContractVersions + fi + export_key_from_toml_file l2_chain_id $_CDK_CONFIG_FILE Aggregator ChainID + if [ $? -ne 0 ]; then + log_debug "l2_chain_id not found in Aggregator section, using 0" + export l2_chain_id="0" + fi export_key_from_toml_file_or_fatal zkevm_aggregator_port $_CDK_CONFIG_FILE Aggregator Port - export_key_from_toml_file_or_fatal zkevm_l2_agglayer_address $_CDK_CONFIG_FILE Aggregator SenderAddress + export_key_from_toml_file zkevm_l2_agglayer_address $_CDK_CONFIG_FILE Aggregator SenderAddress + 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_or_fatal zkevm_l2_aggregator_keystore_password $_CDK_CONFIG_FILE Aggregator.EthTxManager PrivateKeys Password - - export_key_from_toml_file_or_fatal zkevm_rollup_fork_id $_CDK_CONFIG_FILE Aggregator ForkId - export_key_from_toml_file_or_fatal zkevm_l2_agglayer_keystore_password $_CDK_CONFIG_FILE AggSender.SequencerPrivateKey Password - export_key_from_toml_file_or_fatal zkevm_bridge_address $_CDK_CONFIG_FILE BridgeL1Sync BridgeAddr - + 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 + fi + export_key_from_toml_file zkevm_rollup_fork_id $_CDK_CONFIG_FILE Aggregator ForkId + if [ $? -ne 0 ]; then + export_key_from_toml_file_or_fatal zkevm_rollup_fork_id $_CDK_CONFIG_FILE "." ForkId + fi + export_key_from_toml_file zkevm_l2_agglayer_keystore_password $_CDK_CONFIG_FILE AggSender.SequencerPrivateKey Password + if [ $? -ne 0 ]; then + export_key_from_toml_file_or_fatal zkevm_l2_agglayer_keystore_password $_CDK_CONFIG_FILE "." SequencerPrivateKeyPassword + fi + export_key_from_toml_file zkevm_bridge_address $_CDK_CONFIG_FILE BridgeL1Sync BridgeAddr + if [ $? -ne 0 ]; then + export_key_from_toml_file_or_fatal zkevm_bridge_address $_CDK_CONFIG_FILE "." polygonBridgeAddr + fi export is_cdk_validium=$zkevm_is_validium export zkevm_rollup_chain_id=$l2_chain_id @@ -253,10 +316,10 @@ EOF ############################################################################### function create_dest_folder(){ export DEST=${TMP_CDK_FOLDER}/local_config - export path_rw_data=${TMP_CDK_FOLDER}/runtime + export zkevm_path_rw_data=${TMP_CDK_FOLDER}/runtime [ ! -d ${DEST} ] && mkdir -p ${DEST} rm $DEST/* - mkdir $path_rw_data + mkdir $zkevm_path_rw_data } ############################################################################### function download_kurtosis_artifacts(){ @@ -281,6 +344,18 @@ function download_kurtosis_artifacts(){ } ############################################################################### +function add_translation_rules_for_validium(){ + if [ $is_cdk_validium != "true" ]; then + return + fi + log_debug " For Validium mode, we need to reach the DAC SERVER: adding translation rules" + + echo "[Aggregator.Synchronizer.Etherman.Validium.Translator]" + echo "FullMatchRules = [" + echo " {Old=\"http://zkevm-dac-001:8484\", New=\"http://127.0.0.1:${dac_port}\"}," + echo " ]" +} +############################################################################### function check_generated_config_file(){ grep "" $DEST_TEMPLATE_FILE > /dev/null if [ $? -ne 1 ]; then @@ -337,15 +412,16 @@ ok_or_fatal "Error generating template" check_generated_config_file +add_translation_rules_for_validium echo " " echo "file generated at:" $DEST/test.kurtosis.toml echo "- to restart kurtosis:" -echo " kurtosis clean --all; kurtosis run --enclave cdk-v1 --args-file params.yml --image-download always ." +echo " kurtosis clean --all; kurtosis run --enclave cdk --args-file params.yml --image-download always ." echo " " echo "- Stop cdk-node:" -echo " kurtosis service stop cdk-v1 cdk-node-001" +echo " kurtosis service stop cdk cdk-node-001" echo " " echo "- Add next configuration to vscode launch.json" echo " -----------------------------------------------------------" @@ -371,4 +447,4 @@ EOF echo " -----------------------------------------------------------" echo " " echo " - rembember to clean previous execution data: " -echo " rm -Rf ${path_rw_data}/*" \ No newline at end of file +echo " rm -Rf ${zkevm_path_rw_data}/*" 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) diff --git a/sonar-project.properties b/sonar-project.properties index f46e9863..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=**/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 # ===================================================== diff --git a/sync/driver.go b/sync/driver.go index bd066ba1..f85c04fb 100644 --- a/sync/driver.go +++ b/sync/driver.go @@ -1,6 +1,11 @@ package sync -import "context" +import ( + "context" + "errors" +) + +var ErrInconsistentState = errors.New("state is inconsistent, try again later once the state is consolidated") type Block struct { Num uint64 diff --git a/sync/evmdriver.go b/sync/evmdriver.go index 4e195af2..3412cd13 100644 --- a/sync/evmdriver.go +++ b/sync/evmdriver.go @@ -2,6 +2,7 @@ package sync import ( "context" + "errors" "github.com/0xPolygon/cdk/log" "github.com/0xPolygon/cdk/reorgdetector" @@ -97,8 +98,8 @@ reset: cancel() return case b := <-downloadCh: - d.log.Debug("handleNewBlock", " blockNum: ", b.Num, " blockHash: ", b.Hash) - d.handleNewBlock(ctx, b) + d.log.Debugf("handleNewBlock, blockNum: %d, blockHash: %s", b.Num, b.Hash) + d.handleNewBlock(ctx, cancel, b) case firstReorgedBlock := <-d.reorgSub.ReorgedBlock: d.log.Debug("handleReorg from block: ", firstReorgedBlock) d.handleReorg(ctx, cancel, firstReorgedBlock) @@ -107,32 +108,59 @@ reset: } } -func (d *EVMDriver) handleNewBlock(ctx context.Context, b EVMBlock) { +func (d *EVMDriver) handleNewBlock(ctx context.Context, cancel context.CancelFunc, b EVMBlock) { attempts := 0 + succeed := false for { - err := d.reorgDetector.AddBlockToTrack(ctx, d.reorgDetectorID, b.Num, b.Hash) - if err != nil { - attempts++ - d.log.Errorf("error adding block %d to tracker: %v", b.Num, err) - d.rh.Handle("handleNewBlock", attempts) - continue + select { + case <-ctx.Done(): + // If the context is canceled, exit the function + d.log.Warnf("context canceled while adding block %d to tracker", b.Num) + return + default: + err := d.reorgDetector.AddBlockToTrack(ctx, d.reorgDetectorID, b.Num, b.Hash) + if err != nil { + attempts++ + d.log.Errorf("error adding block %d to tracker: %v", b.Num, err) + d.rh.Handle("handleNewBlock", attempts) + } else { + succeed = true + } + } + if succeed { + break } - break } attempts = 0 + succeed = false for { - blockToProcess := Block{ - Num: b.Num, - Events: b.Events, + select { + case <-ctx.Done(): + // If the context is canceled, exit the function + d.log.Warnf("context canceled while processing block %d", b.Num) + return + default: + blockToProcess := Block{ + Num: b.Num, + Events: b.Events, + } + err := d.processor.ProcessBlock(ctx, blockToProcess) + if err != nil { + if errors.Is(err, ErrInconsistentState) { + d.log.Warn("state got inconsistent after processing this block. Stopping downloader until there is a reorg") + cancel() + return + } + attempts++ + d.log.Errorf("error processing events for block %d, err: ", b.Num, err) + d.rh.Handle("handleNewBlock", attempts) + } else { + succeed = true + } } - err := d.processor.ProcessBlock(ctx, blockToProcess) - if err != nil { - attempts++ - d.log.Errorf("error processing events for block %d, err: ", b.Num, err) - d.rh.Handle("handleNewBlock", attempts) - continue + if succeed { + break } - break } } diff --git a/sync/evmdriver_test.go b/sync/evmdriver_test.go index c17370e1..ef551d0f 100644 --- a/sync/evmdriver_test.go +++ b/sync/evmdriver_test.go @@ -144,7 +144,7 @@ func TestHandleNewBlock(t *testing.T) { Return(nil) pm.On("ProcessBlock", ctx, Block{Num: b1.Num, Events: b1.Events}). Return(nil) - driver.handleNewBlock(ctx, b1) + driver.handleNewBlock(ctx, nil, b1) // reorg deteector fails once b2 := EVMBlock{ @@ -161,7 +161,7 @@ func TestHandleNewBlock(t *testing.T) { Return(nil).Once() pm.On("ProcessBlock", ctx, Block{Num: b2.Num, Events: b2.Events}). Return(nil) - driver.handleNewBlock(ctx, b2) + driver.handleNewBlock(ctx, nil, b2) // processor fails once b3 := EVMBlock{ @@ -177,7 +177,26 @@ func TestHandleNewBlock(t *testing.T) { Return(errors.New("foo")).Once() pm.On("ProcessBlock", ctx, Block{Num: b3.Num, Events: b3.Events}). Return(nil).Once() - driver.handleNewBlock(ctx, b3) + driver.handleNewBlock(ctx, nil, b3) + + // inconsistent state error + b4 := EVMBlock{ + EVMBlockHeader: EVMBlockHeader{ + Num: 4, + Hash: common.HexToHash("f00"), + }, + } + rdm. + On("AddBlockToTrack", ctx, reorgDetectorID, b4.Num, b4.Hash). + Return(nil) + pm.On("ProcessBlock", ctx, Block{Num: b4.Num, Events: b4.Events}). + Return(ErrInconsistentState) + cancelIsCalled := false + cancel := func() { + cancelIsCalled = true + } + driver.handleNewBlock(ctx, cancel, b4) + require.True(t, cancelIsCalled) } func TestHandleReorg(t *testing.T) { diff --git a/test/Makefile b/test/Makefile index a864cf82..12f406fd 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 @@ -35,7 +37,7 @@ generate-mocks-rpc: ## Generates mocks for rpc, using mockery tool generate-mocks-l1infotreesync: ## Generates mocks for l1infotreesync, using mockery tool rm -Rf ../l1infotreesync/mocks export "GOROOT=$$(go env GOROOT)" && $$(go env GOPATH)/bin/mockery --all --case snake --dir ../l1infotreesync --output ../l1infotreesync/mocks --outpkg mocks_l1infotreesync ${COMMON_MOCKERY_PARAMS} - export "GOROOT=$$(go env GOROOT)" && $$(go env GOPATH)/bin/mockery --name=ReorgDetector --dir=../sync --output=../l1infotreesync --outpkg=l1infotreesync --structname=ReorgDetectorMock --filename=mock_reorgdetector_test.go + export "GOROOT=$$(go env GOROOT)" && $$(go env GOPATH)/bin/mockery --name=ReorgDetector --dir=../sync --output=../l1infotreesync/mocks --outpkg=mocks_l1infotreesync --structname=ReorgDetectorMock --filename=mock_reorgdetector.go .PHONY: generate-mocks-aggoracle generate-mocks-helpers: ## Generates mocks for helpers , using mockery tool @@ -61,15 +63,12 @@ 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 - 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 diff --git a/test/aggoraclehelpers/aggoracle_e2e.go b/test/aggoraclehelpers/aggoracle_e2e.go index be362ccc..7830b941 100644 --- a/test/aggoraclehelpers/aggoracle_e2e.go +++ b/test/aggoraclehelpers/aggoracle_e2e.go @@ -105,7 +105,7 @@ func CommonSetup(t *testing.T) ( l1Client, authL1, gerL1Addr, gerL1Contract, bridgeL1Addr, bridgeL1Contract := newSimulatedL1(t) // Reorg detector - dbPathReorgDetector := t.TempDir() + dbPathReorgDetector := path.Join(t.TempDir(), "file::memory:?cache=shared") reorg, err := reorgdetector.New(l1Client.Client(), reorgdetector.Config{DBPath: dbPathReorgDetector}) require.NoError(t, err) diff --git a/test/bridge-e2e.bats b/test/bridge-e2e.bats index d504c1c9..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 } @@ -76,6 +88,9 @@ setup() { local initial_receiver_balance=$(cast balance "$receiver" --rpc-url "$l2_rpc_url") echo "Initial receiver balance of native token on L2 $initial_receiver_balance" >&3 + local l1_minter_balance=$(cast balance "0x8943545177806ED17B9F23F0a21ee5948eCaa776" --rpc-url "$l1_rpc_url") + echo "Initial minter balance on L1 $l1_minter_balance" >&3 + # Query for initial sender balance run query_contract "$l1_rpc_url" "$gas_token_addr" "$balance_of_fn_sig" "$sender_addr" assert_success @@ -85,7 +100,7 @@ setup() { # Mint gas token on L1 local tokens_amount="0.1ether" local wei_amount=$(cast --to-unit $tokens_amount wei) - local minter_key=${MINTER_KEY:-"42b6e34dc21598a807dc19d7784c71b2a7a01f6480dc6f58258f78e539f1a1fa"} + local minter_key=${MINTER_KEY:-"bcdf20249abf0ed6d944c0288fad489e33f66b3960d9e6229c1cd214ed3bbe31"} run mint_erc20_tokens "$l1_rpc_url" "$gas_token_addr" "$minter_key" "$sender_addr" "$tokens_amount" assert_success diff --git a/test/combinations/fork11-rollup.yml b/test/combinations/fork11-rollup.yml index 1afd8f79..79baa92d 100644 --- a/test/combinations/fork11-rollup.yml +++ b/test/combinations/fork11-rollup.yml @@ -1,9 +1,10 @@ args: zkevm_contracts_image: leovct/zkevm-contracts:v7.0.0-rc.2-fork.11 zkevm_prover_image: hermeznetwork/zkevm-prover:v7.0.2-fork.11 - cdk_erigon_node_image: hermeznetwork/cdk-erigon:v2.1.0 + 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 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 ed618754..c17444b3 100644 --- a/test/combinations/fork12-cdk-validium.yml +++ b/test/combinations/fork12-cdk-validium.yml @@ -1,8 +1,10 @@ args: zkevm_contracts_image: leovct/zkevm-contracts:v8.0.0-rc.4-fork.12 zkevm_prover_image: hermeznetwork/zkevm-prover:v8.0.0-RC12-fork.12 - cdk_erigon_node_image: hermeznetwork/cdk-erigon:v2.1.0 + cdk_erigon_node_image: hermeznetwork/cdk-erigon:v2.1.2 cdk_node_image: cdk zkevm_use_gas_token_contract: true data_availability_mode: cdk-validium sequencer_type: erigon + + diff --git a/test/combinations/fork12-rollup.yml b/test/combinations/fork12-rollup.yml index c97a25cf..95a5111a 100644 --- a/test/combinations/fork12-rollup.yml +++ b/test/combinations/fork12-rollup.yml @@ -1,7 +1,7 @@ args: zkevm_contracts_image: leovct/zkevm-contracts:v8.0.0-rc.4-fork.12 zkevm_prover_image: hermeznetwork/zkevm-prover:v8.0.0-RC12-fork.12 - cdk_erigon_node_image: hermeznetwork/cdk-erigon:v2.1.0 + cdk_erigon_node_image: hermeznetwork/cdk-erigon:v2.1.2 cdk_node_image: cdk zkevm_use_gas_token_contract: true data_availability_mode: rollup diff --git a/test/combinations/fork9-cdk-validium.yml b/test/combinations/fork9-cdk-validium.yml index c28b2c49..e0543654 100644 --- a/test/combinations/fork9-cdk-validium.yml +++ b/test/combinations/fork9-cdk-validium.yml @@ -1,7 +1,7 @@ args: zkevm_contracts_image: leovct/zkevm-contracts:v6.0.0-rc.1-fork.9 zkevm_prover_image: hermeznetwork/zkevm-prover:v6.0.6 - cdk_erigon_node_image: hermeznetwork/cdk-erigon:v2.1.0 + 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 diff --git a/test/config/kurtosis-cdk-node-config.toml.template b/test/config/kurtosis-cdk-node-config.toml.template index 1d70226d..4069b350 100644 --- a/test/config/kurtosis-cdk-node-config.toml.template +++ b/test/config/kurtosis-cdk-node-config.toml.template @@ -1,12 +1,17 @@ -PathRWData = "{{.path_rw_data}}/" +PathRWData = "{{.zkevm_path_rw_data}}/" L1URL="{{.l1_rpc_url}}" L2URL="http://{{.l2_rpc_name}}{{.deployment_suffix}}:{{.zkevm_rpc_http_port}}" AggLayerURL="{{.agglayer_url}}" ForkId = {{.zkevm_rollup_fork_id}} IsValidiumMode = {{.is_cdk_validium}} + {{if eq .zkevm_rollup_fork_id "12"}} ContractVersions = "banana" +{{else if eq .zkevm_rollup_fork_id "13"}} +# Doesn't look like this is needed at the moment, but soon perhaps? +# ContractVersions = "durian" +ContractVersions = "banana" {{else}} ContractVersions = "elderberry" {{end}} @@ -20,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}}" @@ -46,7 +49,10 @@ Outputs = ["stderr"] [Aggregator] Port = "{{.zkevm_aggregator_port}}" - + RetryTime = "30s" + VerifyProofInterval = "10s" + GasOffset = 150000 + SettlementBackend = "agglayer" [Aggregator.DB] Name = "{{.aggregator_db.name}}" User = "{{.aggregator_db.user}}" @@ -57,4 +63,8 @@ Outputs = ["stderr"] MaxConns = 200 [AggSender] -SequencerPrivateKey = {Path = "{{or .zkevm_l2_agglayer_keystore_file "/pk/sequencer.keystore"}}", Password = "{{.zkevm_l2_agglayer_keystore_password}}"} +CertificateSendInterval = "1m" +CheckSettledInterval = "5s" +SaveCertificatesToFilesPath = "{{.zkevm_path_rw_data}}/" + + diff --git a/test/contracts/abi/verifybatchesmock.abi b/test/contracts/abi/verifybatchesmock.abi index 2b314a92..176fb78b 100644 --- a/test/contracts/abi/verifybatchesmock.abi +++ b/test/contracts/abi/verifybatchesmock.abi @@ -1 +1 @@ -[{"inputs":[{"internalType":"contract IPolygonZkEVMGlobalExitRootV2","name":"_globalExitRootManager","type":"address"}],"stateMutability":"nonpayable","type":"constructor"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint32","name":"rollupID","type":"uint32"},{"indexed":false,"internalType":"uint64","name":"numBatch","type":"uint64"},{"indexed":false,"internalType":"bytes32","name":"stateRoot","type":"bytes32"},{"indexed":false,"internalType":"bytes32","name":"exitRoot","type":"bytes32"},{"indexed":true,"internalType":"address","name":"aggregator","type":"address"}],"name":"VerifyBatches","type":"event"},{"inputs":[],"name":"getRollupExitRoot","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"globalExitRootManager","outputs":[{"internalType":"contract IPolygonZkEVMGlobalExitRootV2","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"rollupCount","outputs":[{"internalType":"uint32","name":"","type":"uint32"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint32","name":"rollupID","type":"uint32"}],"name":"rollupIDToLastExitRoot","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint32","name":"rollupID","type":"uint32"},{"internalType":"uint64","name":"finalNewBatch","type":"uint64"},{"internalType":"bytes32","name":"newLocalExitRoot","type":"bytes32"},{"internalType":"bytes32","name":"newStateRoot","type":"bytes32"},{"internalType":"bool","name":"updateGER","type":"bool"}],"name":"verifyBatches","outputs":[],"stateMutability":"nonpayable","type":"function"}] \ No newline at end of file +[{"inputs":[{"internalType":"contract IPolygonZkEVMGlobalExitRootV2","name":"_globalExitRootManager","type":"address"}],"stateMutability":"nonpayable","type":"constructor"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint32","name":"rollupID","type":"uint32"},{"indexed":false,"internalType":"uint64","name":"numBatch","type":"uint64"},{"indexed":false,"internalType":"bytes32","name":"stateRoot","type":"bytes32"},{"indexed":false,"internalType":"bytes32","name":"exitRoot","type":"bytes32"},{"indexed":true,"internalType":"address","name":"aggregator","type":"address"}],"name":"VerifyBatches","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint32","name":"rollupID","type":"uint32"},{"indexed":false,"internalType":"uint64","name":"numBatch","type":"uint64"},{"indexed":false,"internalType":"bytes32","name":"stateRoot","type":"bytes32"},{"indexed":false,"internalType":"bytes32","name":"exitRoot","type":"bytes32"},{"indexed":true,"internalType":"address","name":"aggregator","type":"address"}],"name":"VerifyBatchesTrustedAggregator","type":"event"},{"inputs":[],"name":"getRollupExitRoot","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"globalExitRootManager","outputs":[{"internalType":"contract IPolygonZkEVMGlobalExitRootV2","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"rollupCount","outputs":[{"internalType":"uint32","name":"","type":"uint32"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint32","name":"rollupID","type":"uint32"}],"name":"rollupIDToLastExitRoot","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint32","name":"rollupID","type":"uint32"},{"internalType":"uint64","name":"finalNewBatch","type":"uint64"},{"internalType":"bytes32","name":"newLocalExitRoot","type":"bytes32"},{"internalType":"bytes32","name":"newStateRoot","type":"bytes32"},{"internalType":"bool","name":"updateGER","type":"bool"}],"name":"verifyBatches","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint32","name":"rollupID","type":"uint32"},{"internalType":"uint64","name":"finalNewBatch","type":"uint64"},{"internalType":"bytes32","name":"newLocalExitRoot","type":"bytes32"},{"internalType":"bytes32","name":"newStateRoot","type":"bytes32"},{"internalType":"bool","name":"updateGER","type":"bool"}],"name":"verifyBatchesTrustedAggregator","outputs":[],"stateMutability":"nonpayable","type":"function"}] \ No newline at end of file diff --git a/test/contracts/bin/verifybatchesmock.bin b/test/contracts/bin/verifybatchesmock.bin index 17badba8..fd4e6d15 100644 --- a/test/contracts/bin/verifybatchesmock.bin +++ b/test/contracts/bin/verifybatchesmock.bin @@ -1 +1 @@ -60a060405234801561001057600080fd5b5060405161082938038061082983398101604081905261002f91610040565b6001600160a01b0316608052610070565b60006020828403121561005257600080fd5b81516001600160a01b038116811461006957600080fd5b9392505050565b60805161079861009160003960008181609c01526104e301526107986000f3fe608060405234801561001057600080fd5b50600436106100575760003560e01c80630680cf5c1461005c578063a2967d991461008f578063d02103ca14610097578063db3abdb9146100d6578063f4e92675146100eb575b600080fd5b61007c61006a3660046105de565b60016020526000908152604090205481565b6040519081526020015b60405180910390f35b61007c610110565b6100be7f000000000000000000000000000000000000000000000000000000000000000081565b6040516001600160a01b039091168152602001610086565b6100e96100e4366004610600565b610499565b005b6000546100fb9063ffffffff1681565b60405163ffffffff9091168152602001610086565b6000805463ffffffff1680820361012957506000919050565b60008167ffffffffffffffff8111156101445761014461066f565b60405190808252806020026020018201604052801561016d578160200160208202803683370190505b50905060005b828110156101d35760016000610189838361069b565b63ffffffff1663ffffffff168152602001908152602001600020548282815181106101b6576101b66106b4565b6020908102919091010152806101cb816106ca565b915050610173565b50600060205b836001146103fd5760006101ee6002866106f9565b6101f960028761070d565b610203919061069b565b905060008167ffffffffffffffff8111156102205761022061066f565b604051908082528060200260200182016040528015610249578160200160208202803683370190505b50905060005b828110156103ad57610262600184610721565b8114801561027a57506102766002886106f9565b6001145b156102f7578561028b826002610734565b8151811061029b5761029b6106b4565b6020026020010151856040516020016102be929190918252602082015260400190565b604051602081830303815290604052805190602001208282815181106102e6576102e66106b4565b60200260200101818152505061039b565b85610303826002610734565b81518110610313576103136106b4565b6020026020010151868260026103299190610734565b61033490600161069b565b81518110610344576103446106b4565b6020026020010151604051602001610366929190918252602082015260400190565b6040516020818303038152906040528051906020012082828151811061038e5761038e6106b4565b6020026020010181815250505b806103a5816106ca565b91505061024f565b5080945081955083846040516020016103d0929190918252602082015260400190565b60405160208183030381529060405280519060200120935082806103f39061074b565b93505050506101d9565b600083600081518110610412576104126106b4565b6020026020010151905060005b8281101561048f57604080516020810184905290810185905260600160408051601f19818403018152828252805160209182012090830187905290820186905292506060016040516020818303038152906040528051906020012093508080610487906106ca565b91505061041f565b5095945050505050565b60005463ffffffff90811690861611156104c3576000805463ffffffff191663ffffffff87161790555b63ffffffff851660009081526001602052604090208390558015610569577f00000000000000000000000000000000000000000000000000000000000000006001600160a01b03166333d6247d610518610110565b6040518263ffffffff1660e01b815260040161053691815260200190565b600060405180830381600087803b15801561055057600080fd5b505af1158015610564573d6000803e3d6000fd5b505050505b6040805167ffffffffffffffff8616815260208101849052908101849052339063ffffffff8716907faac1e7a157b259544ebacd6e8a82ae5d6c8f174e12aa48696277bcc9a661f0b49060600160405180910390a35050505050565b803563ffffffff811681146105d957600080fd5b919050565b6000602082840312156105f057600080fd5b6105f9826105c5565b9392505050565b600080600080600060a0868803121561061857600080fd5b610621866105c5565b9450602086013567ffffffffffffffff8116811461063e57600080fd5b935060408601359250606086013591506080860135801515811461066157600080fd5b809150509295509295909350565b634e487b7160e01b600052604160045260246000fd5b634e487b7160e01b600052601160045260246000fd5b808201808211156106ae576106ae610685565b92915050565b634e487b7160e01b600052603260045260246000fd5b6000600182016106dc576106dc610685565b5060010190565b634e487b7160e01b600052601260045260246000fd5b600082610708576107086106e3565b500690565b60008261071c5761071c6106e3565b500490565b818103818111156106ae576106ae610685565b80820281158282048414176106ae576106ae610685565b60008161075a5761075a610685565b50600019019056fea26469706673582212205adc139a1c2a423d3d8d0db882b69ac1b5cdcb3419bc6315ca33eeac9aa68a7464736f6c63430008120033 \ No newline at end of file +60a060405234801561001057600080fd5b5060405161097138038061097183398101604081905261002f91610040565b6001600160a01b0316608052610070565b60006020828403121561005257600080fd5b81516001600160a01b038116811461006957600080fd5b9392505050565b6080516108d96100986000396000818160bc01528181610178015261062e01526108d96000f3fe608060405234801561001057600080fd5b50600436106100625760003560e01c80630680cf5c1461006757806343955dd31461009a578063a2967d99146100af578063d02103ca146100b7578063db3abdb9146100f6578063f4e9267514610109575b600080fd5b61008761007536600461071f565b60016020526000908152604090205481565b6040519081526020015b60405180910390f35b6100ad6100a8366004610741565b61012e565b005b61008761025b565b6100de7f000000000000000000000000000000000000000000000000000000000000000081565b6040516001600160a01b039091168152602001610091565b6100ad610104366004610741565b6105e4565b6000546101199063ffffffff1681565b60405163ffffffff9091168152602001610091565b60005463ffffffff9081169086161115610158576000805463ffffffff191663ffffffff87161790555b63ffffffff8516600090815260016020526040902083905580156101fe577f00000000000000000000000000000000000000000000000000000000000000006001600160a01b03166333d6247d6101ad61025b565b6040518263ffffffff1660e01b81526004016101cb91815260200190565b600060405180830381600087803b1580156101e557600080fd5b505af11580156101f9573d6000803e3d6000fd5b505050505b6040805167ffffffffffffffff8616815260208101849052908101849052339063ffffffff8716907fd1ec3a1216f08b6eff72e169ceb548b782db18a6614852618d86bb19f3f9b0d3906060015b60405180910390a35050505050565b6000805463ffffffff1680820361027457506000919050565b60008167ffffffffffffffff81111561028f5761028f6107b0565b6040519080825280602002602001820160405280156102b8578160200160208202803683370190505b50905060005b8281101561031e57600160006102d483836107dc565b63ffffffff1663ffffffff16815260200190815260200160002054828281518110610301576103016107f5565b6020908102919091010152806103168161080b565b9150506102be565b50600060205b8360011461054857600061033960028661083a565b61034460028761084e565b61034e91906107dc565b905060008167ffffffffffffffff81111561036b5761036b6107b0565b604051908082528060200260200182016040528015610394578160200160208202803683370190505b50905060005b828110156104f8576103ad600184610862565b811480156103c557506103c160028861083a565b6001145b1561044257856103d6826002610875565b815181106103e6576103e66107f5565b602002602001015185604051602001610409929190918252602082015260400190565b60405160208183030381529060405280519060200120828281518110610431576104316107f5565b6020026020010181815250506104e6565b8561044e826002610875565b8151811061045e5761045e6107f5565b6020026020010151868260026104749190610875565b61047f9060016107dc565b8151811061048f5761048f6107f5565b60200260200101516040516020016104b1929190918252602082015260400190565b604051602081830303815290604052805190602001208282815181106104d9576104d96107f5565b6020026020010181815250505b806104f08161080b565b91505061039a565b50809450819550838460405160200161051b929190918252602082015260400190565b604051602081830303815290604052805190602001209350828061053e9061088c565b9350505050610324565b60008360008151811061055d5761055d6107f5565b6020026020010151905060005b828110156105da57604080516020810184905290810185905260600160408051601f198184030181528282528051602091820120908301879052908201869052925060600160405160208183030381529060405280519060200120935080806105d29061080b565b91505061056a565b5095945050505050565b60005463ffffffff908116908616111561060e576000805463ffffffff191663ffffffff87161790555b63ffffffff8516600090815260016020526040902083905580156106b4577f00000000000000000000000000000000000000000000000000000000000000006001600160a01b03166333d6247d61066361025b565b6040518263ffffffff1660e01b815260040161068191815260200190565b600060405180830381600087803b15801561069b57600080fd5b505af11580156106af573d6000803e3d6000fd5b505050505b6040805167ffffffffffffffff8616815260208101849052908101849052339063ffffffff8716907faac1e7a157b259544ebacd6e8a82ae5d6c8f174e12aa48696277bcc9a661f0b49060600161024c565b803563ffffffff8116811461071a57600080fd5b919050565b60006020828403121561073157600080fd5b61073a82610706565b9392505050565b600080600080600060a0868803121561075957600080fd5b61076286610706565b9450602086013567ffffffffffffffff8116811461077f57600080fd5b93506040860135925060608601359150608086013580151581146107a257600080fd5b809150509295509295909350565b634e487b7160e01b600052604160045260246000fd5b634e487b7160e01b600052601160045260246000fd5b808201808211156107ef576107ef6107c6565b92915050565b634e487b7160e01b600052603260045260246000fd5b60006001820161081d5761081d6107c6565b5060010190565b634e487b7160e01b600052601260045260246000fd5b60008261084957610849610824565b500690565b60008261085d5761085d610824565b500490565b818103818111156107ef576107ef6107c6565b80820281158282048414176107ef576107ef6107c6565b60008161089b5761089b6107c6565b50600019019056fea26469706673582212204b504ae2d3686f35f611e3ef5bc38d1f2d64ce4fea28c7a2a657dbe4ba6178ce64736f6c63430008120033 \ No newline at end of file diff --git a/test/contracts/verifybatchesmock/VerifyBatchesMock.sol b/test/contracts/verifybatchesmock/VerifyBatchesMock.sol index 6a65a548..34db310a 100644 --- a/test/contracts/verifybatchesmock/VerifyBatchesMock.sol +++ b/test/contracts/verifybatchesmock/VerifyBatchesMock.sol @@ -35,6 +35,14 @@ contract VerifyBatchesMock { address indexed aggregator ); + event VerifyBatchesTrustedAggregator( + uint32 indexed rollupID, + uint64 numBatch, + bytes32 stateRoot, + bytes32 exitRoot, + address indexed aggregator + ); + constructor( IPolygonZkEVMGlobalExitRootV2 _globalExitRootManager ) { @@ -65,6 +73,30 @@ contract VerifyBatchesMock { ); } + function verifyBatchesTrustedAggregator( + uint32 rollupID, + uint64 finalNewBatch, + bytes32 newLocalExitRoot, + bytes32 newStateRoot, + bool updateGER + ) external { + if (rollupID > rollupCount) { + rollupCount = rollupID; + } + rollupIDToLastExitRoot[rollupID] = newLocalExitRoot; + if (updateGER) { + globalExitRootManager.updateExitRoot(getRollupExitRoot()); + } + + emit VerifyBatchesTrustedAggregator( + rollupID, + finalNewBatch, + newStateRoot, + newLocalExitRoot, + msg.sender + ); + } + function getRollupExitRoot() public view returns (bytes32) { uint256 currentNodes = rollupCount; diff --git a/test/contracts/verifybatchesmock/verifybatchesmock.go b/test/contracts/verifybatchesmock/verifybatchesmock.go index 10fc3b8d..58b67630 100644 --- a/test/contracts/verifybatchesmock/verifybatchesmock.go +++ b/test/contracts/verifybatchesmock/verifybatchesmock.go @@ -31,8 +31,8 @@ var ( // VerifybatchesmockMetaData contains all meta data concerning the Verifybatchesmock contract. var VerifybatchesmockMetaData = &bind.MetaData{ - ABI: "[{\"inputs\":[{\"internalType\":\"contractIPolygonZkEVMGlobalExitRootV2\",\"name\":\"_globalExitRootManager\",\"type\":\"address\"}],\"stateMutability\":\"nonpayable\",\"type\":\"constructor\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"uint32\",\"name\":\"rollupID\",\"type\":\"uint32\"},{\"indexed\":false,\"internalType\":\"uint64\",\"name\":\"numBatch\",\"type\":\"uint64\"},{\"indexed\":false,\"internalType\":\"bytes32\",\"name\":\"stateRoot\",\"type\":\"bytes32\"},{\"indexed\":false,\"internalType\":\"bytes32\",\"name\":\"exitRoot\",\"type\":\"bytes32\"},{\"indexed\":true,\"internalType\":\"address\",\"name\":\"aggregator\",\"type\":\"address\"}],\"name\":\"VerifyBatches\",\"type\":\"event\"},{\"inputs\":[],\"name\":\"getRollupExitRoot\",\"outputs\":[{\"internalType\":\"bytes32\",\"name\":\"\",\"type\":\"bytes32\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"globalExitRootManager\",\"outputs\":[{\"internalType\":\"contractIPolygonZkEVMGlobalExitRootV2\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"rollupCount\",\"outputs\":[{\"internalType\":\"uint32\",\"name\":\"\",\"type\":\"uint32\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint32\",\"name\":\"rollupID\",\"type\":\"uint32\"}],\"name\":\"rollupIDToLastExitRoot\",\"outputs\":[{\"internalType\":\"bytes32\",\"name\":\"\",\"type\":\"bytes32\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint32\",\"name\":\"rollupID\",\"type\":\"uint32\"},{\"internalType\":\"uint64\",\"name\":\"finalNewBatch\",\"type\":\"uint64\"},{\"internalType\":\"bytes32\",\"name\":\"newLocalExitRoot\",\"type\":\"bytes32\"},{\"internalType\":\"bytes32\",\"name\":\"newStateRoot\",\"type\":\"bytes32\"},{\"internalType\":\"bool\",\"name\":\"updateGER\",\"type\":\"bool\"}],\"name\":\"verifyBatches\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"}]", - Bin: "0x60a060405234801561001057600080fd5b5060405161082938038061082983398101604081905261002f91610040565b6001600160a01b0316608052610070565b60006020828403121561005257600080fd5b81516001600160a01b038116811461006957600080fd5b9392505050565b60805161079861009160003960008181609c01526104e301526107986000f3fe608060405234801561001057600080fd5b50600436106100575760003560e01c80630680cf5c1461005c578063a2967d991461008f578063d02103ca14610097578063db3abdb9146100d6578063f4e92675146100eb575b600080fd5b61007c61006a3660046105de565b60016020526000908152604090205481565b6040519081526020015b60405180910390f35b61007c610110565b6100be7f000000000000000000000000000000000000000000000000000000000000000081565b6040516001600160a01b039091168152602001610086565b6100e96100e4366004610600565b610499565b005b6000546100fb9063ffffffff1681565b60405163ffffffff9091168152602001610086565b6000805463ffffffff1680820361012957506000919050565b60008167ffffffffffffffff8111156101445761014461066f565b60405190808252806020026020018201604052801561016d578160200160208202803683370190505b50905060005b828110156101d35760016000610189838361069b565b63ffffffff1663ffffffff168152602001908152602001600020548282815181106101b6576101b66106b4565b6020908102919091010152806101cb816106ca565b915050610173565b50600060205b836001146103fd5760006101ee6002866106f9565b6101f960028761070d565b610203919061069b565b905060008167ffffffffffffffff8111156102205761022061066f565b604051908082528060200260200182016040528015610249578160200160208202803683370190505b50905060005b828110156103ad57610262600184610721565b8114801561027a57506102766002886106f9565b6001145b156102f7578561028b826002610734565b8151811061029b5761029b6106b4565b6020026020010151856040516020016102be929190918252602082015260400190565b604051602081830303815290604052805190602001208282815181106102e6576102e66106b4565b60200260200101818152505061039b565b85610303826002610734565b81518110610313576103136106b4565b6020026020010151868260026103299190610734565b61033490600161069b565b81518110610344576103446106b4565b6020026020010151604051602001610366929190918252602082015260400190565b6040516020818303038152906040528051906020012082828151811061038e5761038e6106b4565b6020026020010181815250505b806103a5816106ca565b91505061024f565b5080945081955083846040516020016103d0929190918252602082015260400190565b60405160208183030381529060405280519060200120935082806103f39061074b565b93505050506101d9565b600083600081518110610412576104126106b4565b6020026020010151905060005b8281101561048f57604080516020810184905290810185905260600160408051601f19818403018152828252805160209182012090830187905290820186905292506060016040516020818303038152906040528051906020012093508080610487906106ca565b91505061041f565b5095945050505050565b60005463ffffffff90811690861611156104c3576000805463ffffffff191663ffffffff87161790555b63ffffffff851660009081526001602052604090208390558015610569577f00000000000000000000000000000000000000000000000000000000000000006001600160a01b03166333d6247d610518610110565b6040518263ffffffff1660e01b815260040161053691815260200190565b600060405180830381600087803b15801561055057600080fd5b505af1158015610564573d6000803e3d6000fd5b505050505b6040805167ffffffffffffffff8616815260208101849052908101849052339063ffffffff8716907faac1e7a157b259544ebacd6e8a82ae5d6c8f174e12aa48696277bcc9a661f0b49060600160405180910390a35050505050565b803563ffffffff811681146105d957600080fd5b919050565b6000602082840312156105f057600080fd5b6105f9826105c5565b9392505050565b600080600080600060a0868803121561061857600080fd5b610621866105c5565b9450602086013567ffffffffffffffff8116811461063e57600080fd5b935060408601359250606086013591506080860135801515811461066157600080fd5b809150509295509295909350565b634e487b7160e01b600052604160045260246000fd5b634e487b7160e01b600052601160045260246000fd5b808201808211156106ae576106ae610685565b92915050565b634e487b7160e01b600052603260045260246000fd5b6000600182016106dc576106dc610685565b5060010190565b634e487b7160e01b600052601260045260246000fd5b600082610708576107086106e3565b500690565b60008261071c5761071c6106e3565b500490565b818103818111156106ae576106ae610685565b80820281158282048414176106ae576106ae610685565b60008161075a5761075a610685565b50600019019056fea26469706673582212205adc139a1c2a423d3d8d0db882b69ac1b5cdcb3419bc6315ca33eeac9aa68a7464736f6c63430008120033", + ABI: "[{\"inputs\":[{\"internalType\":\"contractIPolygonZkEVMGlobalExitRootV2\",\"name\":\"_globalExitRootManager\",\"type\":\"address\"}],\"stateMutability\":\"nonpayable\",\"type\":\"constructor\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"uint32\",\"name\":\"rollupID\",\"type\":\"uint32\"},{\"indexed\":false,\"internalType\":\"uint64\",\"name\":\"numBatch\",\"type\":\"uint64\"},{\"indexed\":false,\"internalType\":\"bytes32\",\"name\":\"stateRoot\",\"type\":\"bytes32\"},{\"indexed\":false,\"internalType\":\"bytes32\",\"name\":\"exitRoot\",\"type\":\"bytes32\"},{\"indexed\":true,\"internalType\":\"address\",\"name\":\"aggregator\",\"type\":\"address\"}],\"name\":\"VerifyBatches\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"uint32\",\"name\":\"rollupID\",\"type\":\"uint32\"},{\"indexed\":false,\"internalType\":\"uint64\",\"name\":\"numBatch\",\"type\":\"uint64\"},{\"indexed\":false,\"internalType\":\"bytes32\",\"name\":\"stateRoot\",\"type\":\"bytes32\"},{\"indexed\":false,\"internalType\":\"bytes32\",\"name\":\"exitRoot\",\"type\":\"bytes32\"},{\"indexed\":true,\"internalType\":\"address\",\"name\":\"aggregator\",\"type\":\"address\"}],\"name\":\"VerifyBatchesTrustedAggregator\",\"type\":\"event\"},{\"inputs\":[],\"name\":\"getRollupExitRoot\",\"outputs\":[{\"internalType\":\"bytes32\",\"name\":\"\",\"type\":\"bytes32\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"globalExitRootManager\",\"outputs\":[{\"internalType\":\"contractIPolygonZkEVMGlobalExitRootV2\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"rollupCount\",\"outputs\":[{\"internalType\":\"uint32\",\"name\":\"\",\"type\":\"uint32\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint32\",\"name\":\"rollupID\",\"type\":\"uint32\"}],\"name\":\"rollupIDToLastExitRoot\",\"outputs\":[{\"internalType\":\"bytes32\",\"name\":\"\",\"type\":\"bytes32\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint32\",\"name\":\"rollupID\",\"type\":\"uint32\"},{\"internalType\":\"uint64\",\"name\":\"finalNewBatch\",\"type\":\"uint64\"},{\"internalType\":\"bytes32\",\"name\":\"newLocalExitRoot\",\"type\":\"bytes32\"},{\"internalType\":\"bytes32\",\"name\":\"newStateRoot\",\"type\":\"bytes32\"},{\"internalType\":\"bool\",\"name\":\"updateGER\",\"type\":\"bool\"}],\"name\":\"verifyBatches\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint32\",\"name\":\"rollupID\",\"type\":\"uint32\"},{\"internalType\":\"uint64\",\"name\":\"finalNewBatch\",\"type\":\"uint64\"},{\"internalType\":\"bytes32\",\"name\":\"newLocalExitRoot\",\"type\":\"bytes32\"},{\"internalType\":\"bytes32\",\"name\":\"newStateRoot\",\"type\":\"bytes32\"},{\"internalType\":\"bool\",\"name\":\"updateGER\",\"type\":\"bool\"}],\"name\":\"verifyBatchesTrustedAggregator\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"}]", + Bin: "0x60a060405234801561001057600080fd5b5060405161097138038061097183398101604081905261002f91610040565b6001600160a01b0316608052610070565b60006020828403121561005257600080fd5b81516001600160a01b038116811461006957600080fd5b9392505050565b6080516108d96100986000396000818160bc01528181610178015261062e01526108d96000f3fe608060405234801561001057600080fd5b50600436106100625760003560e01c80630680cf5c1461006757806343955dd31461009a578063a2967d99146100af578063d02103ca146100b7578063db3abdb9146100f6578063f4e9267514610109575b600080fd5b61008761007536600461071f565b60016020526000908152604090205481565b6040519081526020015b60405180910390f35b6100ad6100a8366004610741565b61012e565b005b61008761025b565b6100de7f000000000000000000000000000000000000000000000000000000000000000081565b6040516001600160a01b039091168152602001610091565b6100ad610104366004610741565b6105e4565b6000546101199063ffffffff1681565b60405163ffffffff9091168152602001610091565b60005463ffffffff9081169086161115610158576000805463ffffffff191663ffffffff87161790555b63ffffffff8516600090815260016020526040902083905580156101fe577f00000000000000000000000000000000000000000000000000000000000000006001600160a01b03166333d6247d6101ad61025b565b6040518263ffffffff1660e01b81526004016101cb91815260200190565b600060405180830381600087803b1580156101e557600080fd5b505af11580156101f9573d6000803e3d6000fd5b505050505b6040805167ffffffffffffffff8616815260208101849052908101849052339063ffffffff8716907fd1ec3a1216f08b6eff72e169ceb548b782db18a6614852618d86bb19f3f9b0d3906060015b60405180910390a35050505050565b6000805463ffffffff1680820361027457506000919050565b60008167ffffffffffffffff81111561028f5761028f6107b0565b6040519080825280602002602001820160405280156102b8578160200160208202803683370190505b50905060005b8281101561031e57600160006102d483836107dc565b63ffffffff1663ffffffff16815260200190815260200160002054828281518110610301576103016107f5565b6020908102919091010152806103168161080b565b9150506102be565b50600060205b8360011461054857600061033960028661083a565b61034460028761084e565b61034e91906107dc565b905060008167ffffffffffffffff81111561036b5761036b6107b0565b604051908082528060200260200182016040528015610394578160200160208202803683370190505b50905060005b828110156104f8576103ad600184610862565b811480156103c557506103c160028861083a565b6001145b1561044257856103d6826002610875565b815181106103e6576103e66107f5565b602002602001015185604051602001610409929190918252602082015260400190565b60405160208183030381529060405280519060200120828281518110610431576104316107f5565b6020026020010181815250506104e6565b8561044e826002610875565b8151811061045e5761045e6107f5565b6020026020010151868260026104749190610875565b61047f9060016107dc565b8151811061048f5761048f6107f5565b60200260200101516040516020016104b1929190918252602082015260400190565b604051602081830303815290604052805190602001208282815181106104d9576104d96107f5565b6020026020010181815250505b806104f08161080b565b91505061039a565b50809450819550838460405160200161051b929190918252602082015260400190565b604051602081830303815290604052805190602001209350828061053e9061088c565b9350505050610324565b60008360008151811061055d5761055d6107f5565b6020026020010151905060005b828110156105da57604080516020810184905290810185905260600160408051601f198184030181528282528051602091820120908301879052908201869052925060600160405160208183030381529060405280519060200120935080806105d29061080b565b91505061056a565b5095945050505050565b60005463ffffffff908116908616111561060e576000805463ffffffff191663ffffffff87161790555b63ffffffff8516600090815260016020526040902083905580156106b4577f00000000000000000000000000000000000000000000000000000000000000006001600160a01b03166333d6247d61066361025b565b6040518263ffffffff1660e01b815260040161068191815260200190565b600060405180830381600087803b15801561069b57600080fd5b505af11580156106af573d6000803e3d6000fd5b505050505b6040805167ffffffffffffffff8616815260208101849052908101849052339063ffffffff8716907faac1e7a157b259544ebacd6e8a82ae5d6c8f174e12aa48696277bcc9a661f0b49060600161024c565b803563ffffffff8116811461071a57600080fd5b919050565b60006020828403121561073157600080fd5b61073a82610706565b9392505050565b600080600080600060a0868803121561075957600080fd5b61076286610706565b9450602086013567ffffffffffffffff8116811461077f57600080fd5b93506040860135925060608601359150608086013580151581146107a257600080fd5b809150509295509295909350565b634e487b7160e01b600052604160045260246000fd5b634e487b7160e01b600052601160045260246000fd5b808201808211156107ef576107ef6107c6565b92915050565b634e487b7160e01b600052603260045260246000fd5b60006001820161081d5761081d6107c6565b5060010190565b634e487b7160e01b600052601260045260246000fd5b60008261084957610849610824565b500690565b60008261085d5761085d610824565b500490565b818103818111156107ef576107ef6107c6565b80820281158282048414176107ef576107ef6107c6565b60008161089b5761089b6107c6565b50600019019056fea26469706673582212204b504ae2d3686f35f611e3ef5bc38d1f2d64ce4fea28c7a2a657dbe4ba6178ce64736f6c63430008120033", } // VerifybatchesmockABI is the input ABI used to generate the binding from. @@ -347,6 +347,27 @@ func (_Verifybatchesmock *VerifybatchesmockTransactorSession) VerifyBatches(roll return _Verifybatchesmock.Contract.VerifyBatches(&_Verifybatchesmock.TransactOpts, rollupID, finalNewBatch, newLocalExitRoot, newStateRoot, updateGER) } +// VerifyBatchesTrustedAggregator is a paid mutator transaction binding the contract method 0x43955dd3. +// +// Solidity: function verifyBatchesTrustedAggregator(uint32 rollupID, uint64 finalNewBatch, bytes32 newLocalExitRoot, bytes32 newStateRoot, bool updateGER) returns() +func (_Verifybatchesmock *VerifybatchesmockTransactor) VerifyBatchesTrustedAggregator(opts *bind.TransactOpts, rollupID uint32, finalNewBatch uint64, newLocalExitRoot [32]byte, newStateRoot [32]byte, updateGER bool) (*types.Transaction, error) { + return _Verifybatchesmock.contract.Transact(opts, "verifyBatchesTrustedAggregator", rollupID, finalNewBatch, newLocalExitRoot, newStateRoot, updateGER) +} + +// VerifyBatchesTrustedAggregator is a paid mutator transaction binding the contract method 0x43955dd3. +// +// Solidity: function verifyBatchesTrustedAggregator(uint32 rollupID, uint64 finalNewBatch, bytes32 newLocalExitRoot, bytes32 newStateRoot, bool updateGER) returns() +func (_Verifybatchesmock *VerifybatchesmockSession) VerifyBatchesTrustedAggregator(rollupID uint32, finalNewBatch uint64, newLocalExitRoot [32]byte, newStateRoot [32]byte, updateGER bool) (*types.Transaction, error) { + return _Verifybatchesmock.Contract.VerifyBatchesTrustedAggregator(&_Verifybatchesmock.TransactOpts, rollupID, finalNewBatch, newLocalExitRoot, newStateRoot, updateGER) +} + +// VerifyBatchesTrustedAggregator is a paid mutator transaction binding the contract method 0x43955dd3. +// +// Solidity: function verifyBatchesTrustedAggregator(uint32 rollupID, uint64 finalNewBatch, bytes32 newLocalExitRoot, bytes32 newStateRoot, bool updateGER) returns() +func (_Verifybatchesmock *VerifybatchesmockTransactorSession) VerifyBatchesTrustedAggregator(rollupID uint32, finalNewBatch uint64, newLocalExitRoot [32]byte, newStateRoot [32]byte, updateGER bool) (*types.Transaction, error) { + return _Verifybatchesmock.Contract.VerifyBatchesTrustedAggregator(&_Verifybatchesmock.TransactOpts, rollupID, finalNewBatch, newLocalExitRoot, newStateRoot, updateGER) +} + // VerifybatchesmockVerifyBatchesIterator is returned from FilterVerifyBatches and is used to iterate over the raw logs and unpacked data for VerifyBatches events raised by the Verifybatchesmock contract. type VerifybatchesmockVerifyBatchesIterator struct { Event *VerifybatchesmockVerifyBatches // Event containing the contract specifics and raw log @@ -504,3 +525,161 @@ func (_Verifybatchesmock *VerifybatchesmockFilterer) ParseVerifyBatches(log type event.Raw = log return event, nil } + +// VerifybatchesmockVerifyBatchesTrustedAggregatorIterator is returned from FilterVerifyBatchesTrustedAggregator and is used to iterate over the raw logs and unpacked data for VerifyBatchesTrustedAggregator events raised by the Verifybatchesmock contract. +type VerifybatchesmockVerifyBatchesTrustedAggregatorIterator struct { + Event *VerifybatchesmockVerifyBatchesTrustedAggregator // Event containing the contract specifics and raw log + + contract *bind.BoundContract // Generic contract to use for unpacking event data + event string // Event name to use for unpacking event data + + logs chan types.Log // Log channel receiving the found contract events + sub ethereum.Subscription // Subscription for errors, completion and termination + done bool // Whether the subscription completed delivering logs + fail error // Occurred error to stop iteration +} + +// Next advances the iterator to the subsequent event, returning whether there +// are any more events found. In case of a retrieval or parsing error, false is +// returned and Error() can be queried for the exact failure. +func (it *VerifybatchesmockVerifyBatchesTrustedAggregatorIterator) Next() bool { + // If the iterator failed, stop iterating + if it.fail != nil { + return false + } + // If the iterator completed, deliver directly whatever's available + if it.done { + select { + case log := <-it.logs: + it.Event = new(VerifybatchesmockVerifyBatchesTrustedAggregator) + if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil { + it.fail = err + return false + } + it.Event.Raw = log + return true + + default: + return false + } + } + // Iterator still in progress, wait for either a data or an error event + select { + case log := <-it.logs: + it.Event = new(VerifybatchesmockVerifyBatchesTrustedAggregator) + if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil { + it.fail = err + return false + } + it.Event.Raw = log + return true + + case err := <-it.sub.Err(): + it.done = true + it.fail = err + return it.Next() + } +} + +// Error returns any retrieval or parsing error occurred during filtering. +func (it *VerifybatchesmockVerifyBatchesTrustedAggregatorIterator) Error() error { + return it.fail +} + +// Close terminates the iteration process, releasing any pending underlying +// resources. +func (it *VerifybatchesmockVerifyBatchesTrustedAggregatorIterator) Close() error { + it.sub.Unsubscribe() + return nil +} + +// VerifybatchesmockVerifyBatchesTrustedAggregator represents a VerifyBatchesTrustedAggregator event raised by the Verifybatchesmock contract. +type VerifybatchesmockVerifyBatchesTrustedAggregator struct { + RollupID uint32 + NumBatch uint64 + StateRoot [32]byte + ExitRoot [32]byte + Aggregator common.Address + Raw types.Log // Blockchain specific contextual infos +} + +// FilterVerifyBatchesTrustedAggregator is a free log retrieval operation binding the contract event 0xd1ec3a1216f08b6eff72e169ceb548b782db18a6614852618d86bb19f3f9b0d3. +// +// Solidity: event VerifyBatchesTrustedAggregator(uint32 indexed rollupID, uint64 numBatch, bytes32 stateRoot, bytes32 exitRoot, address indexed aggregator) +func (_Verifybatchesmock *VerifybatchesmockFilterer) FilterVerifyBatchesTrustedAggregator(opts *bind.FilterOpts, rollupID []uint32, aggregator []common.Address) (*VerifybatchesmockVerifyBatchesTrustedAggregatorIterator, error) { + + var rollupIDRule []interface{} + for _, rollupIDItem := range rollupID { + rollupIDRule = append(rollupIDRule, rollupIDItem) + } + + var aggregatorRule []interface{} + for _, aggregatorItem := range aggregator { + aggregatorRule = append(aggregatorRule, aggregatorItem) + } + + logs, sub, err := _Verifybatchesmock.contract.FilterLogs(opts, "VerifyBatchesTrustedAggregator", rollupIDRule, aggregatorRule) + if err != nil { + return nil, err + } + return &VerifybatchesmockVerifyBatchesTrustedAggregatorIterator{contract: _Verifybatchesmock.contract, event: "VerifyBatchesTrustedAggregator", logs: logs, sub: sub}, nil +} + +// WatchVerifyBatchesTrustedAggregator is a free log subscription operation binding the contract event 0xd1ec3a1216f08b6eff72e169ceb548b782db18a6614852618d86bb19f3f9b0d3. +// +// Solidity: event VerifyBatchesTrustedAggregator(uint32 indexed rollupID, uint64 numBatch, bytes32 stateRoot, bytes32 exitRoot, address indexed aggregator) +func (_Verifybatchesmock *VerifybatchesmockFilterer) WatchVerifyBatchesTrustedAggregator(opts *bind.WatchOpts, sink chan<- *VerifybatchesmockVerifyBatchesTrustedAggregator, rollupID []uint32, aggregator []common.Address) (event.Subscription, error) { + + var rollupIDRule []interface{} + for _, rollupIDItem := range rollupID { + rollupIDRule = append(rollupIDRule, rollupIDItem) + } + + var aggregatorRule []interface{} + for _, aggregatorItem := range aggregator { + aggregatorRule = append(aggregatorRule, aggregatorItem) + } + + logs, sub, err := _Verifybatchesmock.contract.WatchLogs(opts, "VerifyBatchesTrustedAggregator", rollupIDRule, aggregatorRule) + if err != nil { + return nil, err + } + return event.NewSubscription(func(quit <-chan struct{}) error { + defer sub.Unsubscribe() + for { + select { + case log := <-logs: + // New log arrived, parse the event and forward to the user + event := new(VerifybatchesmockVerifyBatchesTrustedAggregator) + if err := _Verifybatchesmock.contract.UnpackLog(event, "VerifyBatchesTrustedAggregator", log); err != nil { + return err + } + event.Raw = log + + select { + case sink <- event: + case err := <-sub.Err(): + return err + case <-quit: + return nil + } + case err := <-sub.Err(): + return err + case <-quit: + return nil + } + } + }), nil +} + +// ParseVerifyBatchesTrustedAggregator is a log parse operation binding the contract event 0xd1ec3a1216f08b6eff72e169ceb548b782db18a6614852618d86bb19f3f9b0d3. +// +// Solidity: event VerifyBatchesTrustedAggregator(uint32 indexed rollupID, uint64 numBatch, bytes32 stateRoot, bytes32 exitRoot, address indexed aggregator) +func (_Verifybatchesmock *VerifybatchesmockFilterer) ParseVerifyBatchesTrustedAggregator(log types.Log) (*VerifybatchesmockVerifyBatchesTrustedAggregator, error) { + event := new(VerifybatchesmockVerifyBatchesTrustedAggregator) + if err := _Verifybatchesmock.contract.UnpackLog(event, "VerifyBatchesTrustedAggregator", log); err != nil { + return nil, err + } + event.Raw = log + return event, nil +} diff --git a/test/helpers/common-setup.bash b/test/helpers/common-setup.bash index dac81beb..5f53cbf8 100644 --- a/test/helpers/common-setup.bash +++ b/test/helpers/common-setup.bash @@ -21,6 +21,6 @@ _common_setup() { readonly enclave=${KURTOSIS_ENCLAVE:-cdk} readonly contracts_container=${KURTOSIS_CONTRACTS:-contracts-001} readonly contracts_service_wrapper=${KURTOSIS_CONTRACTS_WRAPPER:-"kurtosis service exec $enclave $contracts_container"} - readonly erigon_rpc_node=${KURTOSIS_ERIGON_RPC:-cdk-erigon-node-001} + readonly erigon_rpc_node=${KURTOSIS_ERIGON_RPC:-cdk-erigon-rpc-001} readonly l2_rpc_url=${L2_ETH_RPC_URL:-"$(kurtosis port print $enclave $erigon_rpc_node rpc)"} } 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 diff --git a/test/scripts/batch_verification_monitor.sh b/test/scripts/batch_verification_monitor.sh index 9c923888..a0bfaefd 100755 --- a/test/scripts/batch_verification_monitor.sh +++ b/test/scripts/batch_verification_monitor.sh @@ -17,7 +17,7 @@ timeout="$2" start_time=$(date +%s) end_time=$((start_time + timeout)) -rpc_url="$(kurtosis port print cdk cdk-erigon-node-001 rpc)" +rpc_url="$(kurtosis port print cdk cdk-erigon-rpc-001 rpc)" while true; do verified_batches="$(cast to-dec "$(cast rpc --rpc-url "$rpc_url" zkevm_verifiedBatchNumber | sed 's/"//g')")" diff --git a/test/scripts/env.sh b/test/scripts/env.sh index 063b7d61..298d4f73 100644 --- a/test/scripts/env.sh +++ b/test/scripts/env.sh @@ -3,5 +3,5 @@ KURTOSIS_ENCLAVE=cdk TMP_CDK_FOLDER=tmp/cdk DEST_KURTOSIS_PARAMS_YML=../$TMP_CDK_FOLDER/e2e-params.yml -KURTOSIS_FOLDER=../kurtosis-cdk +KURTOSIS_FOLDER=${KURTOSIS_FOLDER:=../kurtosis-cdk} USE_L1_GAS_TOKEN_CONTRACT=true