diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 1fa8d75904..8d2bb89c13 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -62,6 +62,7 @@ jobs: permissions: actions: read contents: read + packages: read uses: ./.github/workflows/test.yml secrets: inherit diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index ace7a971fb..ce13bd2f80 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -35,6 +35,9 @@ jobs: unit_test: name: Run Unit Tests runs-on: ubuntu-latest + permissions: + contents: read + packages: read steps: - uses: actions/checkout@v6 - name: set up go @@ -70,6 +73,9 @@ jobs: name: Run E2E System Tests needs: build_all-apps runs-on: ubuntu-latest + permissions: + contents: read + packages: read steps: - uses: actions/checkout@v6 - name: set up go diff --git a/.mockery.yaml b/.mockery.yaml index 5393a82a47..5944b9d1ae 100644 --- a/.mockery.yaml +++ b/.mockery.yaml @@ -2,15 +2,6 @@ template: testify template-data: unroll-variadic: true packages: - github.com/evstack/ev-node/core/da: - interfaces: - DA: - config: - pkgname: mocks - filename: da.go - configs: - - dir: ./da/internal/mocks - - dir: ./test/mocks github.com/evstack/ev-node/core/execution: interfaces: Executor: @@ -67,11 +58,18 @@ packages: filename: broadcaster_mock.go github.com/evstack/ev-node/block/internal/da: interfaces: - BlobAPI: + Client: config: - dir: ./block/internal/da - pkgname: da - filename: blob_api_mock.go + dir: ./test/mocks + pkgname: mocks + filename: da.go + github.com/evstack/ev-node/pkg/da/types: + interfaces: + Verifier: + config: + dir: ./test/mocks + pkgname: mocks + filename: da_verifier.go github.com/evstack/ev-node/pkg/da/jsonrpc: interfaces: BlobModule: diff --git a/CHANGELOG.md b/CHANGELOG.md index f8d6bd3e91..438bc7fec6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -27,6 +27,13 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Removed - **BREAKING:** Removed unused and confusing metrics from sequencers and block processing, including sequencer-specific metrics (gas price, blob size, transaction status, pending blocks), channel buffer metrics, overly granular error metrics, block production categorization metrics, and sync lag metrics. Essential metrics for DA submission health, block production, and performance monitoring are retained. [#2904](https://github.com/evstack/ev-node/pull/2904) +- **BREAKING**: Removed `core/da` package and replaced DAClient with internal implementation. The DA client is exposed as `block.FullDAClient`, `block.DAClient`, `block.DAVerifier` without leaking implementation details. [#2910](https://github.com/evstack/ev-node/pull/2910) + +## v1.0.0-beta.11 + +### Improvements + +- Loosen syncer validation for allowing swapping sequencer and full node state [#2925](https://github.com/evstack/ev-node/pull/2925) ## v1.0.0-beta.10 diff --git a/CLAUDE.md b/CLAUDE.md index 70bcf6a9ac..c0688f0a83 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -52,7 +52,7 @@ The project uses a zero-dependency core package pattern: - **Executor** (core/executor.go) - Handles state transitions - **Sequencer** (core/sequencer.go) - Orders transactions -- **DA** (core/da.go) - Data availability layer abstraction +- **DA** (pkg/da/types) - Data availability layer abstraction ### Modular Design @@ -120,7 +120,7 @@ go test -race ./package/... ### Adding a New DA Layer -1. Implement the `DA` interface from `core/da.go` +1. Implement the `DA` interface from `pkg/da/types` 2. Add configuration in the appropriate config package 3. Wire it up in the initialization code 4. Add tests following existing patterns diff --git a/RELEASE.md b/RELEASE.md index 8d890800cd..fd11b700ca 100644 --- a/RELEASE.md +++ b/RELEASE.md @@ -97,7 +97,6 @@ Packages must be released in the following order: These packages only depend on `core` and can be released in parallel after `core`: -1. **github.com/evstack/ev-node/da** - Path: `./da` 2. **github.com/evstack/ev-node** - Path: `./` (root) 3. **github.com/evstack/ev-node/execution/evm** - Path: `./execution/evm` @@ -157,7 +156,6 @@ git tag execution/evm/v0.3.0 git push origin execution/evm/v0.3.0 # Verify all are available -go list -m github.com/evstack/ev-node/da@v0.3.0 go list -m github.com/evstack/ev-node@v0.3.0 go list -m github.com/evstack/ev-node/execution/evm@v0.3.0 ``` @@ -170,7 +168,6 @@ After all dependencies are available: # Update and release apps/evm go get github.com/evstack/ev-node/core@v0.3.0 -go get github.com/evstack/ev-node/da@v0.3.0 go get github.com/evstack/ev-node/execution/evm@v0.3.0 go get github.com/evstack/ev-node@v0.3.0 go mod tidy diff --git a/apps/evm/README.md b/apps/evm/README.md index c52a0c9415..08b14645f7 100644 --- a/apps/evm/README.md +++ b/apps/evm/README.md @@ -11,7 +11,7 @@ This directory contains the implementation of a single EVM sequencer using Ev-no 1. Both EVM and DA layers must be running before starting the sequencer 1. For the EVM layer, Reth can be conveniently run using `docker compose` from /execution/evm/docker. - 2. For the DA layer, local-da can be built and run from the `ev-node/da/cmd/local-da` directory. + 2. For the DA layer, local-da can be built and run from the `ev-node/tools/local-da` directory. 2. Build the sequencer: diff --git a/apps/evm/cmd/post_tx_cmd.go b/apps/evm/cmd/post_tx_cmd.go index 7b03a0926a..211eadd6fa 100644 --- a/apps/evm/cmd/post_tx_cmd.go +++ b/apps/evm/cmd/post_tx_cmd.go @@ -11,12 +11,11 @@ import ( "github.com/spf13/cobra" evblock "github.com/evstack/ev-node/block" - "github.com/evstack/ev-node/core/da" - "github.com/evstack/ev-node/da/jsonrpc" rollcmd "github.com/evstack/ev-node/pkg/cmd" rollconf "github.com/evstack/ev-node/pkg/config" + blobrpc "github.com/evstack/ev-node/pkg/da/jsonrpc" + da "github.com/evstack/ev-node/pkg/da/types" genesispkg "github.com/evstack/ev-node/pkg/genesis" - seqcommon "github.com/evstack/ev-node/sequencers/common" "github.com/evstack/ev-node/types" ) @@ -117,12 +116,11 @@ func postTxRunE(cmd *cobra.Command, args []string) error { logger.Info().Str("namespace", namespace).Float64("gas_price", gasPrice).Int("tx_size", len(txData)).Msg("posting transaction to DA layer") - daClient, err := jsonrpc.NewClient( + daClient, err := blobrpc.NewClient( cmd.Context(), - logger, nodeConfig.DA.Address, nodeConfig.DA.AuthToken, - seqcommon.AbsoluteMaxBlobSize, + "", ) if err != nil { return fmt.Errorf("failed to create DA client: %w", err) @@ -134,7 +132,7 @@ func postTxRunE(cmd *cobra.Command, args []string) error { blobs := [][]byte{txData} options := []byte(nodeConfig.DA.SubmitOptions) - dac := evblock.NewDAClient(&daClient.DA, nodeConfig, logger) + dac := evblock.NewDAClient(daClient, nodeConfig, logger) result := dac.Submit(cmd.Context(), blobs, gasPrice, namespaceBz, options) // Check result diff --git a/apps/evm/cmd/run.go b/apps/evm/cmd/run.go index 06af78e61f..cb1431f133 100644 --- a/apps/evm/cmd/run.go +++ b/apps/evm/cmd/run.go @@ -14,21 +14,20 @@ import ( "github.com/spf13/cobra" "github.com/evstack/ev-node/block" - "github.com/evstack/ev-node/core/da" "github.com/evstack/ev-node/core/execution" coresequencer "github.com/evstack/ev-node/core/sequencer" - "github.com/evstack/ev-node/da/jsonrpc" "github.com/evstack/ev-node/execution/evm" "github.com/evstack/ev-node/node" rollcmd "github.com/evstack/ev-node/pkg/cmd" "github.com/evstack/ev-node/pkg/config" + blobrpc "github.com/evstack/ev-node/pkg/da/jsonrpc" + da "github.com/evstack/ev-node/pkg/da/types" "github.com/evstack/ev-node/pkg/genesis" genesispkg "github.com/evstack/ev-node/pkg/genesis" "github.com/evstack/ev-node/pkg/p2p" "github.com/evstack/ev-node/pkg/p2p/key" "github.com/evstack/ev-node/pkg/store" "github.com/evstack/ev-node/sequencers/based" - seqcommon "github.com/evstack/ev-node/sequencers/common" "github.com/evstack/ev-node/sequencers/single" "github.com/evstack/ev-node/apps/evm/server" @@ -56,6 +55,13 @@ var RunCmd = &cobra.Command{ logger := rollcmd.SetupLogger(nodeConfig.Log) + blobClient, err := blobrpc.NewClient(context.Background(), nodeConfig.DA.Address, nodeConfig.DA.AuthToken, "") + if err != nil { + return fmt.Errorf("failed to create blob client: %w", err) + } + + daClient := block.NewDAClient(blobClient, nodeConfig, logger) + // Attach logger to the EVM engine client if available if ec, ok := executor.(*evm.EngineClient); ok { ec.SetLogger(logger.With().Str("module", "engine_client").Logger()) @@ -66,11 +72,6 @@ var RunCmd = &cobra.Command{ logger.Info().Str("headerNamespace", headerNamespace.HexString()).Str("dataNamespace", dataNamespace.HexString()).Msg("namespaces") - daJrpc, err := jsonrpc.NewClient(context.Background(), logger, nodeConfig.DA.Address, nodeConfig.DA.AuthToken, seqcommon.AbsoluteMaxBlobSize) - if err != nil { - return err - } - datastore, err := store.NewDefaultKVStore(nodeConfig.RootDir, nodeConfig.DBPath, evmDbName) if err != nil { return err @@ -87,7 +88,7 @@ var RunCmd = &cobra.Command{ } // Create sequencer based on configuration - sequencer, err := createSequencer(context.Background(), logger, datastore, &daJrpc.DA, nodeConfig, genesis) + sequencer, err := createSequencer(context.Background(), logger, datastore, nodeConfig, genesis, daClient) if err != nil { return err } @@ -116,7 +117,7 @@ var RunCmd = &cobra.Command{ forceInclusionServer, err := server.NewForceInclusionServer( forceInclusionAddr, - &daJrpc.DA, + daClient, nodeConfig, genesis, logger, @@ -140,7 +141,7 @@ var RunCmd = &cobra.Command{ }() } - return rollcmd.StartNode(logger, cmd, executor, sequencer, &daJrpc.DA, p2pClient, datastore, nodeConfig, genesis, node.NodeOptions{}) + return rollcmd.StartNode(logger, cmd, executor, sequencer, p2pClient, datastore, nodeConfig, genesis, node.NodeOptions{}) }, } @@ -156,11 +157,10 @@ func createSequencer( ctx context.Context, logger zerolog.Logger, datastore datastore.Batching, - da da.DA, nodeConfig config.Config, genesis genesis.Genesis, + daClient block.FullDAClient, ) (coresequencer.Sequencer, error) { - daClient := block.NewDAClient(da, nodeConfig, logger) fiRetriever := block.NewForcedInclusionRetriever(daClient, genesis, logger) if nodeConfig.Node.BasedSequencer { @@ -186,7 +186,7 @@ func createSequencer( ctx, logger, datastore, - da, + daClient, []byte(genesis.ChainID), nodeConfig.Node.BlockTime.Duration, nodeConfig.Node.Aggregator, diff --git a/apps/evm/go.mod b/apps/evm/go.mod index eb0b780c34..f3d9215665 100644 --- a/apps/evm/go.mod +++ b/apps/evm/go.mod @@ -7,7 +7,6 @@ replace github.com/celestiaorg/go-header => github.com/julienrbrt/go-header v0.0 replace ( github.com/evstack/ev-node => ../../ github.com/evstack/ev-node/core => ../../core - github.com/evstack/ev-node/da => ../../da github.com/evstack/ev-node/execution/evm => ../../execution/evm ) @@ -16,7 +15,6 @@ require ( github.com/ethereum/go-ethereum v1.16.7 github.com/evstack/ev-node v1.0.0-beta.10 github.com/evstack/ev-node/core v1.0.0-beta.5 - github.com/evstack/ev-node/da v0.0.0-00010101000000-000000000000 github.com/evstack/ev-node/execution/evm v1.0.0-beta.3 github.com/ipfs/go-datastore v0.9.0 github.com/rs/zerolog v1.34.0 diff --git a/apps/evm/server/force_inclusion.go b/apps/evm/server/force_inclusion.go index f0f7443489..a9812d9944 100644 --- a/apps/evm/server/force_inclusion.go +++ b/apps/evm/server/force_inclusion.go @@ -2,7 +2,6 @@ package server import ( "context" - "encoding/binary" "encoding/hex" "encoding/json" "errors" @@ -15,8 +14,9 @@ import ( "github.com/rs/zerolog" - "github.com/evstack/ev-node/core/da" + "github.com/evstack/ev-node/block" "github.com/evstack/ev-node/pkg/config" + da "github.com/evstack/ev-node/pkg/da/types" "github.com/evstack/ev-node/pkg/genesis" "github.com/evstack/ev-node/types" ) @@ -31,7 +31,7 @@ const ( // that accepts transactions and submits them directly to the DA layer for force inclusion type ForceInclusionServer struct { server *http.Server - daClient da.DA + daClient block.DAClient config config.Config genesis genesis.Genesis logger zerolog.Logger @@ -43,7 +43,7 @@ type ForceInclusionServer struct { // NewForceInclusionServer creates a new force inclusion server func NewForceInclusionServer( addr string, - daClient da.DA, + daClient block.DAClient, cfg config.Config, gen genesis.Genesis, logger zerolog.Logger, @@ -259,24 +259,17 @@ func (s *ForceInclusionServer) handleSendRawTransaction(w http.ResponseWriter, r options := []byte(s.config.DA.SubmitOptions) gasPrice := -1.0 // auto gas price - ids, err := s.daClient.SubmitWithOptions(ctx, blobs, gasPrice, s.namespace, options) - if err != nil { - s.writeError(w, req.ID, InternalError, fmt.Sprintf("failed to submit to DA: %v", err)) - return - } - - if len(ids) == 0 { - s.writeError(w, req.ID, InternalError, "no DA IDs returned") + result := s.daClient.Submit(ctx, blobs, gasPrice, s.namespace, options) + if result.Code != da.StatusSuccess { + s.writeError(w, req.ID, InternalError, fmt.Sprintf("failed to submit to DA: %s", result.Message)) return } - // Extract height from the first ID - // IDs are structured with height in the first 8 bytes (little-endian uint64) - if len(ids[0]) < 8 { - s.writeError(w, req.ID, InternalError, "invalid DA ID format") + daHeight := result.Height + if daHeight == 0 { + s.writeError(w, req.ID, InternalError, "invalid DA height returned") return } - daHeight := binary.LittleEndian.Uint64(ids[0][:8]) s.logger.Info(). Uint64("da_height", daHeight). diff --git a/apps/evm/server/force_inclusion_test.go b/apps/evm/server/force_inclusion_test.go index 36c11492df..a1ad3059ef 100644 --- a/apps/evm/server/force_inclusion_test.go +++ b/apps/evm/server/force_inclusion_test.go @@ -3,41 +3,53 @@ package server import ( "bytes" "context" - "encoding/binary" "encoding/json" "net/http" "net/http/httptest" "testing" "time" - "github.com/evstack/ev-node/core/da" "github.com/evstack/ev-node/pkg/config" + da "github.com/evstack/ev-node/pkg/da/types" "github.com/evstack/ev-node/pkg/genesis" "github.com/rs/zerolog" "gotest.tools/v3/assert" ) -// mockDA implements da.DA interface for testing +// mockDA implements block/internal/da.Client for testing type mockDA struct { - submitFunc func(ctx context.Context, blobs []da.Blob, gasPrice float64, namespace []byte, options []byte) ([]da.ID, error) + submitFunc func(ctx context.Context, data [][]byte, gasPrice float64, namespace []byte, options []byte) da.ResultSubmit } -func (m *mockDA) Submit(ctx context.Context, blobs []da.Blob, gasPrice float64, namespace []byte) ([]da.ID, error) { - return m.SubmitWithOptions(ctx, blobs, gasPrice, namespace, nil) -} - -func (m *mockDA) SubmitWithOptions(ctx context.Context, blobs []da.Blob, gasPrice float64, namespace []byte, options []byte) ([]da.ID, error) { +func (m *mockDA) Submit(ctx context.Context, data [][]byte, gasPrice float64, namespace []byte, options []byte) da.ResultSubmit { if m.submitFunc != nil { - return m.submitFunc(ctx, blobs, gasPrice, namespace, options) + return m.submitFunc(ctx, data, gasPrice, namespace, options) } - return nil, nil + + return da.ResultSubmit{BaseResult: da.BaseResult{Code: da.StatusSuccess, Height: 1}} +} + +func (m *mockDA) Retrieve(ctx context.Context, height uint64, namespace []byte) da.ResultRetrieve { + return da.ResultRetrieve{} +} + +func (m *mockDA) RetrieveHeaders(ctx context.Context, height uint64) da.ResultRetrieve { + return da.ResultRetrieve{} +} + +func (m *mockDA) RetrieveData(ctx context.Context, height uint64) da.ResultRetrieve { + return da.ResultRetrieve{} +} + +func (m *mockDA) RetrieveForcedInclusion(ctx context.Context, height uint64) da.ResultRetrieve { + return da.ResultRetrieve{} } func (m *mockDA) Get(ctx context.Context, ids []da.ID, namespace []byte) ([]da.Blob, error) { return nil, nil } -func (m *mockDA) GetIDs(ctx context.Context, height uint64, namespace []byte) (*da.GetIDsResult, error) { +func (m *mockDA) Validate(ctx context.Context, ids []da.ID, proofs []da.Proof, namespace []byte) ([]bool, error) { return nil, nil } @@ -45,32 +57,29 @@ func (m *mockDA) GetProofs(ctx context.Context, ids []da.ID, namespace []byte) ( return nil, nil } -func (m *mockDA) Commit(ctx context.Context, blobs []da.Blob, namespace []byte) ([]da.Commitment, error) { - return nil, nil +func (m *mockDA) GetHeaderNamespace() []byte { + return []byte("header") } -func (m *mockDA) Validate(ctx context.Context, ids []da.ID, proofs []da.Proof, namespace []byte) ([]bool, error) { - return nil, nil +func (m *mockDA) GetDataNamespace() []byte { + return []byte("data") } -func (m *mockDA) MaxBlobSize(ctx context.Context) (uint64, error) { - return 2 * 1024 * 1024, nil +func (m *mockDA) GetForcedInclusionNamespace() []byte { + return []byte("forced") } -// createTestID creates a test DA ID with the given height -func createTestID(height uint64) da.ID { - id := make([]byte, 8) - binary.LittleEndian.PutUint64(id, height) - return id +func (m *mockDA) HasForcedInclusionNamespace() bool { + return true } func TestForceInclusionServer_handleSendRawTransaction_Success(t *testing.T) { testHeight := uint64(100) mockDAClient := &mockDA{ - submitFunc: func(ctx context.Context, blobs []da.Blob, gasPrice float64, namespace []byte, options []byte) ([]da.ID, error) { - assert.Equal(t, 1, len(blobs)) - return []da.ID{createTestID(testHeight)}, nil + submitFunc: func(ctx context.Context, data [][]byte, gasPrice float64, namespace []byte, options []byte) da.ResultSubmit { + assert.Equal(t, 1, len(data)) + return da.ResultSubmit{BaseResult: da.BaseResult{Code: da.StatusSuccess, Height: testHeight}} }, } @@ -183,8 +192,8 @@ func TestForceInclusionServer_handleSendRawTransaction_InvalidParams(t *testing. func TestForceInclusionServer_handleSendRawTransaction_DAError(t *testing.T) { mockDAClient := &mockDA{ - submitFunc: func(ctx context.Context, blobs []da.Blob, gasPrice float64, namespace []byte, options []byte) ([]da.ID, error) { - return nil, da.ErrBlobSizeOverLimit + submitFunc: func(ctx context.Context, data [][]byte, gasPrice float64, namespace []byte, options []byte) da.ResultSubmit { + return da.ResultSubmit{BaseResult: da.BaseResult{Code: da.StatusError, Message: da.ErrBlobSizeOverLimit.Error()}} }, } diff --git a/apps/grpc/README.md b/apps/grpc/README.md index 51a49ebc09..3c1f24ba41 100644 --- a/apps/grpc/README.md +++ b/apps/grpc/README.md @@ -79,8 +79,8 @@ Start the Evolve node with: 1. Start the local DA service: ```bash - cd da/cmd/local-da - go run main.go + cd tools/local-da + go run . ``` 2. Start your gRPC execution service: diff --git a/apps/grpc/cmd/run.go b/apps/grpc/cmd/run.go index 305b2e2a44..6198fb7727 100644 --- a/apps/grpc/cmd/run.go +++ b/apps/grpc/cmd/run.go @@ -10,21 +10,20 @@ import ( "github.com/spf13/cobra" "github.com/evstack/ev-node/block" - "github.com/evstack/ev-node/core/da" "github.com/evstack/ev-node/core/execution" coresequencer "github.com/evstack/ev-node/core/sequencer" - "github.com/evstack/ev-node/da/jsonrpc" executiongrpc "github.com/evstack/ev-node/execution/grpc" "github.com/evstack/ev-node/node" rollcmd "github.com/evstack/ev-node/pkg/cmd" "github.com/evstack/ev-node/pkg/config" + blobrpc "github.com/evstack/ev-node/pkg/da/jsonrpc" + da "github.com/evstack/ev-node/pkg/da/types" "github.com/evstack/ev-node/pkg/genesis" rollgenesis "github.com/evstack/ev-node/pkg/genesis" "github.com/evstack/ev-node/pkg/p2p" "github.com/evstack/ev-node/pkg/p2p/key" "github.com/evstack/ev-node/pkg/store" "github.com/evstack/ev-node/sequencers/based" - seqcommon "github.com/evstack/ev-node/sequencers/common" "github.com/evstack/ev-node/sequencers/single" ) @@ -60,12 +59,6 @@ The execution client must implement the Evolve execution gRPC interface.`, logger.Info().Str("headerNamespace", headerNamespace.HexString()).Str("dataNamespace", dataNamespace.HexString()).Msg("namespaces") - // Create DA client - daJrpc, err := jsonrpc.NewClient(cmd.Context(), logger, nodeConfig.DA.Address, nodeConfig.DA.AuthToken, seqcommon.AbsoluteMaxBlobSize) - if err != nil { - return err - } - // Create datastore datastore, err := store.NewDefaultKVStore(nodeConfig.RootDir, nodeConfig.DBPath, grpcDbName) if err != nil { @@ -83,7 +76,7 @@ The execution client must implement the Evolve execution gRPC interface.`, } // Create sequencer based on configuration - sequencer, err := createSequencer(cmd.Context(), logger, datastore, &daJrpc.DA, nodeConfig, genesis) + sequencer, err := createSequencer(cmd.Context(), logger, datastore, nodeConfig, genesis) if err != nil { return err } @@ -101,7 +94,7 @@ The execution client must implement the Evolve execution gRPC interface.`, } // Start the node - return rollcmd.StartNode(logger, cmd, executor, sequencer, &daJrpc.DA, p2pClient, datastore, nodeConfig, genesis, node.NodeOptions{}) + return rollcmd.StartNode(logger, cmd, executor, sequencer, p2pClient, datastore, nodeConfig, genesis, node.NodeOptions{}) }, } @@ -118,11 +111,15 @@ func createSequencer( ctx context.Context, logger zerolog.Logger, datastore datastore.Batching, - da da.DA, nodeConfig config.Config, genesis genesis.Genesis, ) (coresequencer.Sequencer, error) { - daClient := block.NewDAClient(da, nodeConfig, logger) + blobClient, err := blobrpc.NewClient(ctx, nodeConfig.DA.Address, nodeConfig.DA.AuthToken, "") + if err != nil { + return nil, fmt.Errorf("failed to create blob client: %w", err) + } + + daClient := block.NewDAClient(blobClient, nodeConfig, logger) fiRetriever := block.NewForcedInclusionRetriever(daClient, genesis, logger) if nodeConfig.Node.BasedSequencer { @@ -148,7 +145,7 @@ func createSequencer( ctx, logger, datastore, - da, + daClient, []byte(genesis.ChainID), nodeConfig.Node.BlockTime.Duration, nodeConfig.Node.Aggregator, diff --git a/apps/grpc/docker-compose.yml b/apps/grpc/docker-compose.yml index 9e502128ab..bcb1072697 100644 --- a/apps/grpc/docker-compose.yml +++ b/apps/grpc/docker-compose.yml @@ -5,7 +5,7 @@ services: local-da: build: context: ../../../ - dockerfile: apps/local-da/Dockerfile + dockerfile: tools/local-da/Dockerfile ports: - "7980:7980" environment: diff --git a/apps/grpc/go.mod b/apps/grpc/go.mod index b019248929..36effcae49 100644 --- a/apps/grpc/go.mod +++ b/apps/grpc/go.mod @@ -7,14 +7,12 @@ replace github.com/celestiaorg/go-header => github.com/julienrbrt/go-header v0.0 replace ( github.com/evstack/ev-node => ../../ github.com/evstack/ev-node/core => ../../core - github.com/evstack/ev-node/da => ../../da github.com/evstack/ev-node/execution/grpc => ../../execution/grpc ) require ( github.com/evstack/ev-node v1.0.0-beta.10 github.com/evstack/ev-node/core v1.0.0-beta.5 - github.com/evstack/ev-node/da v1.0.0-beta.6 github.com/evstack/ev-node/execution/grpc v0.0.0 github.com/ipfs/go-datastore v0.9.0 github.com/rs/zerolog v1.34.0 diff --git a/apps/testapp/cmd/run.go b/apps/testapp/cmd/run.go index 690a2764db..78c8bdaeaa 100644 --- a/apps/testapp/cmd/run.go +++ b/apps/testapp/cmd/run.go @@ -11,18 +11,17 @@ import ( kvexecutor "github.com/evstack/ev-node/apps/testapp/kv" "github.com/evstack/ev-node/block" - "github.com/evstack/ev-node/core/da" coresequencer "github.com/evstack/ev-node/core/sequencer" - "github.com/evstack/ev-node/da/jsonrpc" "github.com/evstack/ev-node/node" "github.com/evstack/ev-node/pkg/cmd" "github.com/evstack/ev-node/pkg/config" + blobrpc "github.com/evstack/ev-node/pkg/da/jsonrpc" + da "github.com/evstack/ev-node/pkg/da/types" "github.com/evstack/ev-node/pkg/genesis" "github.com/evstack/ev-node/pkg/p2p" "github.com/evstack/ev-node/pkg/p2p/key" "github.com/evstack/ev-node/pkg/store" "github.com/evstack/ev-node/sequencers/based" - seqcommon "github.com/evstack/ev-node/sequencers/common" "github.com/evstack/ev-node/sequencers/single" ) @@ -60,11 +59,6 @@ var RunCmd = &cobra.Command{ logger.Info().Str("headerNamespace", headerNamespace.HexString()).Str("dataNamespace", dataNamespace.HexString()).Msg("namespaces") - daJrpc, err := jsonrpc.NewClient(ctx, logger, nodeConfig.DA.Address, nodeConfig.DA.AuthToken, seqcommon.AbsoluteMaxBlobSize) - if err != nil { - return err - } - nodeKey, err := key.LoadNodeKey(filepath.Dir(nodeConfig.ConfigPath())) if err != nil { return err @@ -97,7 +91,7 @@ var RunCmd = &cobra.Command{ } // Create sequencer based on configuration - sequencer, err := createSequencer(ctx, logger, datastore, &daJrpc.DA, nodeConfig, genesis) + sequencer, err := createSequencer(ctx, logger, datastore, nodeConfig, genesis) if err != nil { return err } @@ -107,7 +101,7 @@ var RunCmd = &cobra.Command{ return err } - return cmd.StartNode(logger, command, executor, sequencer, &daJrpc.DA, p2pClient, datastore, nodeConfig, genesis, node.NodeOptions{}) + return cmd.StartNode(logger, command, executor, sequencer, p2pClient, datastore, nodeConfig, genesis, node.NodeOptions{}) }, } @@ -118,11 +112,15 @@ func createSequencer( ctx context.Context, logger zerolog.Logger, datastore datastore.Batching, - da da.DA, nodeConfig config.Config, genesis genesis.Genesis, ) (coresequencer.Sequencer, error) { - daClient := block.NewDAClient(da, nodeConfig, logger) + blobClient, err := blobrpc.NewClient(ctx, nodeConfig.DA.Address, nodeConfig.DA.AuthToken, "") + if err != nil { + return nil, fmt.Errorf("failed to create blob client: %w", err) + } + + daClient := block.NewDAClient(blobClient, nodeConfig, logger) fiRetriever := block.NewForcedInclusionRetriever(daClient, genesis, logger) if nodeConfig.Node.BasedSequencer { @@ -148,7 +146,7 @@ func createSequencer( ctx, logger, datastore, - da, + daClient, []byte(genesis.ChainID), nodeConfig.Node.BlockTime.Duration, nodeConfig.Node.Aggregator, diff --git a/apps/testapp/go.mod b/apps/testapp/go.mod index 6f0edc534a..5b8edaf042 100644 --- a/apps/testapp/go.mod +++ b/apps/testapp/go.mod @@ -7,14 +7,12 @@ replace github.com/celestiaorg/go-header => github.com/julienrbrt/go-header v0.0 replace ( github.com/evstack/ev-node => ../../. github.com/evstack/ev-node/core => ../../core - github.com/evstack/ev-node/da => ../../da ) require ( github.com/celestiaorg/go-header v0.7.4 github.com/evstack/ev-node v1.0.0-beta.10 github.com/evstack/ev-node/core v1.0.0-beta.5 - github.com/evstack/ev-node/da v0.0.0-00010101000000-000000000000 github.com/ipfs/go-datastore v0.9.0 github.com/rs/zerolog v1.34.0 github.com/spf13/cobra v1.10.2 diff --git a/block/components.go b/block/components.go index 0b7996623f..c82df7c364 100644 --- a/block/components.go +++ b/block/components.go @@ -9,11 +9,11 @@ import ( "github.com/evstack/ev-node/block/internal/cache" "github.com/evstack/ev-node/block/internal/common" + da "github.com/evstack/ev-node/block/internal/da" "github.com/evstack/ev-node/block/internal/executing" "github.com/evstack/ev-node/block/internal/reaping" "github.com/evstack/ev-node/block/internal/submitting" "github.com/evstack/ev-node/block/internal/syncing" - coreda "github.com/evstack/ev-node/core/da" coreexecutor "github.com/evstack/ev-node/core/execution" coresequencer "github.com/evstack/ev-node/core/sequencer" "github.com/evstack/ev-node/pkg/config" @@ -120,7 +120,7 @@ func NewSyncComponents( genesis genesis.Genesis, store store.Store, exec coreexecutor.Executor, - da coreda.DA, + daClient da.Client, headerStore common.Broadcaster[*types.SignedHeader], dataStore common.Broadcaster[*types.Data], logger zerolog.Logger, @@ -133,8 +133,6 @@ func NewSyncComponents( return nil, fmt.Errorf("failed to create cache manager: %w", err) } - daClient := NewDAClient(da, config, logger) - // error channel for critical failures errorCh := make(chan error, 1) @@ -185,7 +183,7 @@ func NewAggregatorComponents( store store.Store, exec coreexecutor.Executor, sequencer coresequencer.Sequencer, - da coreda.DA, + daClient da.Client, signer signer.Signer, headerBroadcaster common.Broadcaster[*types.SignedHeader], dataBroadcaster common.Broadcaster[*types.Data], @@ -243,8 +241,6 @@ func NewAggregatorComponents( }, nil } - // Create DA client and submitter for aggregator nodes (with signer for submission) - daClient := NewDAClient(da, config, logger) daSubmitter := submitting.NewDASubmitter(daClient, config, genesis, blockOpts, metrics, logger) submitter := submitting.NewSubmitter( store, diff --git a/block/components_test.go b/block/components_test.go index e8ae8a2a12..d2ecfa5cf1 100644 --- a/block/components_test.go +++ b/block/components_test.go @@ -15,9 +15,9 @@ import ( "github.com/stretchr/testify/mock" "github.com/stretchr/testify/require" - coreda "github.com/evstack/ev-node/core/da" coresequencer "github.com/evstack/ev-node/core/sequencer" "github.com/evstack/ev-node/pkg/config" + datypes "github.com/evstack/ev-node/pkg/da/types" "github.com/evstack/ev-node/pkg/genesis" "github.com/evstack/ev-node/pkg/signer/noop" "github.com/evstack/ev-node/pkg/store" @@ -80,7 +80,11 @@ func TestNewSyncComponents_Creation(t *testing.T) { } mockExec := testmocks.NewMockExecutor(t) - dummyDA := coreda.NewDummyDA(10_000_000, 10*time.Millisecond) + daClient := testmocks.NewMockClient(t) + daClient.On("GetHeaderNamespace").Return(datypes.NamespaceFromString("ns").Bytes()).Maybe() + daClient.On("GetDataNamespace").Return(datypes.NamespaceFromString("data-ns").Bytes()).Maybe() + daClient.On("GetForcedInclusionNamespace").Return([]byte(nil)).Maybe() + daClient.On("HasForcedInclusionNamespace").Return(false).Maybe() // Just test that the constructor doesn't panic - don't start the components // to avoid P2P store dependencies @@ -89,7 +93,7 @@ func TestNewSyncComponents_Creation(t *testing.T) { gen, memStore, mockExec, - dummyDA, + daClient, nil, nil, zerolog.Nop(), @@ -131,7 +135,11 @@ func TestNewAggregatorComponents_Creation(t *testing.T) { mockExec := testmocks.NewMockExecutor(t) mockSeq := testmocks.NewMockSequencer(t) - dummyDA := coreda.NewDummyDA(10_000_000, 10*time.Millisecond) + daClient := testmocks.NewMockClient(t) + daClient.On("GetHeaderNamespace").Return(datypes.NamespaceFromString("ns").Bytes()).Maybe() + daClient.On("GetDataNamespace").Return(datypes.NamespaceFromString("data-ns").Bytes()).Maybe() + daClient.On("GetForcedInclusionNamespace").Return([]byte(nil)).Maybe() + daClient.On("HasForcedInclusionNamespace").Return(false).Maybe() components, err := NewAggregatorComponents( cfg, @@ -139,7 +147,7 @@ func TestNewAggregatorComponents_Creation(t *testing.T) { memStore, mockExec, mockSeq, - dummyDA, + daClient, mockSigner, nil, // header broadcaster nil, // data broadcaster @@ -185,7 +193,11 @@ func TestExecutor_RealExecutionClientFailure_StopsNode(t *testing.T) { // Create mock executor that will fail on ExecuteTxs mockExec := testmocks.NewMockExecutor(t) mockSeq := testmocks.NewMockSequencer(t) - dummyDA := coreda.NewDummyDA(10_000_000, 10*time.Millisecond) + daClient := testmocks.NewMockClient(t) + daClient.On("GetHeaderNamespace").Return(datypes.NamespaceFromString("ns").Bytes()).Maybe() + daClient.On("GetDataNamespace").Return(datypes.NamespaceFromString("data-ns").Bytes()).Maybe() + daClient.On("GetForcedInclusionNamespace").Return([]byte(nil)).Maybe() + daClient.On("HasForcedInclusionNamespace").Return(false).Maybe() // Mock InitChain to succeed initially mockExec.On("InitChain", mock.Anything, mock.Anything, mock.Anything, mock.Anything). @@ -217,7 +229,7 @@ func TestExecutor_RealExecutionClientFailure_StopsNode(t *testing.T) { memStore, mockExec, mockSeq, - dummyDA, + daClient, testSigner, nil, // header broadcaster nil, // data broadcaster diff --git a/block/internal/da/blob_api_mock.go b/block/internal/da/blob_api_mock.go deleted file mode 100644 index 6e038a386d..0000000000 --- a/block/internal/da/blob_api_mock.go +++ /dev/null @@ -1,498 +0,0 @@ -// Code generated by mockery; DO NOT EDIT. -// github.com/vektra/mockery -// template: testify - -package da - -import ( - "context" - - "github.com/celestiaorg/go-square/v3/share" - "github.com/evstack/ev-node/pkg/da/jsonrpc" - mock "github.com/stretchr/testify/mock" -) - -// NewMockBlobAPI creates a new instance of MockBlobAPI. 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 NewMockBlobAPI(t interface { - mock.TestingT - Cleanup(func()) -}) *MockBlobAPI { - mock := &MockBlobAPI{} - mock.Mock.Test(t) - - t.Cleanup(func() { mock.AssertExpectations(t) }) - - return mock -} - -// MockBlobAPI is an autogenerated mock type for the BlobAPI type -type MockBlobAPI struct { - mock.Mock -} - -type MockBlobAPI_Expecter struct { - mock *mock.Mock -} - -func (_m *MockBlobAPI) EXPECT() *MockBlobAPI_Expecter { - return &MockBlobAPI_Expecter{mock: &_m.Mock} -} - -// GetAll provides a mock function for the type MockBlobAPI -func (_mock *MockBlobAPI) GetAll(ctx context.Context, height uint64, namespaces []share.Namespace) ([]*blob.Blob, error) { - ret := _mock.Called(ctx, height, namespaces) - - if len(ret) == 0 { - panic("no return value specified for GetAll") - } - - var r0 []*blob.Blob - var r1 error - if returnFunc, ok := ret.Get(0).(func(context.Context, uint64, []share.Namespace) ([]*blob.Blob, error)); ok { - return returnFunc(ctx, height, namespaces) - } - if returnFunc, ok := ret.Get(0).(func(context.Context, uint64, []share.Namespace) []*blob.Blob); ok { - r0 = returnFunc(ctx, height, namespaces) - } else { - if ret.Get(0) != nil { - r0 = ret.Get(0).([]*blob.Blob) - } - } - if returnFunc, ok := ret.Get(1).(func(context.Context, uint64, []share.Namespace) error); ok { - r1 = returnFunc(ctx, height, namespaces) - } else { - r1 = ret.Error(1) - } - return r0, r1 -} - -// MockBlobAPI_GetAll_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GetAll' -type MockBlobAPI_GetAll_Call struct { - *mock.Call -} - -// GetAll is a helper method to define mock.On call -// - ctx context.Context -// - height uint64 -// - namespaces []share.Namespace -func (_e *MockBlobAPI_Expecter) GetAll(ctx interface{}, height interface{}, namespaces interface{}) *MockBlobAPI_GetAll_Call { - return &MockBlobAPI_GetAll_Call{Call: _e.mock.On("GetAll", ctx, height, namespaces)} -} - -func (_c *MockBlobAPI_GetAll_Call) Run(run func(ctx context.Context, height uint64, namespaces []share.Namespace)) *MockBlobAPI_GetAll_Call { - _c.Call.Run(func(args mock.Arguments) { - var arg0 context.Context - if args[0] != nil { - arg0 = args[0].(context.Context) - } - var arg1 uint64 - if args[1] != nil { - arg1 = args[1].(uint64) - } - var arg2 []share.Namespace - if args[2] != nil { - arg2 = args[2].([]share.Namespace) - } - run( - arg0, - arg1, - arg2, - ) - }) - return _c -} - -func (_c *MockBlobAPI_GetAll_Call) Return(blobs []*blob.Blob, err error) *MockBlobAPI_GetAll_Call { - _c.Call.Return(blobs, err) - return _c -} - -func (_c *MockBlobAPI_GetAll_Call) RunAndReturn(run func(ctx context.Context, height uint64, namespaces []share.Namespace) ([]*blob.Blob, error)) *MockBlobAPI_GetAll_Call { - _c.Call.Return(run) - return _c -} - -// GetCommitmentProof provides a mock function for the type MockBlobAPI -func (_mock *MockBlobAPI) GetCommitmentProof(ctx context.Context, height uint64, namespace share.Namespace, shareCommitment []byte) (*blob.CommitmentProof, error) { - ret := _mock.Called(ctx, height, namespace, shareCommitment) - - if len(ret) == 0 { - panic("no return value specified for GetCommitmentProof") - } - - var r0 *blob.CommitmentProof - var r1 error - if returnFunc, ok := ret.Get(0).(func(context.Context, uint64, share.Namespace, []byte) (*blob.CommitmentProof, error)); ok { - return returnFunc(ctx, height, namespace, shareCommitment) - } - if returnFunc, ok := ret.Get(0).(func(context.Context, uint64, share.Namespace, []byte) *blob.CommitmentProof); ok { - r0 = returnFunc(ctx, height, namespace, shareCommitment) - } else { - if ret.Get(0) != nil { - r0 = ret.Get(0).(*blob.CommitmentProof) - } - } - if returnFunc, ok := ret.Get(1).(func(context.Context, uint64, share.Namespace, []byte) error); ok { - r1 = returnFunc(ctx, height, namespace, shareCommitment) - } else { - r1 = ret.Error(1) - } - return r0, r1 -} - -// MockBlobAPI_GetCommitmentProof_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GetCommitmentProof' -type MockBlobAPI_GetCommitmentProof_Call struct { - *mock.Call -} - -// GetCommitmentProof is a helper method to define mock.On call -// - ctx context.Context -// - height uint64 -// - namespace share.Namespace -// - shareCommitment []byte -func (_e *MockBlobAPI_Expecter) GetCommitmentProof(ctx interface{}, height interface{}, namespace interface{}, shareCommitment interface{}) *MockBlobAPI_GetCommitmentProof_Call { - return &MockBlobAPI_GetCommitmentProof_Call{Call: _e.mock.On("GetCommitmentProof", ctx, height, namespace, shareCommitment)} -} - -func (_c *MockBlobAPI_GetCommitmentProof_Call) Run(run func(ctx context.Context, height uint64, namespace share.Namespace, shareCommitment []byte)) *MockBlobAPI_GetCommitmentProof_Call { - _c.Call.Run(func(args mock.Arguments) { - var arg0 context.Context - if args[0] != nil { - arg0 = args[0].(context.Context) - } - var arg1 uint64 - if args[1] != nil { - arg1 = args[1].(uint64) - } - var arg2 share.Namespace - if args[2] != nil { - arg2 = args[2].(share.Namespace) - } - var arg3 []byte - if args[3] != nil { - arg3 = args[3].([]byte) - } - run( - arg0, - arg1, - arg2, - arg3, - ) - }) - return _c -} - -func (_c *MockBlobAPI_GetCommitmentProof_Call) Return(commitmentProof *blob.CommitmentProof, err error) *MockBlobAPI_GetCommitmentProof_Call { - _c.Call.Return(commitmentProof, err) - return _c -} - -func (_c *MockBlobAPI_GetCommitmentProof_Call) RunAndReturn(run func(ctx context.Context, height uint64, namespace share.Namespace, shareCommitment []byte) (*blob.CommitmentProof, error)) *MockBlobAPI_GetCommitmentProof_Call { - _c.Call.Return(run) - return _c -} - -// GetProof provides a mock function for the type MockBlobAPI -func (_mock *MockBlobAPI) GetProof(ctx context.Context, height uint64, namespace share.Namespace, commitment blob.Commitment) (*blob.Proof, error) { - ret := _mock.Called(ctx, height, namespace, commitment) - - if len(ret) == 0 { - panic("no return value specified for GetProof") - } - - var r0 *blob.Proof - var r1 error - if returnFunc, ok := ret.Get(0).(func(context.Context, uint64, share.Namespace, blob.Commitment) (*blob.Proof, error)); ok { - return returnFunc(ctx, height, namespace, commitment) - } - if returnFunc, ok := ret.Get(0).(func(context.Context, uint64, share.Namespace, blob.Commitment) *blob.Proof); ok { - r0 = returnFunc(ctx, height, namespace, commitment) - } else { - if ret.Get(0) != nil { - r0 = ret.Get(0).(*blob.Proof) - } - } - if returnFunc, ok := ret.Get(1).(func(context.Context, uint64, share.Namespace, blob.Commitment) error); ok { - r1 = returnFunc(ctx, height, namespace, commitment) - } else { - r1 = ret.Error(1) - } - return r0, r1 -} - -// MockBlobAPI_GetProof_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GetProof' -type MockBlobAPI_GetProof_Call struct { - *mock.Call -} - -// GetProof is a helper method to define mock.On call -// - ctx context.Context -// - height uint64 -// - namespace share.Namespace -// - commitment blob.Commitment -func (_e *MockBlobAPI_Expecter) GetProof(ctx interface{}, height interface{}, namespace interface{}, commitment interface{}) *MockBlobAPI_GetProof_Call { - return &MockBlobAPI_GetProof_Call{Call: _e.mock.On("GetProof", ctx, height, namespace, commitment)} -} - -func (_c *MockBlobAPI_GetProof_Call) Run(run func(ctx context.Context, height uint64, namespace share.Namespace, commitment blob.Commitment)) *MockBlobAPI_GetProof_Call { - _c.Call.Run(func(args mock.Arguments) { - var arg0 context.Context - if args[0] != nil { - arg0 = args[0].(context.Context) - } - var arg1 uint64 - if args[1] != nil { - arg1 = args[1].(uint64) - } - var arg2 share.Namespace - if args[2] != nil { - arg2 = args[2].(share.Namespace) - } - var arg3 blob.Commitment - if args[3] != nil { - arg3 = args[3].(blob.Commitment) - } - run( - arg0, - arg1, - arg2, - arg3, - ) - }) - return _c -} - -func (_c *MockBlobAPI_GetProof_Call) Return(proof *blob.Proof, err error) *MockBlobAPI_GetProof_Call { - _c.Call.Return(proof, err) - return _c -} - -func (_c *MockBlobAPI_GetProof_Call) RunAndReturn(run func(ctx context.Context, height uint64, namespace share.Namespace, commitment blob.Commitment) (*blob.Proof, error)) *MockBlobAPI_GetProof_Call { - _c.Call.Return(run) - return _c -} - -// Included provides a mock function for the type MockBlobAPI -func (_mock *MockBlobAPI) Included(ctx context.Context, height uint64, namespace share.Namespace, proof *blob.Proof, commitment blob.Commitment) (bool, error) { - ret := _mock.Called(ctx, height, namespace, proof, commitment) - - if len(ret) == 0 { - panic("no return value specified for Included") - } - - var r0 bool - var r1 error - if returnFunc, ok := ret.Get(0).(func(context.Context, uint64, share.Namespace, *blob.Proof, blob.Commitment) (bool, error)); ok { - return returnFunc(ctx, height, namespace, proof, commitment) - } - if returnFunc, ok := ret.Get(0).(func(context.Context, uint64, share.Namespace, *blob.Proof, blob.Commitment) bool); ok { - r0 = returnFunc(ctx, height, namespace, proof, commitment) - } else { - r0 = ret.Get(0).(bool) - } - if returnFunc, ok := ret.Get(1).(func(context.Context, uint64, share.Namespace, *blob.Proof, blob.Commitment) error); ok { - r1 = returnFunc(ctx, height, namespace, proof, commitment) - } else { - r1 = ret.Error(1) - } - return r0, r1 -} - -// MockBlobAPI_Included_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Included' -type MockBlobAPI_Included_Call struct { - *mock.Call -} - -// Included is a helper method to define mock.On call -// - ctx context.Context -// - height uint64 -// - namespace share.Namespace -// - proof *blob.Proof -// - commitment blob.Commitment -func (_e *MockBlobAPI_Expecter) Included(ctx interface{}, height interface{}, namespace interface{}, proof interface{}, commitment interface{}) *MockBlobAPI_Included_Call { - return &MockBlobAPI_Included_Call{Call: _e.mock.On("Included", ctx, height, namespace, proof, commitment)} -} - -func (_c *MockBlobAPI_Included_Call) Run(run func(ctx context.Context, height uint64, namespace share.Namespace, proof *blob.Proof, commitment blob.Commitment)) *MockBlobAPI_Included_Call { - _c.Call.Run(func(args mock.Arguments) { - var arg0 context.Context - if args[0] != nil { - arg0 = args[0].(context.Context) - } - var arg1 uint64 - if args[1] != nil { - arg1 = args[1].(uint64) - } - var arg2 share.Namespace - if args[2] != nil { - arg2 = args[2].(share.Namespace) - } - var arg3 *blob.Proof - if args[3] != nil { - arg3 = args[3].(*blob.Proof) - } - var arg4 blob.Commitment - if args[4] != nil { - arg4 = args[4].(blob.Commitment) - } - run( - arg0, - arg1, - arg2, - arg3, - arg4, - ) - }) - return _c -} - -func (_c *MockBlobAPI_Included_Call) Return(b bool, err error) *MockBlobAPI_Included_Call { - _c.Call.Return(b, err) - return _c -} - -func (_c *MockBlobAPI_Included_Call) RunAndReturn(run func(ctx context.Context, height uint64, namespace share.Namespace, proof *blob.Proof, commitment blob.Commitment) (bool, error)) *MockBlobAPI_Included_Call { - _c.Call.Return(run) - return _c -} - -// Submit provides a mock function for the type MockBlobAPI -func (_mock *MockBlobAPI) Submit(ctx context.Context, blobs []*blob.Blob, opts *blob.SubmitOptions) (uint64, error) { - ret := _mock.Called(ctx, blobs, opts) - - if len(ret) == 0 { - panic("no return value specified for Submit") - } - - var r0 uint64 - var r1 error - if returnFunc, ok := ret.Get(0).(func(context.Context, []*blob.Blob, *blob.SubmitOptions) (uint64, error)); ok { - return returnFunc(ctx, blobs, opts) - } - if returnFunc, ok := ret.Get(0).(func(context.Context, []*blob.Blob, *blob.SubmitOptions) uint64); ok { - r0 = returnFunc(ctx, blobs, opts) - } else { - r0 = ret.Get(0).(uint64) - } - if returnFunc, ok := ret.Get(1).(func(context.Context, []*blob.Blob, *blob.SubmitOptions) error); ok { - r1 = returnFunc(ctx, blobs, opts) - } else { - r1 = ret.Error(1) - } - return r0, r1 -} - -// MockBlobAPI_Submit_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Submit' -type MockBlobAPI_Submit_Call struct { - *mock.Call -} - -// Submit is a helper method to define mock.On call -// - ctx context.Context -// - blobs []*blob.Blob -// - opts *blob.SubmitOptions -func (_e *MockBlobAPI_Expecter) Submit(ctx interface{}, blobs interface{}, opts interface{}) *MockBlobAPI_Submit_Call { - return &MockBlobAPI_Submit_Call{Call: _e.mock.On("Submit", ctx, blobs, opts)} -} - -func (_c *MockBlobAPI_Submit_Call) Run(run func(ctx context.Context, blobs []*blob.Blob, opts *blob.SubmitOptions)) *MockBlobAPI_Submit_Call { - _c.Call.Run(func(args mock.Arguments) { - var arg0 context.Context - if args[0] != nil { - arg0 = args[0].(context.Context) - } - var arg1 []*blob.Blob - if args[1] != nil { - arg1 = args[1].([]*blob.Blob) - } - var arg2 *blob.SubmitOptions - if args[2] != nil { - arg2 = args[2].(*blob.SubmitOptions) - } - run( - arg0, - arg1, - arg2, - ) - }) - return _c -} - -func (_c *MockBlobAPI_Submit_Call) Return(v uint64, err error) *MockBlobAPI_Submit_Call { - _c.Call.Return(v, err) - return _c -} - -func (_c *MockBlobAPI_Submit_Call) RunAndReturn(run func(ctx context.Context, blobs []*blob.Blob, opts *blob.SubmitOptions) (uint64, error)) *MockBlobAPI_Submit_Call { - _c.Call.Return(run) - return _c -} - -// Subscribe provides a mock function for the type MockBlobAPI -func (_mock *MockBlobAPI) Subscribe(ctx context.Context, namespace share.Namespace) (<-chan *blob.SubscriptionResponse, error) { - ret := _mock.Called(ctx, namespace) - - if len(ret) == 0 { - panic("no return value specified for Subscribe") - } - - var r0 <-chan *blob.SubscriptionResponse - var r1 error - if returnFunc, ok := ret.Get(0).(func(context.Context, share.Namespace) (<-chan *blob.SubscriptionResponse, error)); ok { - return returnFunc(ctx, namespace) - } - if returnFunc, ok := ret.Get(0).(func(context.Context, share.Namespace) <-chan *blob.SubscriptionResponse); ok { - r0 = returnFunc(ctx, namespace) - } else { - if ret.Get(0) != nil { - r0 = ret.Get(0).(<-chan *blob.SubscriptionResponse) - } - } - if returnFunc, ok := ret.Get(1).(func(context.Context, share.Namespace) error); ok { - r1 = returnFunc(ctx, namespace) - } else { - r1 = ret.Error(1) - } - return r0, r1 -} - -// MockBlobAPI_Subscribe_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Subscribe' -type MockBlobAPI_Subscribe_Call struct { - *mock.Call -} - -// Subscribe is a helper method to define mock.On call -// - ctx context.Context -// - namespace share.Namespace -func (_e *MockBlobAPI_Expecter) Subscribe(ctx interface{}, namespace interface{}) *MockBlobAPI_Subscribe_Call { - return &MockBlobAPI_Subscribe_Call{Call: _e.mock.On("Subscribe", ctx, namespace)} -} - -func (_c *MockBlobAPI_Subscribe_Call) Run(run func(ctx context.Context, namespace share.Namespace)) *MockBlobAPI_Subscribe_Call { - _c.Call.Run(func(args mock.Arguments) { - var arg0 context.Context - if args[0] != nil { - arg0 = args[0].(context.Context) - } - var arg1 share.Namespace - if args[1] != nil { - arg1 = args[1].(share.Namespace) - } - run( - arg0, - arg1, - ) - }) - return _c -} - -func (_c *MockBlobAPI_Subscribe_Call) Return(subscriptionResponseCh <-chan *blob.SubscriptionResponse, err error) *MockBlobAPI_Subscribe_Call { - _c.Call.Return(subscriptionResponseCh, err) - return _c -} - -func (_c *MockBlobAPI_Subscribe_Call) RunAndReturn(run func(ctx context.Context, namespace share.Namespace) (<-chan *blob.SubscriptionResponse, error)) *MockBlobAPI_Subscribe_Call { - _c.Call.Return(run) - return _c -} diff --git a/block/internal/da/blob_client.go b/block/internal/da/blob_client.go deleted file mode 100644 index 4764f95dd4..0000000000 --- a/block/internal/da/blob_client.go +++ /dev/null @@ -1,289 +0,0 @@ -package da - -import ( - "context" - "encoding/json" - "errors" - "fmt" - "strings" - "time" - - "github.com/celestiaorg/go-square/v3/share" - blobrpc "github.com/evstack/ev-node/pkg/da/jsonrpc" - "github.com/rs/zerolog" -) - -var ( - ErrBlobNotFound = errors.New("blob: not found") - ErrBlobSizeOverLimit = errors.New("blob: over size limit") - ErrTxTimedOut = errors.New("timed out waiting for tx to be included in a block") - ErrTxAlreadyInMempool = errors.New("tx already in mempool") - ErrTxIncorrectAccountSequence = errors.New("incorrect account sequence") - ErrContextDeadline = errors.New("context deadline") - ErrHeightFromFuture = errors.New("given height is from the future") - ErrContextCanceled = errors.New("context canceled") -) - -// BlobConfig contains configuration for the Celestia blob client. -type BlobConfig struct { - Logger zerolog.Logger - DefaultTimeout time.Duration - Namespace string - DataNamespace string - MaxBlobSize uint64 -} - -// BlobAPI captures the subset of blobrpc.BlobAPI used by BlobClient. -type BlobAPI interface { - Submit(ctx context.Context, blobs []*blobrpc.Blob, opts *blobrpc.SubmitOptions) (uint64, error) - GetAll(ctx context.Context, height uint64, namespaces []share.Namespace) ([]*blobrpc.Blob, error) - GetProof(ctx context.Context, height uint64, namespace share.Namespace, commitment blobrpc.Commitment) (*blobrpc.Proof, error) - Included(ctx context.Context, height uint64, namespace share.Namespace, proof *blobrpc.Proof, commitment blobrpc.Commitment) (bool, error) - GetCommitmentProof(ctx context.Context, height uint64, namespace share.Namespace, shareCommitment []byte) (*blobrpc.CommitmentProof, error) - Subscribe(ctx context.Context, namespace share.Namespace) (<-chan *blobrpc.SubscriptionResponse, error) -} - -// BlobClient wraps the blob RPC with namespace handling and error mapping. -type BlobClient struct { - blobAPI BlobAPI - logger zerolog.Logger - defaultTimeout time.Duration - namespaceBz []byte - dataNamespaceBz []byte - maxBlobSize uint64 -} - -// NewBlobClient creates a new blob client wrapper with pre-calculated namespace bytes. -func NewBlobClient(api BlobAPI, cfg BlobConfig) *BlobClient { - if api == nil { - return nil - } - if cfg.DefaultTimeout == 0 { - cfg.DefaultTimeout = 30 * time.Second - } - if cfg.MaxBlobSize == 0 { - cfg.MaxBlobSize = blobrpc.DefaultMaxBlobSize - } - - return &BlobClient{ - blobAPI: api, - logger: cfg.Logger.With().Str("component", "blob_da_client").Logger(), - defaultTimeout: cfg.DefaultTimeout, - namespaceBz: share.MustNewV0Namespace([]byte(cfg.Namespace)).Bytes(), - dataNamespaceBz: share.MustNewV0Namespace([]byte(cfg.DataNamespace)).Bytes(), - maxBlobSize: cfg.MaxBlobSize, - } -} - -// Submit submits blobs to the DA layer with the specified options. -func (c *BlobClient) Submit(ctx context.Context, data [][]byte, namespace []byte, options []byte) ResultSubmit { - // calculate blob size - var blobSize uint64 - for _, b := range data { - blobSize += uint64(len(b)) - } - - ns, err := share.NewNamespaceFromBytes(namespace) - if err != nil { - return ResultSubmit{ - BaseResult: BaseResult{ - Code: StatusError, - Message: fmt.Sprintf("invalid namespace: %v", err), - }, - } - } - - blobs := make([]*blobrpc.Blob, len(data)) - for i, raw := range data { - if uint64(len(raw)) > c.maxBlobSize { - return ResultSubmit{ - BaseResult: BaseResult{ - Code: StatusTooBig, - Message: ErrBlobSizeOverLimit.Error(), - }, - } - } - blobs[i], err = blobrpc.NewBlobV0(ns, raw) - if err != nil { - return ResultSubmit{ - BaseResult: BaseResult{ - Code: StatusError, - Message: fmt.Sprintf("failed to build blob %d: %v", i, err), - }, - } - } - } - - var submitOpts blobrpc.SubmitOptions - if len(options) > 0 { - if err := json.Unmarshal(options, &submitOpts); err != nil { - return ResultSubmit{ - BaseResult: BaseResult{ - Code: StatusError, - Message: fmt.Sprintf("failed to parse submit options: %v", err), - }, - } - } - } - - height, err := c.blobAPI.Submit(ctx, blobs, &submitOpts) - if err != nil { - code := StatusError - switch { - case errors.Is(err, context.Canceled): - code = StatusContextCanceled - case strings.Contains(err.Error(), ErrTxTimedOut.Error()): - code = StatusNotIncludedInBlock - case strings.Contains(err.Error(), ErrTxAlreadyInMempool.Error()): - code = StatusAlreadyInMempool - case strings.Contains(err.Error(), ErrTxIncorrectAccountSequence.Error()): - code = StatusIncorrectAccountSequence - case strings.Contains(err.Error(), ErrBlobSizeOverLimit.Error()): - code = StatusTooBig - case strings.Contains(err.Error(), ErrContextDeadline.Error()): - code = StatusContextDeadline - } - if code == StatusTooBig { - c.logger.Debug().Err(err).Uint64("status", uint64(code)).Msg("DA submission failed") - } else { - c.logger.Error().Err(err).Uint64("status", uint64(code)).Msg("DA submission failed") - } - return ResultSubmit{ - BaseResult: BaseResult{ - Code: code, - Message: "failed to submit blobs: " + err.Error(), - SubmittedCount: 0, - Height: 0, - Timestamp: time.Now(), - BlobSize: blobSize, - }, - } - } - - if len(blobs) == 0 { - return ResultSubmit{ - BaseResult: BaseResult{ - Code: StatusSuccess, - BlobSize: blobSize, - Height: height, - }, - } - } - - ids := make([]ID, len(blobs)) - for i, b := range blobs { - ids[i] = blobrpc.MakeID(height, b.Commitment) - } - - return ResultSubmit{ - BaseResult: BaseResult{ - Code: StatusSuccess, - IDs: ids, - SubmittedCount: uint64(len(ids)), - Height: height, - BlobSize: blobSize, - Timestamp: time.Now(), - }, - } -} - -// Retrieve retrieves blobs from the DA layer at the specified height and namespace. -func (c *BlobClient) Retrieve(ctx context.Context, height uint64, namespace []byte) ResultRetrieve { - ns, err := share.NewNamespaceFromBytes(namespace) - if err != nil { - return ResultRetrieve{ - BaseResult: BaseResult{ - Code: StatusError, - Message: fmt.Sprintf("invalid namespace: %v", err), - Height: height, - }, - } - } - - getCtx, cancel := context.WithTimeout(ctx, c.defaultTimeout) - defer cancel() - - blobs, err := c.blobAPI.GetAll(getCtx, height, []share.Namespace{ns}) - if err != nil { - // Handle known errors by substring because RPC may wrap them. - switch { - case strings.Contains(err.Error(), ErrBlobNotFound.Error()): - return ResultRetrieve{ - BaseResult: BaseResult{ - Code: StatusNotFound, - Message: ErrBlobNotFound.Error(), - Height: height, - Timestamp: time.Now(), - }, - } - case strings.Contains(err.Error(), ErrHeightFromFuture.Error()): - return ResultRetrieve{ - BaseResult: BaseResult{ - Code: StatusHeightFromFuture, - Message: ErrHeightFromFuture.Error(), - Height: height, - Timestamp: time.Now(), - }, - } - default: - c.logger.Error().Uint64("height", height).Err(err).Msg("failed to get blobs") - return ResultRetrieve{ - BaseResult: BaseResult{ - Code: StatusError, - Message: fmt.Sprintf("failed to get blobs: %s", err.Error()), - Height: height, - Timestamp: time.Now(), - }, - } - } - } - - if len(blobs) == 0 { - c.logger.Debug().Uint64("height", height).Msg("No blobs found at height") - return ResultRetrieve{ - BaseResult: BaseResult{ - Code: StatusNotFound, - Message: ErrBlobNotFound.Error(), - Height: height, - Timestamp: time.Now(), - }, - } - } - - out := make([][]byte, len(blobs)) - ids := make([][]byte, len(blobs)) - for i, b := range blobs { - out[i] = b.Data() - ids[i] = blobrpc.MakeID(height, b.Commitment) - } - - return ResultRetrieve{ - BaseResult: BaseResult{ - Code: StatusSuccess, - Height: height, - IDs: ids, - Timestamp: time.Now(), - }, - Data: out, - } -} - -// RetrieveHeaders retrieves blobs from the header namespace at the specified height. -func (c *BlobClient) RetrieveHeaders(ctx context.Context, height uint64) ResultRetrieve { - return c.Retrieve(ctx, height, c.namespaceBz) -} - -// RetrieveData retrieves blobs from the data namespace at the specified height. -func (c *BlobClient) RetrieveData(ctx context.Context, height uint64) ResultRetrieve { - return c.Retrieve(ctx, height, c.dataNamespaceBz) -} - -// GetHeaderNamespace returns the header namespace bytes. -func (c *BlobClient) GetHeaderNamespace() []byte { - return c.namespaceBz -} - -// GetDataNamespace returns the data namespace bytes. -func (c *BlobClient) GetDataNamespace() []byte { - return c.dataNamespaceBz -} diff --git a/block/internal/da/blob_client_test.go b/block/internal/da/blob_client_test.go deleted file mode 100644 index aaab677830..0000000000 --- a/block/internal/da/blob_client_test.go +++ /dev/null @@ -1,132 +0,0 @@ -package da - -import ( - "context" - "encoding/json" - "errors" - "testing" - - "github.com/celestiaorg/go-square/v3/share" - blobrpc "github.com/evstack/ev-node/pkg/da/jsonrpc" - "github.com/rs/zerolog" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/mock" - "github.com/stretchr/testify/require" -) - -func TestBlobClient_Submit_ErrorMapping(t *testing.T) { - ns := share.MustNewV0Namespace([]byte("ns")).Bytes() - testCases := []struct { - name string - err error - wantStatus StatusCode - }{ - {"timeout", ErrTxTimedOut, StatusNotIncludedInBlock}, - {"alreadyInMempool", ErrTxAlreadyInMempool, StatusAlreadyInMempool}, - {"seq", ErrTxIncorrectAccountSequence, StatusIncorrectAccountSequence}, - {"tooBig", ErrBlobSizeOverLimit, StatusTooBig}, - {"deadline", ErrContextDeadline, StatusContextDeadline}, - {"canceled", context.Canceled, StatusContextCanceled}, - {"other", errors.New("boom"), StatusError}, - } - - for _, tc := range testCases { - t.Run(tc.name, func(t *testing.T) { - mockAPI := NewMockBlobAPI(t) - mockAPI.On("Submit", mock.Anything, mock.Anything, mock.Anything). - Return(uint64(0), tc.err) - - cl := NewBlobClient(mockAPI, BlobConfig{ - Logger: zerolog.Nop(), - Namespace: "ns", - DataNamespace: "ns", - }) - require.NotNil(t, cl) - res := cl.Submit(context.Background(), [][]byte{[]byte("data")}, ns, nil) - assert.Equal(t, tc.wantStatus, res.Code) - }) - } -} - -func TestBlobClient_Submit_Success(t *testing.T) { - ns := share.MustNewV0Namespace([]byte("ns")).Bytes() - mockAPI := NewMockBlobAPI(t) - mockAPI.On("Submit", mock.Anything, mock.Anything, mock.Anything). - Return(uint64(10), nil) - cl := NewBlobClient(mockAPI, BlobConfig{ - Logger: zerolog.Nop(), - Namespace: "ns", - DataNamespace: "ns", - }) - require.NotNil(t, cl) - res := cl.Submit(context.Background(), [][]byte{[]byte("data")}, ns, nil) - require.Equal(t, StatusSuccess, res.Code) - require.Equal(t, uint64(10), res.Height) - require.Len(t, res.IDs, 1) -} - -func TestBlobClient_Submit_InvalidNamespace(t *testing.T) { - mockAPI := NewMockBlobAPI(t) - cl := NewBlobClient(mockAPI, BlobConfig{ - Logger: zerolog.Nop(), - Namespace: "ns", - DataNamespace: "ns", - }) - require.NotNil(t, cl) - res := cl.Submit(context.Background(), [][]byte{[]byte("data")}, []byte{0x01, 0x02}, nil) - require.Equal(t, StatusError, res.Code) -} - -func TestBlobClient_Retrieve_NotFound(t *testing.T) { - ns := share.MustNewV0Namespace([]byte("ns")).Bytes() - mockAPI := NewMockBlobAPI(t) - mockAPI.On("GetAll", mock.Anything, uint64(5), []share.Namespace{share.MustNewV0Namespace([]byte("ns"))}). - Return([]*blobrpc.Blob(nil), ErrBlobNotFound) - cl := NewBlobClient(mockAPI, BlobConfig{ - Logger: zerolog.Nop(), - Namespace: "ns", - DataNamespace: "ns", - }) - require.NotNil(t, cl) - res := cl.Retrieve(context.Background(), 5, ns) - require.Equal(t, StatusNotFound, res.Code) -} - -func TestBlobClient_Retrieve_Success(t *testing.T) { - ns := share.MustNewV0Namespace([]byte("ns")).Bytes() - b, err := blobrpc.NewBlobV0(share.MustNewV0Namespace([]byte("ns")), []byte("payload")) - require.NoError(t, err) - mockAPI := NewMockBlobAPI(t) - mockAPI.On("GetAll", mock.Anything, uint64(7), []share.Namespace{share.MustNewV0Namespace([]byte("ns"))}). - Return([]*blobrpc.Blob{b}, nil) - cl := NewBlobClient(mockAPI, BlobConfig{ - Logger: zerolog.Nop(), - Namespace: "ns", - DataNamespace: "ns", - }) - require.NotNil(t, cl) - res := cl.Retrieve(context.Background(), 7, ns) - require.Equal(t, StatusSuccess, res.Code) - require.Len(t, res.Data, 1) - require.Len(t, res.IDs, 1) -} - -func TestBlobClient_SubmitOptionsMerge(t *testing.T) { - ns := share.MustNewV0Namespace([]byte("ns")).Bytes() - mockAPI := NewMockBlobAPI(t) - mockAPI.On("Submit", mock.Anything, mock.Anything, mock.Anything). - Return(uint64(1), nil) - cl := NewBlobClient(mockAPI, BlobConfig{ - Logger: zerolog.Nop(), - Namespace: "ns", - DataNamespace: "ns", - }) - require.NotNil(t, cl) - - opts := map[string]any{"signer_address": "celestia1xyz"} - raw, err := json.Marshal(opts) - require.NoError(t, err) - - res := cl.Submit(context.Background(), [][]byte{[]byte("data")}, ns, raw) - require.Equal(t, StatusSuccess, res.Code) -} diff --git a/block/internal/da/client.go b/block/internal/da/client.go index 61fb1ec051..160a0c00e6 100644 --- a/block/internal/da/client.go +++ b/block/internal/da/client.go @@ -1,141 +1,150 @@ -// Package da provides a reusable wrapper around the core DA interface -// with common configuration for namespace handling and timeouts. package da import ( "context" + "encoding/json" "errors" "fmt" "strings" "time" + "github.com/celestiaorg/go-square/v3/share" + "github.com/evstack/ev-node/block/internal/common" + blobrpc "github.com/evstack/ev-node/pkg/da/jsonrpc" "github.com/rs/zerolog" - coreda "github.com/evstack/ev-node/core/da" + datypes "github.com/evstack/ev-node/pkg/da/types" ) -// Client is the interface representing the DA client. -type Client interface { - Submit(ctx context.Context, data [][]byte, gasPrice float64, namespace []byte, options []byte) coreda.ResultSubmit - Retrieve(ctx context.Context, height uint64, namespace []byte) coreda.ResultRetrieve - RetrieveHeaders(ctx context.Context, height uint64) coreda.ResultRetrieve - RetrieveData(ctx context.Context, height uint64) coreda.ResultRetrieve - RetrieveForcedInclusion(ctx context.Context, height uint64) coreda.ResultRetrieve - - GetHeaderNamespace() []byte - GetDataNamespace() []byte - GetForcedInclusionNamespace() []byte - HasForcedInclusionNamespace() bool - GetDA() coreda.DA -} - -// client provides a reusable wrapper around the core DA interface -// with common configuration for namespace handling and timeouts. -type client struct { - da coreda.DA - logger zerolog.Logger - defaultTimeout time.Duration - batchSize int - namespaceBz []byte - namespaceDataBz []byte - namespaceForcedInclusionBz []byte - hasForcedInclusionNs bool -} - -const ( - defaultRetrieveBatchSize = 150 -) - -// Config contains configuration for the DA client. +// Config contains configuration for the blob DA client. type Config struct { - DA coreda.DA + DA *blobrpc.Client Logger zerolog.Logger DefaultTimeout time.Duration - RetrieveBatchSize int Namespace string DataNamespace string ForcedInclusionNamespace string } -// NewClient creates a new DA client with pre-calculated namespace bytes. -func NewClient(cfg Config) *client { +// client wraps the blob RPC with namespace handling and error mapping. +// It is unexported; callers should use the exported Client interface. +type client struct { + blobAPI *blobrpc.BlobAPI + logger zerolog.Logger + defaultTimeout time.Duration + namespaceBz []byte + dataNamespaceBz []byte + forcedNamespaceBz []byte + hasForcedNamespace bool +} + +// Ensure client implements the FullClient interface (Client + BlobGetter + Verifier). +var _ FullClient = (*client)(nil) + +// NewClient creates a new blob client wrapper with pre-calculated namespace bytes. +func NewClient(cfg Config) FullClient { + if cfg.DA == nil { + return nil + } if cfg.DefaultTimeout == 0 { cfg.DefaultTimeout = 60 * time.Second } - if cfg.RetrieveBatchSize <= 0 { - cfg.RetrieveBatchSize = defaultRetrieveBatchSize - } - hasForcedInclusionNs := cfg.ForcedInclusionNamespace != "" - var namespaceForcedInclusionBz []byte - if hasForcedInclusionNs { - namespaceForcedInclusionBz = coreda.NamespaceFromString(cfg.ForcedInclusionNamespace).Bytes() + hasForcedNamespace := cfg.ForcedInclusionNamespace != "" + var forcedNamespaceBz []byte + if hasForcedNamespace { + forcedNamespaceBz = datypes.NamespaceFromString(cfg.ForcedInclusionNamespace).Bytes() } return &client{ - da: cfg.DA, - logger: cfg.Logger.With().Str("component", "da_client").Logger(), - defaultTimeout: cfg.DefaultTimeout, - batchSize: cfg.RetrieveBatchSize, - namespaceBz: coreda.NamespaceFromString(cfg.Namespace).Bytes(), - namespaceDataBz: coreda.NamespaceFromString(cfg.DataNamespace).Bytes(), - namespaceForcedInclusionBz: namespaceForcedInclusionBz, - hasForcedInclusionNs: hasForcedInclusionNs, + blobAPI: &cfg.DA.Blob, + logger: cfg.Logger.With().Str("component", "da_client").Logger(), + defaultTimeout: cfg.DefaultTimeout, + namespaceBz: datypes.NamespaceFromString(cfg.Namespace).Bytes(), + dataNamespaceBz: datypes.NamespaceFromString(cfg.DataNamespace).Bytes(), + forcedNamespaceBz: forcedNamespaceBz, + hasForcedNamespace: hasForcedNamespace, } } // Submit submits blobs to the DA layer with the specified options. -func (c *client) Submit(ctx context.Context, data [][]byte, gasPrice float64, namespace []byte, options []byte) coreda.ResultSubmit { - submitCtx, cancel := context.WithTimeout(ctx, c.defaultTimeout) - defer cancel() - - ids, err := c.da.SubmitWithOptions(submitCtx, data, gasPrice, namespace, options) - +func (c *client) Submit(ctx context.Context, data [][]byte, _ float64, namespace []byte, options []byte) datypes.ResultSubmit { // calculate blob size var blobSize uint64 - for _, blob := range data { - blobSize += uint64(len(blob)) + for _, b := range data { + blobSize += uint64(len(b)) } - // Handle errors returned by Submit + ns, err := share.NewNamespaceFromBytes(namespace) if err != nil { - if errors.Is(err, context.Canceled) { - c.logger.Debug().Msg("DA submission canceled due to context cancellation") - return coreda.ResultSubmit{ - BaseResult: coreda.BaseResult{ - Code: coreda.StatusContextCanceled, - Message: "submission canceled", - IDs: ids, - BlobSize: blobSize, + return datypes.ResultSubmit{ + BaseResult: datypes.BaseResult{ + Code: datypes.StatusError, + Message: fmt.Sprintf("invalid namespace: %v", err), + }, + } + } + + blobs := make([]*blobrpc.Blob, len(data)) + for i, raw := range data { + if uint64(len(raw)) > common.DefaultMaxBlobSize { + return datypes.ResultSubmit{ + BaseResult: datypes.BaseResult{ + Code: datypes.StatusTooBig, + Message: datypes.ErrBlobSizeOverLimit.Error(), }, } } - status := coreda.StatusError - switch { - case errors.Is(err, coreda.ErrTxTimedOut): - status = coreda.StatusNotIncludedInBlock - case errors.Is(err, coreda.ErrTxAlreadyInMempool): - status = coreda.StatusAlreadyInMempool - case errors.Is(err, coreda.ErrTxIncorrectAccountSequence): - status = coreda.StatusIncorrectAccountSequence - case errors.Is(err, coreda.ErrBlobSizeOverLimit): - status = coreda.StatusTooBig - case errors.Is(err, coreda.ErrContextDeadline): - status = coreda.StatusContextDeadline + blobs[i], err = blobrpc.NewBlobV0(ns, raw) + if err != nil { + return datypes.ResultSubmit{ + BaseResult: datypes.BaseResult{ + Code: datypes.StatusError, + Message: fmt.Sprintf("failed to build blob %d: %v", i, err), + }, + } } + } + + var submitOpts blobrpc.SubmitOptions + if len(options) > 0 { + if err := json.Unmarshal(options, &submitOpts); err != nil { + return datypes.ResultSubmit{ + BaseResult: datypes.BaseResult{ + Code: datypes.StatusError, + Message: fmt.Sprintf("failed to parse submit options: %v", err), + }, + } + } + } - // Use debug level for StatusTooBig as it gets handled later in submitToDA through recursive splitting - if status == coreda.StatusTooBig { - c.logger.Debug().Err(err).Uint64("status", uint64(status)).Msg("DA submission failed") + height, err := c.blobAPI.Submit(ctx, blobs, &submitOpts) + if err != nil { + code := datypes.StatusError + switch { + case errors.Is(err, context.Canceled): + code = datypes.StatusContextCanceled + case strings.Contains(err.Error(), datypes.ErrTxTimedOut.Error()): + code = datypes.StatusNotIncludedInBlock + case strings.Contains(err.Error(), datypes.ErrTxAlreadyInMempool.Error()): + code = datypes.StatusAlreadyInMempool + case strings.Contains(err.Error(), datypes.ErrTxIncorrectAccountSequence.Error()): + code = datypes.StatusIncorrectAccountSequence + case strings.Contains(err.Error(), datypes.ErrBlobSizeOverLimit.Error()): + code = datypes.StatusTooBig + case strings.Contains(err.Error(), datypes.ErrContextDeadline.Error()): + code = datypes.StatusContextDeadline + } + if code == datypes.StatusTooBig { + c.logger.Debug().Err(err).Uint64("status", uint64(code)).Msg("DA submission failed") } else { - c.logger.Error().Err(err).Uint64("status", uint64(status)).Msg("DA submission failed") + c.logger.Error().Err(err).Uint64("status", uint64(code)).Msg("DA submission failed") } - return coreda.ResultSubmit{ - BaseResult: coreda.BaseResult{ - Code: status, + return datypes.ResultSubmit{ + BaseResult: datypes.BaseResult{ + Code: code, Message: "failed to submit blobs: " + err.Error(), - IDs: ids, - SubmittedCount: uint64(len(ids)), + SubmittedCount: 0, Height: 0, Timestamp: time.Now(), BlobSize: blobSize, @@ -143,29 +152,25 @@ func (c *client) Submit(ctx context.Context, data [][]byte, gasPrice float64, na } } - if len(ids) == 0 && len(data) > 0 { - c.logger.Warn().Msg("DA submission returned no IDs for non-empty input data") - return coreda.ResultSubmit{ - BaseResult: coreda.BaseResult{ - Code: coreda.StatusError, - Message: "failed to submit blobs: no IDs returned despite non-empty input", + if len(blobs) == 0 { + return datypes.ResultSubmit{ + BaseResult: datypes.BaseResult{ + Code: datypes.StatusSuccess, + BlobSize: blobSize, + Height: height, }, } } - // Get height from the first ID - var height uint64 - if len(ids) > 0 { - height, _, err = coreda.SplitID(ids[0]) - if err != nil { - c.logger.Error().Err(err).Msg("failed to split ID") - } + ids := make([]datypes.ID, len(blobs)) + for i, b := range blobs { + ids[i] = blobrpc.MakeID(height, b.Commitment) } - c.logger.Debug().Int("num_ids", len(ids)).Msg("DA submission successful") - return coreda.ResultSubmit{ - BaseResult: coreda.BaseResult{ - Code: coreda.StatusSuccess, + + return datypes.ResultSubmit{ + BaseResult: datypes.BaseResult{ + Code: datypes.StatusSuccess, IDs: ids, SubmittedCount: uint64(len(ids)), Height: height, @@ -176,117 +181,104 @@ func (c *client) Submit(ctx context.Context, data [][]byte, gasPrice float64, na } // Retrieve retrieves blobs from the DA layer at the specified height and namespace. -func (c *client) Retrieve(ctx context.Context, height uint64, namespace []byte) coreda.ResultRetrieve { - getIDsCtx, cancel := context.WithTimeout(ctx, c.defaultTimeout) +// It uses GetAll to fetch all blobs at once. +func (c *client) Retrieve(ctx context.Context, height uint64, namespace []byte) datypes.ResultRetrieve { + ns, err := share.NewNamespaceFromBytes(namespace) + if err != nil { + return datypes.ResultRetrieve{ + BaseResult: datypes.BaseResult{ + Code: datypes.StatusError, + Message: fmt.Sprintf("invalid namespace: %v", err), + Height: height, + Timestamp: time.Now(), + }, + } + } + + getCtx, cancel := context.WithTimeout(ctx, c.defaultTimeout) defer cancel() - idsResult, err := c.da.GetIDs(getIDsCtx, height, namespace) + blobs, err := c.blobAPI.GetAll(getCtx, height, []share.Namespace{ns}) if err != nil { - // Handle specific "not found" error - if strings.Contains(err.Error(), coreda.ErrBlobNotFound.Error()) { - c.logger.Debug().Uint64("height", height).Msg("Blobs not found at height") - return coreda.ResultRetrieve{ - BaseResult: coreda.BaseResult{ - Code: coreda.StatusNotFound, - Message: coreda.ErrBlobNotFound.Error(), + // Handle known errors by substring because RPC may wrap them. + switch { + case strings.Contains(err.Error(), datypes.ErrBlobNotFound.Error()): + c.logger.Debug().Uint64("height", height).Msg("No blobs found at height") + return datypes.ResultRetrieve{ + BaseResult: datypes.BaseResult{ + Code: datypes.StatusNotFound, + Message: datypes.ErrBlobNotFound.Error(), Height: height, Timestamp: time.Now(), }, } - } - if strings.Contains(err.Error(), coreda.ErrHeightFromFuture.Error()) { - c.logger.Debug().Uint64("height", height).Msg("Blobs not found at height") - return coreda.ResultRetrieve{ - BaseResult: coreda.BaseResult{ - Code: coreda.StatusHeightFromFuture, - Message: coreda.ErrHeightFromFuture.Error(), + case strings.Contains(err.Error(), datypes.ErrHeightFromFuture.Error()): + return datypes.ResultRetrieve{ + BaseResult: datypes.BaseResult{ + Code: datypes.StatusHeightFromFuture, + Message: datypes.ErrHeightFromFuture.Error(), + Height: height, + Timestamp: time.Now(), + }, + } + default: + c.logger.Error().Uint64("height", height).Err(err).Msg("failed to get blobs") + return datypes.ResultRetrieve{ + BaseResult: datypes.BaseResult{ + Code: datypes.StatusError, + Message: fmt.Sprintf("failed to get blobs: %s", err.Error()), Height: height, Timestamp: time.Now(), }, } - } - // Handle other errors during GetIDs - c.logger.Error().Uint64("height", height).Err(err).Msg("Failed to get IDs") - return coreda.ResultRetrieve{ - BaseResult: coreda.BaseResult{ - Code: coreda.StatusError, - Message: fmt.Sprintf("failed to get IDs: %s", err.Error()), - Height: height, - Timestamp: time.Now(), - }, } } - // This check should technically be redundant if GetIDs correctly returns ErrBlobNotFound - if idsResult == nil || len(idsResult.IDs) == 0 { - c.logger.Debug().Uint64("height", height).Msg("No IDs found at height") - return coreda.ResultRetrieve{ - BaseResult: coreda.BaseResult{ - Code: coreda.StatusNotFound, - Message: coreda.ErrBlobNotFound.Error(), + if len(blobs) == 0 { + c.logger.Debug().Uint64("height", height).Msg("No blobs found at height") + return datypes.ResultRetrieve{ + BaseResult: datypes.BaseResult{ + Code: datypes.StatusNotFound, + Message: datypes.ErrBlobNotFound.Error(), Height: height, Timestamp: time.Now(), }, } } - // 2. Get Blobs using the retrieved IDs in batches - // Each batch has its own timeout while keeping the link to the parent context - batchSize := c.batchSize - blobs := make([][]byte, 0, len(idsResult.IDs)) - for i := 0; i < len(idsResult.IDs); i += batchSize { - end := min(i+batchSize, len(idsResult.IDs)) - - getBlobsCtx, cancel := context.WithTimeout(ctx, c.defaultTimeout) - batchBlobs, err := c.da.Get(getBlobsCtx, idsResult.IDs[i:end], namespace) - cancel() - if err != nil { - // Handle errors during Get - c.logger.Error().Uint64("height", height).Int("num_ids", len(idsResult.IDs)).Err(err).Msg("Failed to get blobs") - return coreda.ResultRetrieve{ - BaseResult: coreda.BaseResult{ - Code: coreda.StatusError, - Message: fmt.Sprintf("failed to get blobs for batch %d-%d: %s", i, end-1, err.Error()), - Height: height, - Timestamp: time.Now(), - }, - } - } - blobs = append(blobs, batchBlobs...) + + // Extract IDs and data from the blobs. + ids := make([]datypes.ID, len(blobs)) + data := make([]datypes.Blob, len(blobs)) + for i, b := range blobs { + ids[i] = blobrpc.MakeID(height, b.Commitment) + data[i] = b.Data() } - // Success - c.logger.Debug().Uint64("height", height).Int("num_blobs", len(blobs)).Msg("Successfully retrieved blobs") - return coreda.ResultRetrieve{ - BaseResult: coreda.BaseResult{ - Code: coreda.StatusSuccess, + + c.logger.Debug().Int("num_blobs", len(blobs)).Msg("retrieved blobs") + + return datypes.ResultRetrieve{ + BaseResult: datypes.BaseResult{ + Code: datypes.StatusSuccess, Height: height, - IDs: idsResult.IDs, - Timestamp: idsResult.Timestamp, + IDs: ids, + Timestamp: time.Now(), }, - Data: blobs, + Data: data, } } -// RetrieveHeaders retrieves blobs from the header namespace at the specified height. -func (c *client) RetrieveHeaders(ctx context.Context, height uint64) coreda.ResultRetrieve { - return c.Retrieve(ctx, height, c.namespaceBz) -} - -// RetrieveData retrieves blobs from the data namespace at the specified height. -func (c *client) RetrieveData(ctx context.Context, height uint64) coreda.ResultRetrieve { - return c.Retrieve(ctx, height, c.namespaceDataBz) -} - // RetrieveForcedInclusion retrieves blobs from the forced inclusion namespace at the specified height. -func (c *client) RetrieveForcedInclusion(ctx context.Context, height uint64) coreda.ResultRetrieve { - if !c.hasForcedInclusionNs { - return coreda.ResultRetrieve{ - BaseResult: coreda.BaseResult{ - Code: coreda.StatusError, +func (c *client) RetrieveForcedInclusion(ctx context.Context, height uint64) datypes.ResultRetrieve { + if !c.hasForcedNamespace { + return datypes.ResultRetrieve{ + BaseResult: datypes.BaseResult{ + Code: datypes.StatusError, Message: "forced inclusion namespace not configured", + Height: height, }, } } - return c.Retrieve(ctx, height, c.namespaceForcedInclusionBz) + return c.Retrieve(ctx, height, c.forcedNamespaceBz) } // GetHeaderNamespace returns the header namespace bytes. @@ -296,20 +288,125 @@ func (c *client) GetHeaderNamespace() []byte { // GetDataNamespace returns the data namespace bytes. func (c *client) GetDataNamespace() []byte { - return c.namespaceDataBz + return c.dataNamespaceBz } // GetForcedInclusionNamespace returns the forced inclusion namespace bytes. func (c *client) GetForcedInclusionNamespace() []byte { - return c.namespaceForcedInclusionBz + return c.forcedNamespaceBz } -// HasForcedInclusionNamespace returns whether forced inclusion namespace is configured. +// HasForcedInclusionNamespace reports whether forced inclusion namespace is configured. func (c *client) HasForcedInclusionNamespace() bool { - return c.hasForcedInclusionNs + return c.hasForcedNamespace +} + +// Get fetches blobs by their IDs. Used for visualization and fetching specific blobs. +func (c *client) Get(ctx context.Context, ids []datypes.ID, namespace []byte) ([]datypes.Blob, error) { + if len(ids) == 0 { + return nil, nil + } + + ns, err := share.NewNamespaceFromBytes(namespace) + if err != nil { + return nil, fmt.Errorf("invalid namespace: %w", err) + } + + getCtx, cancel := context.WithTimeout(ctx, c.defaultTimeout) + defer cancel() + + res := make([]datypes.Blob, 0, len(ids)) + for _, id := range ids { + height, commitment := blobrpc.SplitID(id) + if commitment == nil { + return nil, fmt.Errorf("invalid blob id: %x", id) + } + + b, err := c.blobAPI.Get(getCtx, height, ns, commitment) + if err != nil { + return nil, err + } + if b == nil { + continue + } + res = append(res, b.Data()) + } + + return res, nil } -// GetDA returns the underlying DA interface for advanced usage. -func (c *client) GetDA() coreda.DA { - return c.da +// GetProofs returns inclusion proofs for the provided IDs. +func (c *client) GetProofs(ctx context.Context, ids []datypes.ID, namespace []byte) ([]datypes.Proof, error) { + if len(ids) == 0 { + return []datypes.Proof{}, nil + } + + ns, err := share.NewNamespaceFromBytes(namespace) + if err != nil { + return nil, fmt.Errorf("invalid namespace: %w", err) + } + + getCtx, cancel := context.WithTimeout(ctx, c.defaultTimeout) + defer cancel() + + proofs := make([]datypes.Proof, len(ids)) + for i, id := range ids { + height, commitment := blobrpc.SplitID(id) + if commitment == nil { + return nil, fmt.Errorf("invalid blob id: %x", id) + } + + proof, err := c.blobAPI.GetProof(getCtx, height, ns, commitment) + if err != nil { + return nil, err + } + + bz, err := json.Marshal(proof) + if err != nil { + return nil, err + } + proofs[i] = bz + } + + return proofs, nil +} + +// Validate validates commitments against the corresponding proofs. +func (c *client) Validate(ctx context.Context, ids []datypes.ID, proofs []datypes.Proof, namespace []byte) ([]bool, error) { + if len(ids) != len(proofs) { + return nil, errors.New("number of IDs and proofs must match") + } + + if len(ids) == 0 { + return []bool{}, nil + } + + ns, err := share.NewNamespaceFromBytes(namespace) + if err != nil { + return nil, fmt.Errorf("invalid namespace: %w", err) + } + + getCtx, cancel := context.WithTimeout(ctx, c.defaultTimeout) + defer cancel() + + results := make([]bool, len(ids)) + for i, id := range ids { + var proof blobrpc.Proof + if err := json.Unmarshal(proofs[i], &proof); err != nil { + return nil, err + } + + height, commitment := blobrpc.SplitID(id) + if commitment == nil { + return nil, fmt.Errorf("invalid blob id: %x", id) + } + + included, err := c.blobAPI.Included(getCtx, height, ns, &proof, commitment) + if err != nil { + c.logger.Debug().Err(err).Uint64("height", height).Msg("blob inclusion check returned error") + } + results[i] = included + } + + return results, nil } diff --git a/block/internal/da/client_test.go b/block/internal/da/client_test.go index 1fa8414781..4b5041b6cf 100644 --- a/block/internal/da/client_test.go +++ b/block/internal/da/client_test.go @@ -2,747 +2,302 @@ package da import ( "context" + "encoding/json" "errors" "testing" - "time" + "github.com/celestiaorg/go-square/v3/share" + blobrpc "github.com/evstack/ev-node/pkg/da/jsonrpc" + "github.com/evstack/ev-node/pkg/da/jsonrpc/mocks" + datypes "github.com/evstack/ev-node/pkg/da/types" "github.com/rs/zerolog" - "gotest.tools/v3/assert" - - coreda "github.com/evstack/ev-node/core/da" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/mock" + "github.com/stretchr/testify/require" ) -// mockDA is a simple mock implementation of coreda.DA for testing -type mockDA struct { - submitFunc func(ctx context.Context, blobs []coreda.Blob, gasPrice float64, namespace []byte) ([]coreda.ID, error) - submitWithOptions func(ctx context.Context, blobs []coreda.Blob, gasPrice float64, namespace []byte, options []byte) ([]coreda.ID, error) - getIDsFunc func(ctx context.Context, height uint64, namespace []byte) (*coreda.GetIDsResult, error) - getFunc func(ctx context.Context, ids []coreda.ID, namespace []byte) ([]coreda.Blob, error) -} - -func (m *mockDA) Submit(ctx context.Context, blobs []coreda.Blob, gasPrice float64, namespace []byte) ([]coreda.ID, error) { - if m.submitFunc != nil { - return m.submitFunc(ctx, blobs, gasPrice, namespace) - } - return nil, nil -} - -func (m *mockDA) SubmitWithOptions(ctx context.Context, blobs []coreda.Blob, gasPrice float64, namespace []byte, options []byte) ([]coreda.ID, error) { - if m.submitWithOptions != nil { - return m.submitWithOptions(ctx, blobs, gasPrice, namespace, options) - } - return nil, nil -} - -func (m *mockDA) GetIDs(ctx context.Context, height uint64, namespace []byte) (*coreda.GetIDsResult, error) { - if m.getIDsFunc != nil { - return m.getIDsFunc(ctx, height, namespace) - } - return nil, errors.New("not implemented") +func makeBlobRPCClient(module *mocks.MockBlobModule) *blobrpc.Client { + var api blobrpc.BlobAPI + api.Internal.Submit = module.Submit + api.Internal.Get = module.Get + api.Internal.GetAll = module.GetAll + api.Internal.GetProof = module.GetProof + api.Internal.Included = module.Included + api.Internal.GetCommitmentProof = module.GetCommitmentProof + api.Internal.Subscribe = module.Subscribe + return &blobrpc.Client{Blob: api} } -func (m *mockDA) Get(ctx context.Context, ids []coreda.ID, namespace []byte) ([]coreda.Blob, error) { - if m.getFunc != nil { - return m.getFunc(ctx, ids, namespace) - } - return nil, errors.New("not implemented") -} - -func (m *mockDA) GetProofs(ctx context.Context, ids []coreda.ID, namespace []byte) ([]coreda.Proof, error) { - return nil, errors.New("not implemented") -} - -func (m *mockDA) Commit(ctx context.Context, blobs []coreda.Blob, namespace []byte) ([]coreda.Commitment, error) { - return nil, errors.New("not implemented") -} - -func (m *mockDA) Validate(ctx context.Context, ids []coreda.ID, proofs []coreda.Proof, namespace []byte) ([]bool, error) { - return nil, errors.New("not implemented") -} - -func TestNewClient(t *testing.T) { - tests := []struct { - name string - cfg Config +func TestClient_Submit_ErrorMapping(t *testing.T) { + ns := share.MustNewV0Namespace([]byte("ns")).Bytes() + testCases := []struct { + name string + err error + wantStatus datypes.StatusCode }{ - { - name: "with all namespaces", - cfg: Config{ - DA: &mockDA{}, - Logger: zerolog.Nop(), - DefaultTimeout: 5 * time.Second, - Namespace: "test-ns", - DataNamespace: "test-data-ns", - ForcedInclusionNamespace: "test-fi-ns", - }, - }, - { - name: "without forced inclusion namespace", - cfg: Config{ - DA: &mockDA{}, - Logger: zerolog.Nop(), - DefaultTimeout: 5 * time.Second, - Namespace: "test-ns", - DataNamespace: "test-data-ns", - }, - }, - { - name: "with default timeout", - cfg: Config{ - DA: &mockDA{}, - Logger: zerolog.Nop(), - Namespace: "test-ns", - DataNamespace: "test-data-ns", - }, - }, + {"timeout", datypes.ErrTxTimedOut, datypes.StatusNotIncludedInBlock}, + {"alreadyInMempool", datypes.ErrTxAlreadyInMempool, datypes.StatusAlreadyInMempool}, + {"seq", datypes.ErrTxIncorrectAccountSequence, datypes.StatusIncorrectAccountSequence}, + {"tooBig", datypes.ErrBlobSizeOverLimit, datypes.StatusTooBig}, + {"deadline", datypes.ErrContextDeadline, datypes.StatusContextDeadline}, + {"canceled", context.Canceled, datypes.StatusContextCanceled}, + {"other", errors.New("boom"), datypes.StatusError}, } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - client := NewClient(tt.cfg) - assert.Assert(t, client != nil) - assert.Assert(t, client.da != nil) - assert.Assert(t, len(client.namespaceBz) > 0) - assert.Assert(t, len(client.namespaceDataBz) > 0) - - if tt.cfg.ForcedInclusionNamespace != "" { - assert.Assert(t, client.hasForcedInclusionNs) - assert.Assert(t, len(client.namespaceForcedInclusionBz) > 0) - } else { - assert.Assert(t, !client.hasForcedInclusionNs) - } - - expectedTimeout := tt.cfg.DefaultTimeout - if expectedTimeout == 0 { - expectedTimeout = 60 * time.Second - } - assert.Equal(t, client.defaultTimeout, expectedTimeout) - }) - } -} + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + module := mocks.NewMockBlobModule(t) + module.On("Submit", mock.Anything, mock.Anything, mock.Anything).Return(uint64(0), tc.err) -func TestClient_HasForcedInclusionNamespace(t *testing.T) { - tests := []struct { - name string - cfg Config - expected bool - }{ - { - name: "with forced inclusion namespace", - cfg: Config{ - DA: &mockDA{}, - Logger: zerolog.Nop(), - Namespace: "test-ns", - DataNamespace: "test-data-ns", - ForcedInclusionNamespace: "test-fi-ns", - }, - expected: true, - }, - { - name: "without forced inclusion namespace", - cfg: Config{ - DA: &mockDA{}, + cl := NewClient(Config{ + DA: makeBlobRPCClient(module), Logger: zerolog.Nop(), - Namespace: "test-ns", - DataNamespace: "test-data-ns", - }, - expected: false, - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - client := NewClient(tt.cfg) - assert.Equal(t, client.HasForcedInclusionNamespace(), tt.expected) + Namespace: "ns", + DataNamespace: "ns", + }) + res := cl.Submit(context.Background(), [][]byte{[]byte("data")}, 0, ns, nil) + assert.Equal(t, tc.wantStatus, res.Code) }) } } -func TestClient_GetNamespaces(t *testing.T) { - cfg := Config{ - DA: &mockDA{}, - Logger: zerolog.Nop(), - Namespace: "test-header", - DataNamespace: "test-data", - ForcedInclusionNamespace: "test-fi", - } - - client := NewClient(cfg) - - headerNs := client.GetHeaderNamespace() - assert.Assert(t, len(headerNs) > 0) - - dataNs := client.GetDataNamespace() - assert.Assert(t, len(dataNs) > 0) +func TestClient_Submit_Success(t *testing.T) { + ns := share.MustNewV0Namespace([]byte("ns")).Bytes() + module := mocks.NewMockBlobModule(t) + module.On("Submit", mock.Anything, mock.Anything, mock.Anything).Return(uint64(10), nil) - fiNs := client.GetForcedInclusionNamespace() - assert.Assert(t, len(fiNs) > 0) - - // Namespaces should be different - assert.Assert(t, string(headerNs) != string(dataNs)) - assert.Assert(t, string(headerNs) != string(fiNs)) - assert.Assert(t, string(dataNs) != string(fiNs)) -} - -func TestClient_RetrieveForcedInclusion_NotConfigured(t *testing.T) { - cfg := Config{ - DA: &mockDA{}, + cl := NewClient(Config{ + DA: makeBlobRPCClient(module), Logger: zerolog.Nop(), - Namespace: "test-ns", - DataNamespace: "test-data-ns", - } - - client := NewClient(cfg) - ctx := context.Background() - - result := client.RetrieveForcedInclusion(ctx, 100) - assert.Equal(t, result.Code, coreda.StatusError) - assert.Assert(t, result.Message != "") + Namespace: "ns", + DataNamespace: "ns", + }) + res := cl.Submit(context.Background(), [][]byte{[]byte("data")}, 0, ns, nil) + require.Equal(t, datypes.StatusSuccess, res.Code) + require.Equal(t, uint64(10), res.Height) + require.Len(t, res.IDs, 1) } -func TestClient_GetDA(t *testing.T) { - mockDAInstance := &mockDA{} - cfg := Config{ - DA: mockDAInstance, +func TestClient_Submit_InvalidNamespace(t *testing.T) { + module := mocks.NewMockBlobModule(t) + cl := NewClient(Config{ + DA: makeBlobRPCClient(module), Logger: zerolog.Nop(), - Namespace: "test-ns", - DataNamespace: "test-data-ns", - } - - client := NewClient(cfg) - da := client.GetDA() - assert.Equal(t, da, mockDAInstance) + Namespace: "ns", + DataNamespace: "ns", + }) + res := cl.Submit(context.Background(), [][]byte{[]byte("data")}, 0, []byte{0x01, 0x02}, nil) + require.Equal(t, datypes.StatusError, res.Code) } -func TestClient_Submit(t *testing.T) { - logger := zerolog.Nop() +func TestClient_Retrieve_NotFound(t *testing.T) { + ns := share.MustNewV0Namespace([]byte("ns")).Bytes() + module := mocks.NewMockBlobModule(t) + module.On("GetAll", mock.Anything, mock.Anything, mock.Anything).Return([]*blobrpc.Blob(nil), datypes.ErrBlobNotFound) - testCases := []struct { - name string - data [][]byte - gasPrice float64 - options []byte - submitErr error - submitIDs [][]byte - expectedCode coreda.StatusCode - expectedErrMsg string - expectedIDs [][]byte - expectedCount uint64 - }{ - { - name: "successful submission", - data: [][]byte{[]byte("blob1"), []byte("blob2")}, - gasPrice: 1.0, - options: []byte("opts"), - submitIDs: [][]byte{[]byte("id1"), []byte("id2")}, - expectedCode: coreda.StatusSuccess, - expectedIDs: [][]byte{[]byte("id1"), []byte("id2")}, - expectedCount: 2, - }, - { - name: "context canceled error", - data: [][]byte{[]byte("blob1")}, - gasPrice: 1.0, - options: []byte("opts"), - submitErr: context.Canceled, - expectedCode: coreda.StatusContextCanceled, - expectedErrMsg: "submission canceled", - }, - { - name: "tx timed out error", - data: [][]byte{[]byte("blob1")}, - gasPrice: 1.0, - options: []byte("opts"), - submitErr: coreda.ErrTxTimedOut, - expectedCode: coreda.StatusNotIncludedInBlock, - expectedErrMsg: "failed to submit blobs: " + coreda.ErrTxTimedOut.Error(), - }, - { - name: "tx already in mempool error", - data: [][]byte{[]byte("blob1")}, - gasPrice: 1.0, - options: []byte("opts"), - submitErr: coreda.ErrTxAlreadyInMempool, - expectedCode: coreda.StatusAlreadyInMempool, - expectedErrMsg: "failed to submit blobs: " + coreda.ErrTxAlreadyInMempool.Error(), - }, - { - name: "incorrect account sequence error", - data: [][]byte{[]byte("blob1")}, - gasPrice: 1.0, - options: []byte("opts"), - submitErr: coreda.ErrTxIncorrectAccountSequence, - expectedCode: coreda.StatusIncorrectAccountSequence, - expectedErrMsg: "failed to submit blobs: " + coreda.ErrTxIncorrectAccountSequence.Error(), - }, - { - name: "blob size over limit error", - data: [][]byte{[]byte("blob1")}, - gasPrice: 1.0, - options: []byte("opts"), - submitErr: coreda.ErrBlobSizeOverLimit, - expectedCode: coreda.StatusTooBig, - expectedErrMsg: "failed to submit blobs: " + coreda.ErrBlobSizeOverLimit.Error(), - }, - { - name: "context deadline error", - data: [][]byte{[]byte("blob1")}, - gasPrice: 1.0, - options: []byte("opts"), - submitErr: coreda.ErrContextDeadline, - expectedCode: coreda.StatusContextDeadline, - expectedErrMsg: "failed to submit blobs: " + coreda.ErrContextDeadline.Error(), - }, - { - name: "generic submission error", - data: [][]byte{[]byte("blob1")}, - gasPrice: 1.0, - options: []byte("opts"), - submitErr: errors.New("some generic error"), - expectedCode: coreda.StatusError, - expectedErrMsg: "failed to submit blobs: some generic error", - }, - { - name: "no IDs returned for non-empty data", - data: [][]byte{[]byte("blob1")}, - gasPrice: 1.0, - options: []byte("opts"), - submitIDs: [][]byte{}, - expectedCode: coreda.StatusError, - expectedErrMsg: "failed to submit blobs: no IDs returned despite non-empty input", - }, - } - - for _, tc := range testCases { - t.Run(tc.name, func(t *testing.T) { - mockDAInstance := &mockDA{ - submitWithOptions: func(ctx context.Context, blobs []coreda.Blob, gasPrice float64, namespace []byte, options []byte) ([]coreda.ID, error) { - return tc.submitIDs, tc.submitErr - }, - } - - client := NewClient(Config{ - DA: mockDAInstance, - Logger: logger, - Namespace: "test-namespace", - DataNamespace: "test-data-namespace", - }) + cl := NewClient(Config{ + DA: makeBlobRPCClient(module), + Logger: zerolog.Nop(), + Namespace: "ns", + DataNamespace: "ns", + }) + res := cl.Retrieve(context.Background(), 5, ns) + require.Equal(t, datypes.StatusNotFound, res.Code) +} - encodedNamespace := coreda.NamespaceFromString("test-namespace") - result := client.Submit(context.Background(), tc.data, tc.gasPrice, encodedNamespace.Bytes(), tc.options) - - assert.Equal(t, tc.expectedCode, result.Code) - if tc.expectedErrMsg != "" { - assert.Assert(t, result.Message != "") - } - if tc.expectedIDs != nil { - assert.Equal(t, len(tc.expectedIDs), len(result.IDs)) - } - if tc.expectedCount != 0 { - assert.Equal(t, tc.expectedCount, result.SubmittedCount) - } - }) - } +func TestClient_Retrieve_Success(t *testing.T) { + ns := share.MustNewV0Namespace([]byte("ns")) + nsBz := ns.Bytes() + b, err := blobrpc.NewBlobV0(ns, []byte("payload")) + require.NoError(t, err) + module := mocks.NewMockBlobModule(t) + // GetAll returns all blobs at the height/namespace + module.On("GetAll", mock.Anything, uint64(7), mock.Anything).Return([]*blobrpc.Blob{b}, nil) + + cl := NewClient(Config{ + DA: makeBlobRPCClient(module), + Logger: zerolog.Nop(), + Namespace: "ns", + DataNamespace: "ns", + }) + res := cl.Retrieve(context.Background(), 7, nsBz) + require.Equal(t, datypes.StatusSuccess, res.Code) + require.Len(t, res.Data, 1) + require.Len(t, res.IDs, 1) } -func TestClient_Retrieve(t *testing.T) { - logger := zerolog.Nop() - dataLayerHeight := uint64(100) - mockIDs := [][]byte{[]byte("id1"), []byte("id2")} - mockBlobs := [][]byte{[]byte("blobA"), []byte("blobB")} - mockTimestamp := time.Now() +func TestClient_SubmitOptionsMerge(t *testing.T) { + ns := share.MustNewV0Namespace([]byte("ns")).Bytes() + module := mocks.NewMockBlobModule(t) + module.On("Submit", mock.Anything, mock.Anything, mock.Anything).Return(uint64(1), nil) - testCases := []struct { - name string - getIDsResult *coreda.GetIDsResult - getIDsErr error - getBlobsErr error - expectedCode coreda.StatusCode - expectedErrMsg string - expectedIDs [][]byte - expectedData [][]byte - expectedHeight uint64 - }{ - { - name: "successful retrieval", - getIDsResult: &coreda.GetIDsResult{ - IDs: mockIDs, - Timestamp: mockTimestamp, - }, - expectedCode: coreda.StatusSuccess, - expectedIDs: mockIDs, - expectedData: mockBlobs, - expectedHeight: dataLayerHeight, - }, - { - name: "blob not found error during GetIDs", - getIDsErr: coreda.ErrBlobNotFound, - expectedCode: coreda.StatusNotFound, - expectedErrMsg: coreda.ErrBlobNotFound.Error(), - expectedHeight: dataLayerHeight, - }, - { - name: "height from future error during GetIDs", - getIDsErr: coreda.ErrHeightFromFuture, - expectedCode: coreda.StatusHeightFromFuture, - expectedErrMsg: coreda.ErrHeightFromFuture.Error(), - expectedHeight: dataLayerHeight, - }, - { - name: "generic error during GetIDs", - getIDsErr: errors.New("failed to connect to DA"), - expectedCode: coreda.StatusError, - expectedErrMsg: "failed to get IDs: failed to connect to DA", - expectedHeight: dataLayerHeight, - }, - { - name: "GetIDs returns nil result", - getIDsResult: nil, - expectedCode: coreda.StatusNotFound, - expectedErrMsg: coreda.ErrBlobNotFound.Error(), - expectedHeight: dataLayerHeight, - }, - { - name: "GetIDs returns empty IDs", - getIDsResult: &coreda.GetIDsResult{ - IDs: [][]byte{}, - Timestamp: mockTimestamp, - }, - expectedCode: coreda.StatusNotFound, - expectedErrMsg: coreda.ErrBlobNotFound.Error(), - expectedHeight: dataLayerHeight, - }, - { - name: "error during Get (blobs retrieval)", - getIDsResult: &coreda.GetIDsResult{ - IDs: mockIDs, - Timestamp: mockTimestamp, - }, - getBlobsErr: errors.New("network error during blob retrieval"), - expectedCode: coreda.StatusError, - expectedErrMsg: "failed to get blobs for batch 0-1: network error during blob retrieval", - expectedHeight: dataLayerHeight, - }, - } + cl := NewClient(Config{ + DA: makeBlobRPCClient(module), + Logger: zerolog.Nop(), + Namespace: "ns", + DataNamespace: "ns", + }) - for _, tc := range testCases { - t.Run(tc.name, func(t *testing.T) { - mockDAInstance := &mockDA{ - getIDsFunc: func(ctx context.Context, height uint64, namespace []byte) (*coreda.GetIDsResult, error) { - return tc.getIDsResult, tc.getIDsErr - }, - getFunc: func(ctx context.Context, ids []coreda.ID, namespace []byte) ([]coreda.Blob, error) { - if tc.getBlobsErr != nil { - return nil, tc.getBlobsErr - } - return mockBlobs, nil - }, - } - - client := NewClient(Config{ - DA: mockDAInstance, - Logger: logger, - Namespace: "test-namespace", - DataNamespace: "test-data-namespace", - DefaultTimeout: 5 * time.Second, - }) + opts := map[string]any{"signer_address": "signer1xyz"} + raw, err := json.Marshal(opts) + require.NoError(t, err) - encodedNamespace := coreda.NamespaceFromString("test-namespace") - result := client.Retrieve(context.Background(), dataLayerHeight, encodedNamespace.Bytes()) - - assert.Equal(t, tc.expectedCode, result.Code) - assert.Equal(t, tc.expectedHeight, result.Height) - if tc.expectedErrMsg != "" { - assert.Assert(t, result.Message != "") - } - if tc.expectedIDs != nil { - assert.Equal(t, len(tc.expectedIDs), len(result.IDs)) - } - if tc.expectedData != nil { - assert.Equal(t, len(tc.expectedData), len(result.Data)) - } - }) - } + res := cl.Submit(context.Background(), [][]byte{[]byte("data")}, 0, ns, raw) + require.Equal(t, datypes.StatusSuccess, res.Code) } -func TestClient_Retrieve_Timeout(t *testing.T) { - logger := zerolog.Nop() - dataLayerHeight := uint64(100) - encodedNamespace := coreda.NamespaceFromString("test-namespace") - - t.Run("timeout during GetIDs", func(t *testing.T) { - mockDAInstance := &mockDA{ - getIDsFunc: func(ctx context.Context, height uint64, namespace []byte) (*coreda.GetIDsResult, error) { - <-ctx.Done() // Wait for context cancellation - return nil, context.DeadlineExceeded - }, +// TestClient_Get tests the Get method. +func TestClient_Get(t *testing.T) { + ns := share.MustNewV0Namespace([]byte("ns")) + nsBz := ns.Bytes() + + t.Run("Get fetches blobs by IDs", func(t *testing.T) { + module := mocks.NewMockBlobModule(t) + + blobs := make([]*blobrpc.Blob, 3) + ids := make([]datypes.ID, 3) + for i := 0; i < 3; i++ { + blb, err := blobrpc.NewBlobV0(ns, []byte{byte(i)}) + require.NoError(t, err) + blobs[i] = blb + ids[i] = blobrpc.MakeID(uint64(100+i), blb.Commitment) + module.On("Get", mock.Anything, uint64(100+i), ns, blb.Commitment).Return(blb, nil).Once() } - client := NewClient(Config{ - DA: mockDAInstance, - Logger: logger, - Namespace: "test-namespace", - DataNamespace: "test-data-namespace", - DefaultTimeout: 1 * time.Millisecond, + cl := NewClient(Config{ + DA: makeBlobRPCClient(module), + Logger: zerolog.Nop(), + Namespace: "ns", + DataNamespace: "ns", }) - result := client.Retrieve(context.Background(), dataLayerHeight, encodedNamespace.Bytes()) - - assert.Equal(t, coreda.StatusError, result.Code) - assert.Assert(t, result.Message != "") - }) - - t.Run("timeout during Get", func(t *testing.T) { - mockIDs := [][]byte{[]byte("id1")} - mockTimestamp := time.Now() - - mockDAInstance := &mockDA{ - getIDsFunc: func(ctx context.Context, height uint64, namespace []byte) (*coreda.GetIDsResult, error) { - return &coreda.GetIDsResult{ - IDs: mockIDs, - Timestamp: mockTimestamp, - }, nil - }, - getFunc: func(ctx context.Context, ids []coreda.ID, namespace []byte) ([]coreda.Blob, error) { - <-ctx.Done() // Wait for context cancellation - return nil, context.DeadlineExceeded - }, + result, err := cl.Get(context.Background(), ids, nsBz) + require.NoError(t, err) + require.Len(t, result, 3) + for i := 0; i < 3; i++ { + assert.Equal(t, blobs[i].Data(), result[i]) } + }) - client := NewClient(Config{ - DA: mockDAInstance, - Logger: logger, - Namespace: "test-namespace", - DataNamespace: "test-data-namespace", - DefaultTimeout: 1 * time.Millisecond, + t.Run("Get propagates errors", func(t *testing.T) { + module := mocks.NewMockBlobModule(t) + blb, _ := blobrpc.NewBlobV0(ns, []byte{0}) + ids := []datypes.ID{blobrpc.MakeID(100, blb.Commitment)} + module.On("Get", mock.Anything, uint64(100), ns, blb.Commitment).Return(nil, errors.New("network error")).Once() + + cl := NewClient(Config{ + DA: makeBlobRPCClient(module), + Logger: zerolog.Nop(), + Namespace: "ns", + DataNamespace: "ns", }) - result := client.Retrieve(context.Background(), dataLayerHeight, encodedNamespace.Bytes()) - - assert.Equal(t, coreda.StatusError, result.Code) - assert.Assert(t, result.Message != "") + _, err := cl.Get(context.Background(), ids, nsBz) + require.Error(t, err) + assert.Contains(t, err.Error(), "network error") }) } -func TestClient_RetrieveHeaders(t *testing.T) { - logger := zerolog.Nop() - dataLayerHeight := uint64(100) - mockIDs := [][]byte{[]byte("id1")} - mockBlobs := [][]byte{[]byte("header-blob")} - mockTimestamp := time.Now() - - mockDAInstance := &mockDA{ - getIDsFunc: func(ctx context.Context, height uint64, namespace []byte) (*coreda.GetIDsResult, error) { - return &coreda.GetIDsResult{ - IDs: mockIDs, - Timestamp: mockTimestamp, - }, nil - }, - getFunc: func(ctx context.Context, ids []coreda.ID, namespace []byte) ([]coreda.Blob, error) { - return mockBlobs, nil - }, - } - - client := NewClient(Config{ - DA: mockDAInstance, - Logger: logger, - Namespace: "test-header-ns", - DataNamespace: "test-data-ns", - }) +// TestClient_GetProofs tests the GetProofs method. +func TestClient_GetProofs(t *testing.T) { + ns := share.MustNewV0Namespace([]byte("ns")) + nsBz := ns.Bytes() - result := client.RetrieveHeaders(context.Background(), dataLayerHeight) + module := mocks.NewMockBlobModule(t) - assert.Equal(t, coreda.StatusSuccess, result.Code) - assert.Equal(t, dataLayerHeight, result.Height) - assert.Equal(t, len(mockBlobs), len(result.Data)) -} - -func TestClient_RetrieveData(t *testing.T) { - logger := zerolog.Nop() - dataLayerHeight := uint64(200) - mockIDs := [][]byte{[]byte("id1"), []byte("id2")} - mockBlobs := [][]byte{[]byte("data-blob-1"), []byte("data-blob-2")} - mockTimestamp := time.Now() - - mockDAInstance := &mockDA{ - getIDsFunc: func(ctx context.Context, height uint64, namespace []byte) (*coreda.GetIDsResult, error) { - return &coreda.GetIDsResult{ - IDs: mockIDs, - Timestamp: mockTimestamp, - }, nil - }, - getFunc: func(ctx context.Context, ids []coreda.ID, namespace []byte) ([]coreda.Blob, error) { - return mockBlobs, nil - }, + ids := make([]datypes.ID, 3) + for i := 0; i < 3; i++ { + blb, _ := blobrpc.NewBlobV0(ns, []byte{byte(i)}) + ids[i] = blobrpc.MakeID(uint64(200+i), blb.Commitment) + module.On("GetProof", mock.Anything, uint64(200+i), ns, blb.Commitment).Return(&blobrpc.Proof{}, nil).Once() } - client := NewClient(Config{ - DA: mockDAInstance, - Logger: logger, - Namespace: "test-header-ns", - DataNamespace: "test-data-ns", + cl := NewClient(Config{ + DA: makeBlobRPCClient(module), + Logger: zerolog.Nop(), + Namespace: "ns", + DataNamespace: "ns", }) - result := client.RetrieveData(context.Background(), dataLayerHeight) - - assert.Equal(t, coreda.StatusSuccess, result.Code) - assert.Equal(t, dataLayerHeight, result.Height) - assert.Equal(t, len(mockBlobs), len(result.Data)) + proofs, err := cl.GetProofs(context.Background(), ids, nsBz) + require.NoError(t, err) + require.Len(t, proofs, 3) } -func TestClient_RetrieveBatched(t *testing.T) { - logger := zerolog.Nop() - dataLayerHeight := uint64(100) - - // Create 200 IDs to exceed default batch size - numIDs := 200 - mockIDs := make([][]byte, numIDs) - for i := range numIDs { - mockIDs[i] = []byte{byte(i)} - } +// TestClient_Validate tests the Validate method. +func TestClient_Validate(t *testing.T) { + ns := share.MustNewV0Namespace([]byte("ns")) + nsBz := ns.Bytes() + + t.Run("Validate with mixed results", func(t *testing.T) { + module := mocks.NewMockBlobModule(t) + + ids := make([]datypes.ID, 3) + proofs := make([]datypes.Proof, 3) + for i := 0; i < 3; i++ { + blb, _ := blobrpc.NewBlobV0(ns, []byte{byte(i)}) + ids[i] = blobrpc.MakeID(uint64(300+i), blb.Commitment) + proofBz, _ := json.Marshal(&blobrpc.Proof{}) + proofs[i] = proofBz + module.On("Included", mock.Anything, uint64(300+i), ns, mock.Anything, blb.Commitment).Return(i%2 == 0, nil).Once() + } - // Track which batches were requested - batchCalls := []int{} - - mockDAInstance := &mockDA{ - getIDsFunc: func(ctx context.Context, height uint64, namespace []byte) (*coreda.GetIDsResult, error) { - return &coreda.GetIDsResult{ - IDs: mockIDs, - Timestamp: time.Now(), - }, nil - }, - getFunc: func(ctx context.Context, ids []coreda.ID, namespace []byte) ([]coreda.Blob, error) { - batchCalls = append(batchCalls, len(ids)) - // Return a blob for each ID in the batch - blobs := make([][]byte, len(ids)) - for i := range ids { - blobs[i] = []byte("blob") - } - return blobs, nil - }, - } + cl := NewClient(Config{ + DA: makeBlobRPCClient(module), + Logger: zerolog.Nop(), + Namespace: "ns", + DataNamespace: "ns", + }) - client := NewClient(Config{ - DA: mockDAInstance, - Logger: logger, - Namespace: "test-ns", - DataNamespace: "test-data-ns", - RetrieveBatchSize: 50, // Set smaller batch size for testing + results, err := cl.Validate(context.Background(), ids, proofs, nsBz) + require.NoError(t, err) + require.Len(t, results, 3) + for i := 0; i < 3; i++ { + assert.Equal(t, i%2 == 0, results[i]) + } }) - encodedNamespace := coreda.NamespaceFromString("test-ns") - result := client.Retrieve(context.Background(), dataLayerHeight, encodedNamespace.Bytes()) + t.Run("Validate continues on inclusion check error", func(t *testing.T) { + module := mocks.NewMockBlobModule(t) - assert.Equal(t, coreda.StatusSuccess, result.Code) - assert.Equal(t, numIDs, len(result.Data)) - - // Should have made 4 batches: 50 + 50 + 50 + 50 = 200 - assert.Equal(t, 4, len(batchCalls)) - assert.Equal(t, 50, batchCalls[0]) - assert.Equal(t, 50, batchCalls[1]) - assert.Equal(t, 50, batchCalls[2]) - assert.Equal(t, 50, batchCalls[3]) -} - -func TestClient_RetrieveBatched_PartialBatch(t *testing.T) { - logger := zerolog.Nop() - dataLayerHeight := uint64(100) + blb0, _ := blobrpc.NewBlobV0(ns, []byte{0}) + blb1, _ := blobrpc.NewBlobV0(ns, []byte{1}) + ids := []datypes.ID{ + blobrpc.MakeID(400, blb0.Commitment), + blobrpc.MakeID(401, blb1.Commitment), + } + proofs := make([]datypes.Proof, 2) + for i := range proofs { + proofs[i], _ = json.Marshal(&blobrpc.Proof{}) + } - // Create 175 IDs to test partial batch at the end - numIDs := 175 - mockIDs := make([][]byte, numIDs) - for i := range numIDs { - mockIDs[i] = []byte{byte(i)} - } + module.On("Included", mock.Anything, uint64(400), ns, mock.Anything, blb0.Commitment).Return(true, nil).Once() + module.On("Included", mock.Anything, uint64(401), ns, mock.Anything, blb1.Commitment).Return(false, errors.New("check failed")).Once() - batchCalls := []int{} - - mockDAInstance := &mockDA{ - getIDsFunc: func(ctx context.Context, height uint64, namespace []byte) (*coreda.GetIDsResult, error) { - return &coreda.GetIDsResult{ - IDs: mockIDs, - Timestamp: time.Now(), - }, nil - }, - getFunc: func(ctx context.Context, ids []coreda.ID, namespace []byte) ([]coreda.Blob, error) { - batchCalls = append(batchCalls, len(ids)) - blobs := make([][]byte, len(ids)) - for i := range ids { - blobs[i] = []byte("blob") - } - return blobs, nil - }, - } + cl := NewClient(Config{ + DA: makeBlobRPCClient(module), + Logger: zerolog.Nop(), + Namespace: "ns", + DataNamespace: "ns", + }) - client := NewClient(Config{ - DA: mockDAInstance, - Logger: logger, - Namespace: "test-ns", - DataNamespace: "test-data-ns", - RetrieveBatchSize: 50, + results, err := cl.Validate(context.Background(), ids, proofs, nsBz) + require.NoError(t, err) + assert.True(t, results[0]) + assert.False(t, results[1]) }) - encodedNamespace := coreda.NamespaceFromString("test-ns") - result := client.Retrieve(context.Background(), dataLayerHeight, encodedNamespace.Bytes()) - - assert.Equal(t, coreda.StatusSuccess, result.Code) - assert.Equal(t, numIDs, len(result.Data)) - - // Should have made 4 batches: 50 + 50 + 50 + 25 = 175 - assert.Equal(t, 4, len(batchCalls)) - assert.Equal(t, 50, batchCalls[0]) - assert.Equal(t, 50, batchCalls[1]) - assert.Equal(t, 50, batchCalls[2]) - assert.Equal(t, 25, batchCalls[3]) // Partial batch -} - -func TestClient_RetrieveBatched_ErrorInSecondBatch(t *testing.T) { - logger := zerolog.Nop() - dataLayerHeight := uint64(100) - - // Create 200 IDs to require multiple batches - numIDs := 200 - mockIDs := make([][]byte, numIDs) - for i := range numIDs { - mockIDs[i] = []byte{byte(i)} - } - - batchCallCount := 0 - - mockDAInstance := &mockDA{ - getIDsFunc: func(ctx context.Context, height uint64, namespace []byte) (*coreda.GetIDsResult, error) { - return &coreda.GetIDsResult{ - IDs: mockIDs, - Timestamp: time.Now(), - }, nil - }, - getFunc: func(ctx context.Context, ids []coreda.ID, namespace []byte) ([]coreda.Blob, error) { - batchCallCount++ - // Fail on second batch - if batchCallCount == 2 { - return nil, errors.New("network error in batch 2") - } - blobs := make([][]byte, len(ids)) - for i := range ids { - blobs[i] = []byte("blob") - } - return blobs, nil - }, - } + t.Run("Validate rejects mismatched ids and proofs", func(t *testing.T) { + module := mocks.NewMockBlobModule(t) + cl := NewClient(Config{ + DA: makeBlobRPCClient(module), + Logger: zerolog.Nop(), + Namespace: "ns", + DataNamespace: "ns", + }) - client := NewClient(Config{ - DA: mockDAInstance, - Logger: logger, - Namespace: "test-ns", - DataNamespace: "test-data-ns", - RetrieveBatchSize: 50, + _, err := cl.Validate(context.Background(), make([]datypes.ID, 3), make([]datypes.Proof, 2), nsBz) + require.Error(t, err) + assert.Contains(t, err.Error(), "must match") }) - - encodedNamespace := coreda.NamespaceFromString("test-ns") - result := client.Retrieve(context.Background(), dataLayerHeight, encodedNamespace.Bytes()) - - assert.Equal(t, coreda.StatusError, result.Code) - assert.Assert(t, result.Message != "") - // Error message should mention the batch range - assert.Assert(t, len(result.Message) > 0) } diff --git a/block/internal/da/forced_inclusion_retriever.go b/block/internal/da/forced_inclusion_retriever.go index c5ff15945a..f73ecd7568 100644 --- a/block/internal/da/forced_inclusion_retriever.go +++ b/block/internal/da/forced_inclusion_retriever.go @@ -8,7 +8,7 @@ import ( "github.com/rs/zerolog" - coreda "github.com/evstack/ev-node/core/da" + datypes "github.com/evstack/ev-node/pkg/da/types" "github.com/evstack/ev-node/pkg/genesis" "github.com/evstack/ev-node/types" ) @@ -74,22 +74,22 @@ func (r *ForcedInclusionRetriever) RetrieveForcedIncludedTxs(ctx context.Context Txs: [][]byte{}, } - epochEndResult := r.client.RetrieveForcedInclusion(ctx, epochEnd) - if epochEndResult.Code == coreda.StatusHeightFromFuture { + epochEndResult := r.client.Retrieve(ctx, epochEnd, r.client.GetForcedInclusionNamespace()) + if epochEndResult.Code == datypes.StatusHeightFromFuture { r.logger.Debug(). Uint64("epoch_end", epochEnd). Msg("epoch end height not yet available on DA - backoff required") - return nil, fmt.Errorf("%w: epoch end height %d not yet available", coreda.ErrHeightFromFuture, epochEnd) + return nil, fmt.Errorf("%w: epoch end height %d not yet available", datypes.ErrHeightFromFuture, epochEnd) } epochStartResult := epochEndResult if epochStart != epochEnd { - epochStartResult = r.client.RetrieveForcedInclusion(ctx, epochStart) - if epochStartResult.Code == coreda.StatusHeightFromFuture { + epochStartResult = r.client.Retrieve(ctx, epochStart, r.client.GetForcedInclusionNamespace()) + if epochStartResult.Code == datypes.StatusHeightFromFuture { r.logger.Debug(). Uint64("epoch_start", epochStart). Msg("epoch start height not yet available on DA - backoff required") - return nil, fmt.Errorf("%w: epoch start height %d not yet available", coreda.ErrHeightFromFuture, epochStart) + return nil, fmt.Errorf("%w: epoch start height %d not yet available", datypes.ErrHeightFromFuture, epochStart) } } @@ -106,7 +106,7 @@ func (r *ForcedInclusionRetriever) RetrieveForcedIncludedTxs(ctx context.Context // Process heights between start and end (exclusive) for epochHeight := epochStart + 1; epochHeight < epochEnd; epochHeight++ { - result := r.client.RetrieveForcedInclusion(ctx, epochHeight) + result := r.client.Retrieve(ctx, epochHeight, r.client.GetForcedInclusionNamespace()) err = r.processForcedInclusionBlobs(event, result, epochHeight) processErrs = errors.Join(processErrs, err) @@ -141,15 +141,15 @@ func (r *ForcedInclusionRetriever) RetrieveForcedIncludedTxs(ctx context.Context // processForcedInclusionBlobs processes blobs from a single DA height for forced inclusion. func (r *ForcedInclusionRetriever) processForcedInclusionBlobs( event *ForcedInclusionEvent, - result coreda.ResultRetrieve, + result datypes.ResultRetrieve, height uint64, ) error { - if result.Code == coreda.StatusNotFound { + if result.Code == datypes.StatusNotFound { r.logger.Debug().Uint64("height", height).Msg("no forced inclusion blobs at height") return nil } - if result.Code != coreda.StatusSuccess { + if result.Code != datypes.StatusSuccess { return fmt.Errorf("failed to retrieve forced inclusion blobs at height %d: %s", height, result.Message) } diff --git a/block/internal/da/forced_inclusion_retriever_test.go b/block/internal/da/forced_inclusion_retriever_test.go index 4492da3f6c..f4ab3544f2 100644 --- a/block/internal/da/forced_inclusion_retriever_test.go +++ b/block/internal/da/forced_inclusion_retriever_test.go @@ -2,25 +2,22 @@ package da import ( "context" - "errors" "testing" "time" "github.com/rs/zerolog" + "github.com/stretchr/testify/mock" "gotest.tools/v3/assert" - coreda "github.com/evstack/ev-node/core/da" + datypes "github.com/evstack/ev-node/pkg/da/types" "github.com/evstack/ev-node/pkg/genesis" + "github.com/evstack/ev-node/test/mocks" ) func TestNewForcedInclusionRetriever(t *testing.T) { - client := NewClient(Config{ - DA: &mockDA{}, - Logger: zerolog.Nop(), - Namespace: "test-ns", - DataNamespace: "test-data-ns", - ForcedInclusionNamespace: "test-fi-ns", - }) + client := mocks.NewMockClient(t) + client.On("HasForcedInclusionNamespace").Return(true).Maybe() + client.On("GetForcedInclusionNamespace").Return(datypes.NamespaceFromString("test-fi-ns").Bytes()).Maybe() gen := genesis.Genesis{ DAStartHeight: 100, @@ -29,17 +26,11 @@ func TestNewForcedInclusionRetriever(t *testing.T) { retriever := NewForcedInclusionRetriever(client, gen, zerolog.Nop()) assert.Assert(t, retriever != nil) - assert.Equal(t, retriever.daEpochSize, uint64(10)) } func TestForcedInclusionRetriever_RetrieveForcedIncludedTxs_NoNamespace(t *testing.T) { - client := NewClient(Config{ - DA: &mockDA{}, - Logger: zerolog.Nop(), - Namespace: "test-ns", - DataNamespace: "test-data-ns", - // No forced inclusion namespace - }) + client := mocks.NewMockClient(t) + client.On("HasForcedInclusionNamespace").Return(false).Once() gen := genesis.Genesis{ DAStartHeight: 100, @@ -55,13 +46,10 @@ func TestForcedInclusionRetriever_RetrieveForcedIncludedTxs_NoNamespace(t *testi } func TestForcedInclusionRetriever_RetrieveForcedIncludedTxs_NotAtEpochStart(t *testing.T) { - client := NewClient(Config{ - DA: &mockDA{}, - Logger: zerolog.Nop(), - Namespace: "test-ns", - DataNamespace: "test-data-ns", - ForcedInclusionNamespace: "test-fi-ns", - }) + client := mocks.NewMockClient(t) + fiNs := datypes.NamespaceFromString("test-fi-ns").Bytes() + client.On("HasForcedInclusionNamespace").Return(true).Once() + client.On("GetForcedInclusionNamespace").Return(fiNs).Maybe() gen := genesis.Genesis{ DAStartHeight: 100, @@ -87,25 +75,14 @@ func TestForcedInclusionRetriever_RetrieveForcedIncludedTxs_EpochStartSuccess(t []byte("tx3"), } - mockDAInstance := &mockDA{ - getIDsFunc: func(ctx context.Context, height uint64, namespace []byte) (*coreda.GetIDsResult, error) { - return &coreda.GetIDsResult{ - IDs: []coreda.ID{[]byte("id1"), []byte("id2"), []byte("id3")}, - Timestamp: time.Now(), - }, nil - }, - getFunc: func(ctx context.Context, ids []coreda.ID, namespace []byte) ([]coreda.Blob, error) { - return testBlobs, nil - }, - } - - client := NewClient(Config{ - DA: mockDAInstance, - Logger: zerolog.Nop(), - Namespace: "test-ns", - DataNamespace: "test-data-ns", - ForcedInclusionNamespace: "test-fi-ns", - }) + client := mocks.NewMockClient(t) + fiNs := datypes.NamespaceFromString("test-fi-ns").Bytes() + client.On("HasForcedInclusionNamespace").Return(true).Once() + client.On("GetForcedInclusionNamespace").Return(fiNs).Maybe() + client.On("Retrieve", mock.Anything, mock.Anything, fiNs).Return(datypes.ResultRetrieve{ + BaseResult: datypes.BaseResult{Code: datypes.StatusSuccess, IDs: []datypes.ID{[]byte("id1"), []byte("id2"), []byte("id3")}, Timestamp: time.Now()}, + Data: testBlobs, + }).Once() gen := genesis.Genesis{ DAStartHeight: 100, @@ -126,19 +103,13 @@ func TestForcedInclusionRetriever_RetrieveForcedIncludedTxs_EpochStartSuccess(t } func TestForcedInclusionRetriever_RetrieveForcedIncludedTxs_EpochStartNotAvailable(t *testing.T) { - mockDAInstance := &mockDA{ - getIDsFunc: func(ctx context.Context, height uint64, namespace []byte) (*coreda.GetIDsResult, error) { - return nil, coreda.ErrHeightFromFuture - }, - } - - client := NewClient(Config{ - DA: mockDAInstance, - Logger: zerolog.Nop(), - Namespace: "test-ns", - DataNamespace: "test-data-ns", - ForcedInclusionNamespace: "test-fi-ns", - }) + client := mocks.NewMockClient(t) + fiNs := datypes.NamespaceFromString("test-fi-ns").Bytes() + client.On("HasForcedInclusionNamespace").Return(true).Once() + client.On("GetForcedInclusionNamespace").Return(fiNs).Maybe() + client.On("Retrieve", mock.Anything, uint64(109), fiNs).Return(datypes.ResultRetrieve{ + BaseResult: datypes.BaseResult{Code: datypes.StatusHeightFromFuture}, + }).Once() gen := genesis.Genesis{ DAStartHeight: 100, @@ -155,19 +126,13 @@ func TestForcedInclusionRetriever_RetrieveForcedIncludedTxs_EpochStartNotAvailab } func TestForcedInclusionRetriever_RetrieveForcedIncludedTxs_NoBlobsAtHeight(t *testing.T) { - mockDAInstance := &mockDA{ - getIDsFunc: func(ctx context.Context, height uint64, namespace []byte) (*coreda.GetIDsResult, error) { - return nil, coreda.ErrBlobNotFound - }, - } - - client := NewClient(Config{ - DA: mockDAInstance, - Logger: zerolog.Nop(), - Namespace: "test-ns", - DataNamespace: "test-data-ns", - ForcedInclusionNamespace: "test-fi-ns", - }) + client := mocks.NewMockClient(t) + fiNs := datypes.NamespaceFromString("test-fi-ns").Bytes() + client.On("HasForcedInclusionNamespace").Return(true).Once() + client.On("GetForcedInclusionNamespace").Return(fiNs).Maybe() + client.On("Retrieve", mock.Anything, uint64(100), fiNs).Return(datypes.ResultRetrieve{ + BaseResult: datypes.BaseResult{Code: datypes.StatusNotFound}, + }).Once() gen := genesis.Genesis{ DAStartHeight: 100, @@ -184,51 +149,28 @@ func TestForcedInclusionRetriever_RetrieveForcedIncludedTxs_NoBlobsAtHeight(t *t } func TestForcedInclusionRetriever_RetrieveForcedIncludedTxs_MultiHeightEpoch(t *testing.T) { - callCount := 0 testBlobsByHeight := map[uint64][][]byte{ 100: {[]byte("tx1"), []byte("tx2")}, 101: {[]byte("tx3")}, 102: {[]byte("tx4"), []byte("tx5"), []byte("tx6")}, } - mockDAInstance := &mockDA{ - getIDsFunc: func(ctx context.Context, height uint64, namespace []byte) (*coreda.GetIDsResult, error) { - callCount++ - blobs, exists := testBlobsByHeight[height] - if !exists { - return nil, coreda.ErrBlobNotFound - } - ids := make([]coreda.ID, len(blobs)) - for i := range blobs { - ids[i] = []byte("id") - } - return &coreda.GetIDsResult{ - IDs: ids, - Timestamp: time.Now(), - }, nil - }, - getFunc: func(ctx context.Context, ids []coreda.ID, namespace []byte) ([]coreda.Blob, error) { - // Return blobs based on current call count - switch callCount { - case 1: - return testBlobsByHeight[100], nil - case 2: - return testBlobsByHeight[101], nil - case 3: - return testBlobsByHeight[102], nil - default: - return nil, errors.New("unexpected call") - } - }, - } - - client := NewClient(Config{ - DA: mockDAInstance, - Logger: zerolog.Nop(), - Namespace: "test-ns", - DataNamespace: "test-data-ns", - ForcedInclusionNamespace: "test-fi-ns", - }) + client := mocks.NewMockClient(t) + fiNs := datypes.NamespaceFromString("test-fi-ns").Bytes() + client.On("HasForcedInclusionNamespace").Return(true).Once() + client.On("GetForcedInclusionNamespace").Return(fiNs).Maybe() + client.On("Retrieve", mock.Anything, uint64(102), fiNs).Return(datypes.ResultRetrieve{ + BaseResult: datypes.BaseResult{Code: datypes.StatusSuccess, Timestamp: time.Now()}, + Data: testBlobsByHeight[102], + }).Once() + client.On("Retrieve", mock.Anything, uint64(100), fiNs).Return(datypes.ResultRetrieve{ + BaseResult: datypes.BaseResult{Code: datypes.StatusSuccess, Timestamp: time.Now()}, + Data: testBlobsByHeight[100], + }).Once() + client.On("Retrieve", mock.Anything, uint64(101), fiNs).Return(datypes.ResultRetrieve{ + BaseResult: datypes.BaseResult{Code: datypes.StatusSuccess, Timestamp: time.Now()}, + Data: testBlobsByHeight[101], + }).Once() gen := genesis.Genesis{ DAStartHeight: 100, @@ -252,13 +194,7 @@ func TestForcedInclusionRetriever_RetrieveForcedIncludedTxs_MultiHeightEpoch(t * } func TestForcedInclusionRetriever_processForcedInclusionBlobs(t *testing.T) { - client := NewClient(Config{ - DA: &mockDA{}, - Logger: zerolog.Nop(), - Namespace: "test-ns", - DataNamespace: "test-data-ns", - ForcedInclusionNamespace: "test-fi-ns", - }) + client := mocks.NewMockClient(t) gen := genesis.Genesis{ DAStartHeight: 100, @@ -269,16 +205,16 @@ func TestForcedInclusionRetriever_processForcedInclusionBlobs(t *testing.T) { tests := []struct { name string - result coreda.ResultRetrieve + result datypes.ResultRetrieve height uint64 expectedTxCount int expectError bool }{ { name: "success with blobs", - result: coreda.ResultRetrieve{ - BaseResult: coreda.BaseResult{ - Code: coreda.StatusSuccess, + result: datypes.ResultRetrieve{ + BaseResult: datypes.BaseResult{ + Code: datypes.StatusSuccess, }, Data: [][]byte{[]byte("tx1"), []byte("tx2")}, }, @@ -288,9 +224,9 @@ func TestForcedInclusionRetriever_processForcedInclusionBlobs(t *testing.T) { }, { name: "not found", - result: coreda.ResultRetrieve{ - BaseResult: coreda.BaseResult{ - Code: coreda.StatusNotFound, + result: datypes.ResultRetrieve{ + BaseResult: datypes.BaseResult{ + Code: datypes.StatusNotFound, }, }, height: 100, @@ -299,9 +235,9 @@ func TestForcedInclusionRetriever_processForcedInclusionBlobs(t *testing.T) { }, { name: "error status", - result: coreda.ResultRetrieve{ - BaseResult: coreda.BaseResult{ - Code: coreda.StatusError, + result: datypes.ResultRetrieve{ + BaseResult: datypes.BaseResult{ + Code: datypes.StatusError, Message: "test error", }, }, @@ -310,9 +246,9 @@ func TestForcedInclusionRetriever_processForcedInclusionBlobs(t *testing.T) { }, { name: "empty blobs are skipped", - result: coreda.ResultRetrieve{ - BaseResult: coreda.BaseResult{ - Code: coreda.StatusSuccess, + result: datypes.ResultRetrieve{ + BaseResult: datypes.BaseResult{ + Code: datypes.StatusSuccess, }, Data: [][]byte{[]byte("tx1"), {}, []byte("tx2")}, }, diff --git a/block/internal/da/interface.go b/block/internal/da/interface.go new file mode 100644 index 0000000000..69c2d18f7e --- /dev/null +++ b/block/internal/da/interface.go @@ -0,0 +1,42 @@ +package da + +import ( + "context" + + datypes "github.com/evstack/ev-node/pkg/da/types" +) + +// Client represents the DA client contract. +type Client interface { + // Submit submits blobs to the DA layer. + Submit(ctx context.Context, data [][]byte, gasPrice float64, namespace []byte, options []byte) datypes.ResultSubmit + + // Retrieve retrieves blobs from the DA layer at the specified height and namespace. + Retrieve(ctx context.Context, height uint64, namespace []byte) datypes.ResultRetrieve + + // Get retrieves blobs by their IDs. Used for visualization and fetching specific blobs. + Get(ctx context.Context, ids []datypes.ID, namespace []byte) ([]datypes.Blob, error) + + // Namespace accessors. + GetHeaderNamespace() []byte + GetDataNamespace() []byte + GetForcedInclusionNamespace() []byte + HasForcedInclusionNamespace() bool +} + +// Verifier defines the interface for DA proof verification operations. +// This is a subset of the DA interface used by sequencers to verify batch inclusion. +type Verifier interface { + // GetProofs returns inclusion Proofs for Blobs specified by their IDs. + GetProofs(ctx context.Context, ids []datypes.ID, namespace []byte) ([]datypes.Proof, error) + + // Validate validates Commitments against the corresponding Proofs. + Validate(ctx context.Context, ids []datypes.ID, proofs []datypes.Proof, namespace []byte) ([]bool, error) +} + +// FullClient combines Client and Verifier interfaces. +// This is the complete interface implemented by the concrete DA client. +type FullClient interface { + Client + Verifier +} diff --git a/block/internal/submitting/da_submitter.go b/block/internal/submitting/da_submitter.go index 34d6951d81..020f4b02de 100644 --- a/block/internal/submitting/da_submitter.go +++ b/block/internal/submitting/da_submitter.go @@ -13,9 +13,9 @@ import ( "github.com/evstack/ev-node/block/internal/cache" "github.com/evstack/ev-node/block/internal/common" "github.com/evstack/ev-node/block/internal/da" - coreda "github.com/evstack/ev-node/core/da" "github.com/evstack/ev-node/pkg/config" pkgda "github.com/evstack/ev-node/pkg/da" + datypes "github.com/evstack/ev-node/pkg/da/types" "github.com/evstack/ev-node/pkg/genesis" "github.com/evstack/ev-node/pkg/rpc/server" "github.com/evstack/ev-node/pkg/signer" @@ -116,7 +116,7 @@ func NewDASubmitter( if config.RPC.EnableDAVisualization { visualizerLogger := logger.With().Str("component", "da_visualization").Logger() - server.SetDAVisualizationServer(server.NewDAVisualizationServer(client.GetDA(), visualizerLogger, config.Node.Aggregator)) + server.SetDAVisualizationServer(server.NewDAVisualizationServer(client, visualizerLogger, config.Node.Aggregator)) } // Use NoOp metrics if nil to avoid nil checks throughout the code @@ -181,7 +181,7 @@ func (s *DASubmitter) SubmitHeaders(ctx context.Context, cache cache.Manager) er } return proto.Marshal(headerPb) }, - func(submitted []*types.SignedHeader, res *coreda.ResultSubmit) { + func(submitted []*types.SignedHeader, res *datypes.ResultSubmit) { for _, header := range submitted { cache.SetHeaderDAIncluded(header.Hash().String(), res.Height, header.Height()) } @@ -224,7 +224,7 @@ func (s *DASubmitter) SubmitData(ctx context.Context, cache cache.Manager, signe func(signedData *types.SignedData) ([]byte, error) { return signedData.MarshalBinary() }, - func(submitted []*types.SignedData, res *coreda.ResultSubmit) { + func(submitted []*types.SignedData, res *datypes.ResultSubmit) { for _, sd := range submitted { cache.SetDataDAIncluded(sd.Data.DACommitment().String(), res.Height, sd.Height()) } @@ -340,7 +340,7 @@ func submitToDA[T any]( ctx context.Context, items []T, marshalFn func(T) ([]byte, error), - postSubmit func([]T, *coreda.ResultSubmit), + postSubmit func([]T, *datypes.ResultSubmit), itemType string, namespace []byte, options []byte, @@ -409,7 +409,7 @@ func submitToDA[T any]( } switch res.Code { - case coreda.StatusSuccess: + case datypes.StatusSuccess: submitted := items[:res.SubmittedCount] postSubmit(submitted, &res) s.logger.Info().Str("itemType", itemType).Uint64("count", res.SubmittedCount).Msg("successfully submitted items to DA layer") @@ -430,7 +430,7 @@ func submitToDA[T any]( s.metrics.DASubmitterPendingBlobs.Set(float64(getTotalPendingFn())) } - case coreda.StatusTooBig: + case datypes.StatusTooBig: // Record failure metric s.recordFailure(common.DASubmitterFailureReasonTooBig) // Iteratively halve until it fits or single-item too big @@ -454,19 +454,19 @@ func submitToDA[T any]( s.metrics.DASubmitterPendingBlobs.Set(float64(getTotalPendingFn())) } - case coreda.StatusNotIncludedInBlock: + case datypes.StatusNotIncludedInBlock: // Record failure metric s.recordFailure(common.DASubmitterFailureReasonNotIncludedInBlock) s.logger.Info().Dur("backoff", pol.MaxBackoff).Msg("retrying due to mempool state") rs.Next(reasonMempool, pol) - case coreda.StatusAlreadyInMempool: + case datypes.StatusAlreadyInMempool: // Record failure metric s.recordFailure(common.DASubmitterFailureReasonAlreadyInMempool) s.logger.Info().Dur("backoff", pol.MaxBackoff).Msg("retrying due to mempool state") rs.Next(reasonMempool, pol) - case coreda.StatusContextCanceled: + case datypes.StatusContextCanceled: // Record failure metric s.recordFailure(common.DASubmitterFailureReasonContextCanceled) s.logger.Info().Msg("DA layer submission canceled due to context cancellation") diff --git a/block/internal/submitting/da_submitter_integration_test.go b/block/internal/submitting/da_submitter_integration_test.go index 5b768e1a51..8f03ff7c7f 100644 --- a/block/internal/submitting/da_submitter_integration_test.go +++ b/block/internal/submitting/da_submitter_integration_test.go @@ -11,16 +11,17 @@ import ( "github.com/libp2p/go-libp2p/core/crypto" "github.com/rs/zerolog" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/mock" "github.com/stretchr/testify/require" "github.com/evstack/ev-node/block/internal/cache" "github.com/evstack/ev-node/block/internal/common" - "github.com/evstack/ev-node/block/internal/da" - coreda "github.com/evstack/ev-node/core/da" "github.com/evstack/ev-node/pkg/config" + datypes "github.com/evstack/ev-node/pkg/da/types" "github.com/evstack/ev-node/pkg/genesis" "github.com/evstack/ev-node/pkg/signer/noop" "github.com/evstack/ev-node/pkg/store" + "github.com/evstack/ev-node/test/mocks" "github.com/evstack/ev-node/types" ) @@ -83,17 +84,19 @@ func TestDASubmitter_SubmitHeadersAndData_MarksInclusionAndUpdatesLastSubmitted( require.NoError(t, batch2.SetHeight(2)) require.NoError(t, batch2.Commit()) - // Dummy DA - dummyDA := coreda.NewDummyDA(10_000_000, 10*time.Millisecond) - - // Create DA submitter - daClient := da.NewClient(da.Config{ - DA: dummyDA, - Logger: zerolog.Nop(), - Namespace: cfg.DA.Namespace, - DataNamespace: cfg.DA.DataNamespace, - }) - daSubmitter := NewDASubmitter(daClient, cfg, gen, common.DefaultBlockOptions(), common.NopMetrics(), zerolog.Nop()) + // Mock DA client + client := mocks.NewMockClient(t) + headerNs := datypes.NamespaceFromString(cfg.DA.Namespace).Bytes() + dataNs := datypes.NamespaceFromString(cfg.DA.DataNamespace).Bytes() + client.On("GetHeaderNamespace").Return(headerNs).Maybe() + client.On("GetDataNamespace").Return(dataNs).Maybe() + client.On("GetForcedInclusionNamespace").Return([]byte(nil)).Maybe() + client.On("HasForcedInclusionNamespace").Return(false).Maybe() + client.On("Submit", mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything). + Return(func(_ context.Context, blobs [][]byte, _ float64, _ []byte, _ []byte) datypes.ResultSubmit { + return datypes.ResultSubmit{BaseResult: datypes.BaseResult{Code: datypes.StatusSuccess, SubmittedCount: uint64(len(blobs)), Height: 1}} + }).Twice() + daSubmitter := NewDASubmitter(client, cfg, gen, common.DefaultBlockOptions(), common.NopMetrics(), zerolog.Nop()) // Submit headers and data require.NoError(t, daSubmitter.SubmitHeaders(context.Background(), cm)) diff --git a/block/internal/submitting/da_submitter_mocks_test.go b/block/internal/submitting/da_submitter_mocks_test.go index b215b0cf2f..19aea18b0d 100644 --- a/block/internal/submitting/da_submitter_mocks_test.go +++ b/block/internal/submitting/da_submitter_mocks_test.go @@ -2,7 +2,6 @@ package submitting import ( "context" - "errors" "testing" "time" @@ -11,15 +10,14 @@ import ( "github.com/stretchr/testify/mock" "github.com/evstack/ev-node/block/internal/common" - "github.com/evstack/ev-node/block/internal/da" - coreda "github.com/evstack/ev-node/core/da" "github.com/evstack/ev-node/pkg/config" + datypes "github.com/evstack/ev-node/pkg/da/types" "github.com/evstack/ev-node/pkg/genesis" "github.com/evstack/ev-node/test/mocks" ) -// helper to build a basic submitter with provided DA mock and config overrides -func newTestSubmitter(mockDA *mocks.MockDA, override func(*config.Config)) *DASubmitter { +// helper to build a basic submitter with provided DA mock client and config overrides +func newTestSubmitter(t *testing.T, mockClient *mocks.MockClient, override func(*config.Config)) *DASubmitter { cfg := config.Config{} // Keep retries small and backoffs minimal cfg.DA.BlockTime.Duration = 1 * time.Millisecond @@ -30,13 +28,14 @@ func newTestSubmitter(mockDA *mocks.MockDA, override func(*config.Config)) *DASu if override != nil { override(&cfg) } - daClient := da.NewClient(da.Config{ - DA: mockDA, - Logger: zerolog.Nop(), - Namespace: cfg.DA.Namespace, - DataNamespace: cfg.DA.DataNamespace, - }) - return NewDASubmitter(daClient, cfg, genesis.Genesis{} /*options=*/, common.BlockOptions{}, common.NopMetrics(), zerolog.Nop()) + if mockClient == nil { + mockClient = mocks.NewMockClient(t) + } + mockClient.On("GetHeaderNamespace").Return([]byte(cfg.DA.Namespace)).Maybe() + mockClient.On("GetDataNamespace").Return([]byte(cfg.DA.DataNamespace)).Maybe() + mockClient.On("GetForcedInclusionNamespace").Return([]byte(nil)).Maybe() + mockClient.On("HasForcedInclusionNamespace").Return(false).Maybe() + return NewDASubmitter(mockClient, cfg, genesis.Genesis{} /*options=*/, common.BlockOptions{}, common.NopMetrics(), zerolog.Nop()) } // marshal helper for simple items @@ -45,29 +44,28 @@ func marshalString(s string) ([]byte, error) { return []byte(s), nil } func TestSubmitToDA_MempoolRetry_IncreasesGasAndSucceeds(t *testing.T) { t.Parallel() - mockDA := mocks.NewMockDA(t) + client := mocks.NewMockClient(t) - nsBz := coreda.NamespaceFromString("ns").Bytes() + nsBz := datypes.NamespaceFromString("ns").Bytes() opts := []byte("opts") var usedGas []float64 - mockDA. - On("SubmitWithOptions", mock.Anything, mock.Anything, mock.AnythingOfType("float64"), nsBz, opts). + + client.On("Submit", mock.Anything, mock.Anything, mock.AnythingOfType("float64"), nsBz, opts). Run(func(args mock.Arguments) { usedGas = append(usedGas, args.Get(2).(float64)) }). - Return(nil, coreda.ErrTxTimedOut). + Return(datypes.ResultSubmit{BaseResult: datypes.BaseResult{Code: datypes.StatusNotIncludedInBlock, SubmittedCount: 0}}). Once() ids := [][]byte{[]byte("id1"), []byte("id2"), []byte("id3")} - mockDA. - On("SubmitWithOptions", mock.Anything, mock.Anything, mock.AnythingOfType("float64"), nsBz, opts). + client.On("Submit", mock.Anything, mock.Anything, mock.AnythingOfType("float64"), nsBz, opts). Run(func(args mock.Arguments) { usedGas = append(usedGas, args.Get(2).(float64)) }). - Return(ids, nil). + Return(datypes.ResultSubmit{BaseResult: datypes.BaseResult{Code: datypes.StatusSuccess, IDs: ids, SubmittedCount: uint64(len(ids))}}). Once() - s := newTestSubmitter(mockDA, nil) + s := newTestSubmitter(t, client, nil) items := []string{"a", "b", "c"} ctx := context.Background() @@ -76,7 +74,7 @@ func TestSubmitToDA_MempoolRetry_IncreasesGasAndSucceeds(t *testing.T) { ctx, items, marshalString, - func(_ []string, _ *coreda.ResultSubmit) {}, + func(_ []string, _ *datypes.ResultSubmit) {}, "item", nsBz, opts, @@ -86,35 +84,32 @@ func TestSubmitToDA_MempoolRetry_IncreasesGasAndSucceeds(t *testing.T) { // Sentinel value is preserved on retry assert.Equal(t, []float64{-1, -1}, usedGas) - mockDA.AssertExpectations(t) } func TestSubmitToDA_UnknownError_RetriesSameGasThenSucceeds(t *testing.T) { t.Parallel() - mockDA := mocks.NewMockDA(t) + client := mocks.NewMockClient(t) - nsBz := coreda.NamespaceFromString("ns").Bytes() + nsBz := datypes.NamespaceFromString("ns").Bytes() opts := []byte("opts") var usedGas []float64 // First attempt: unknown failure -> reasonFailure, gas unchanged for next attempt - mockDA. - On("SubmitWithOptions", mock.Anything, mock.Anything, mock.AnythingOfType("float64"), nsBz, opts). + client.On("Submit", mock.Anything, mock.Anything, mock.AnythingOfType("float64"), nsBz, opts). Run(func(args mock.Arguments) { usedGas = append(usedGas, args.Get(2).(float64)) }). - Return(nil, errors.New("boom")). + Return(datypes.ResultSubmit{BaseResult: datypes.BaseResult{Code: datypes.StatusError, Message: "boom"}}). Once() // Second attempt: same gas, success ids := [][]byte{[]byte("id1")} - mockDA. - On("SubmitWithOptions", mock.Anything, mock.Anything, mock.AnythingOfType("float64"), nsBz, opts). + client.On("Submit", mock.Anything, mock.Anything, mock.AnythingOfType("float64"), nsBz, opts). Run(func(args mock.Arguments) { usedGas = append(usedGas, args.Get(2).(float64)) }). - Return(ids, nil). + Return(datypes.ResultSubmit{BaseResult: datypes.BaseResult{Code: datypes.StatusSuccess, IDs: ids, SubmittedCount: uint64(len(ids))}}). Once() - s := newTestSubmitter(mockDA, nil) + s := newTestSubmitter(t, client, nil) items := []string{"x"} ctx := context.Background() @@ -123,7 +118,7 @@ func TestSubmitToDA_UnknownError_RetriesSameGasThenSucceeds(t *testing.T) { ctx, items, marshalString, - func(_ []string, _ *coreda.ResultSubmit) {}, + func(_ []string, _ *datypes.ResultSubmit) {}, "item", nsBz, opts, @@ -131,42 +126,36 @@ func TestSubmitToDA_UnknownError_RetriesSameGasThenSucceeds(t *testing.T) { ) assert.NoError(t, err) assert.Equal(t, []float64{-1, -1}, usedGas) - mockDA.AssertExpectations(t) } func TestSubmitToDA_TooBig_HalvesBatch(t *testing.T) { t.Parallel() - mockDA := mocks.NewMockDA(t) + client := mocks.NewMockClient(t) - nsBz := coreda.NamespaceFromString("ns").Bytes() + nsBz := datypes.NamespaceFromString("ns").Bytes() opts := []byte("opts") - // record sizes of batches sent to DA var batchSizes []int - // First attempt: too big -> should halve and retry - mockDA. - On("SubmitWithOptions", mock.Anything, mock.Anything, mock.Anything, nsBz, opts). + client.On("Submit", mock.Anything, mock.Anything, mock.Anything, nsBz, opts). Run(func(args mock.Arguments) { blobs := args.Get(1).([][]byte) batchSizes = append(batchSizes, len(blobs)) }). - Return(nil, coreda.ErrBlobSizeOverLimit). + Return(datypes.ResultSubmit{BaseResult: datypes.BaseResult{Code: datypes.StatusTooBig}}). Once() - // Second attempt: expect half the size, succeed ids := [][]byte{[]byte("id1"), []byte("id2")} - mockDA. - On("SubmitWithOptions", mock.Anything, mock.Anything, mock.Anything, nsBz, opts). + client.On("Submit", mock.Anything, mock.Anything, mock.Anything, nsBz, opts). Run(func(args mock.Arguments) { blobs := args.Get(1).([][]byte) batchSizes = append(batchSizes, len(blobs)) }). - Return(ids, nil). + Return(datypes.ResultSubmit{BaseResult: datypes.BaseResult{Code: datypes.StatusSuccess, IDs: ids, SubmittedCount: uint64(len(ids))}}). Once() - s := newTestSubmitter(mockDA, nil) + s := newTestSubmitter(t, client, nil) items := []string{"a", "b", "c", "d"} ctx := context.Background() @@ -175,7 +164,7 @@ func TestSubmitToDA_TooBig_HalvesBatch(t *testing.T) { ctx, items, marshalString, - func(_ []string, _ *coreda.ResultSubmit) {}, + func(_ []string, _ *datypes.ResultSubmit) {}, "item", nsBz, opts, @@ -183,35 +172,30 @@ func TestSubmitToDA_TooBig_HalvesBatch(t *testing.T) { ) assert.NoError(t, err) assert.Equal(t, []int{4, 2}, batchSizes) - mockDA.AssertExpectations(t) } func TestSubmitToDA_SentinelNoGas_PreservesGasAcrossRetries(t *testing.T) { t.Parallel() - mockDA := mocks.NewMockDA(t) + client := mocks.NewMockClient(t) - nsBz := coreda.NamespaceFromString("ns").Bytes() + nsBz := datypes.NamespaceFromString("ns").Bytes() opts := []byte("opts") var usedGas []float64 - // First attempt: mempool-ish error - mockDA. - On("SubmitWithOptions", mock.Anything, mock.Anything, mock.AnythingOfType("float64"), nsBz, opts). + client.On("Submit", mock.Anything, mock.Anything, mock.AnythingOfType("float64"), nsBz, opts). Run(func(args mock.Arguments) { usedGas = append(usedGas, args.Get(2).(float64)) }). - Return(nil, coreda.ErrTxAlreadyInMempool). + Return(datypes.ResultSubmit{BaseResult: datypes.BaseResult{Code: datypes.StatusAlreadyInMempool}}). Once() - // Second attempt: should use same sentinel gas (-1), succeed ids := [][]byte{[]byte("id1")} - mockDA. - On("SubmitWithOptions", mock.Anything, mock.Anything, mock.AnythingOfType("float64"), nsBz, opts). + client.On("Submit", mock.Anything, mock.Anything, mock.AnythingOfType("float64"), nsBz, opts). Run(func(args mock.Arguments) { usedGas = append(usedGas, args.Get(2).(float64)) }). - Return(ids, nil). + Return(datypes.ResultSubmit{BaseResult: datypes.BaseResult{Code: datypes.StatusSuccess, IDs: ids, SubmittedCount: uint64(len(ids))}}). Once() - s := newTestSubmitter(mockDA, nil) + s := newTestSubmitter(t, client, nil) items := []string{"only"} ctx := context.Background() @@ -220,7 +204,7 @@ func TestSubmitToDA_SentinelNoGas_PreservesGasAcrossRetries(t *testing.T) { ctx, items, marshalString, - func(_ []string, _ *coreda.ResultSubmit) {}, + func(_ []string, _ *datypes.ResultSubmit) {}, "item", nsBz, opts, @@ -228,29 +212,29 @@ func TestSubmitToDA_SentinelNoGas_PreservesGasAcrossRetries(t *testing.T) { ) assert.NoError(t, err) assert.Equal(t, []float64{-1, -1}, usedGas) - mockDA.AssertExpectations(t) } func TestSubmitToDA_PartialSuccess_AdvancesWindow(t *testing.T) { t.Parallel() - mockDA := mocks.NewMockDA(t) + client := mocks.NewMockClient(t) - nsBz := coreda.NamespaceFromString("ns").Bytes() + nsBz := datypes.NamespaceFromString("ns").Bytes() opts := []byte("opts") - // track how many items postSubmit sees across attempts var totalSubmitted int - // First attempt: success for first 2 of 3 firstIDs := [][]byte{[]byte("id1"), []byte("id2")} - mockDA.On("SubmitWithOptions", mock.Anything, mock.Anything, mock.Anything, nsBz, opts).Return(firstIDs, nil).Once() + client.On("Submit", mock.Anything, mock.Anything, mock.Anything, nsBz, opts). + Return(datypes.ResultSubmit{BaseResult: datypes.BaseResult{Code: datypes.StatusSuccess, IDs: firstIDs, SubmittedCount: uint64(len(firstIDs))}}). + Once() - // Second attempt: success for remaining 1 secondIDs := [][]byte{[]byte("id3")} - mockDA.On("SubmitWithOptions", mock.Anything, mock.Anything, mock.Anything, nsBz, opts).Return(secondIDs, nil).Once() + client.On("Submit", mock.Anything, mock.Anything, mock.Anything, nsBz, opts). + Return(datypes.ResultSubmit{BaseResult: datypes.BaseResult{Code: datypes.StatusSuccess, IDs: secondIDs, SubmittedCount: uint64(len(secondIDs))}}). + Once() - s := newTestSubmitter(mockDA, nil) + s := newTestSubmitter(t, client, nil) items := []string{"a", "b", "c"} ctx := context.Background() @@ -259,7 +243,7 @@ func TestSubmitToDA_PartialSuccess_AdvancesWindow(t *testing.T) { ctx, items, marshalString, - func(submitted []string, _ *coreda.ResultSubmit) { totalSubmitted += len(submitted) }, + func(submitted []string, _ *datypes.ResultSubmit) { totalSubmitted += len(submitted) }, "item", nsBz, opts, @@ -267,5 +251,4 @@ func TestSubmitToDA_PartialSuccess_AdvancesWindow(t *testing.T) { ) assert.NoError(t, err) assert.Equal(t, 3, totalSubmitted) - mockDA.AssertExpectations(t) } diff --git a/block/internal/submitting/da_submitter_test.go b/block/internal/submitting/da_submitter_test.go index 214ab98db4..a34589c222 100644 --- a/block/internal/submitting/da_submitter_test.go +++ b/block/internal/submitting/da_submitter_test.go @@ -11,22 +11,28 @@ import ( "github.com/libp2p/go-libp2p/core/crypto" "github.com/rs/zerolog" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/mock" "github.com/stretchr/testify/require" "github.com/evstack/ev-node/block/internal/cache" "github.com/evstack/ev-node/block/internal/common" - "github.com/evstack/ev-node/block/internal/da" - coreda "github.com/evstack/ev-node/core/da" "github.com/evstack/ev-node/pkg/config" + datypes "github.com/evstack/ev-node/pkg/da/types" "github.com/evstack/ev-node/pkg/genesis" "github.com/evstack/ev-node/pkg/rpc/server" "github.com/evstack/ev-node/pkg/signer" "github.com/evstack/ev-node/pkg/signer/noop" "github.com/evstack/ev-node/pkg/store" + "github.com/evstack/ev-node/test/mocks" "github.com/evstack/ev-node/types" ) -func setupDASubmitterTest(t *testing.T) (*DASubmitter, store.Store, cache.Manager, coreda.DA, genesis.Genesis) { +const ( + testHeaderNamespace = "test-headers" + testDataNamespace = "test-data" +) + +func setupDASubmitterTest(t *testing.T) (*DASubmitter, store.Store, cache.Manager, *mocks.MockClient, genesis.Genesis) { t.Helper() // Create store and cache @@ -35,13 +41,19 @@ func setupDASubmitterTest(t *testing.T) (*DASubmitter, store.Store, cache.Manage cm, err := cache.NewManager(config.DefaultConfig(), st, zerolog.Nop()) require.NoError(t, err) - // Create dummy DA - dummyDA := coreda.NewDummyDA(10_000_000, 10*time.Millisecond) - // Create config cfg := config.DefaultConfig() - cfg.DA.Namespace = "test-headers" - cfg.DA.DataNamespace = "test-data" + cfg.DA.Namespace = testHeaderNamespace + cfg.DA.DataNamespace = testDataNamespace + + // Mock DA client + mockDA := mocks.NewMockClient(t) + headerNamespace := datypes.NamespaceFromString(cfg.DA.Namespace).Bytes() + dataNamespace := datypes.NamespaceFromString(cfg.DA.DataNamespace).Bytes() + mockDA.On("GetHeaderNamespace").Return(headerNamespace).Maybe() + mockDA.On("GetDataNamespace").Return(dataNamespace).Maybe() + mockDA.On("GetForcedInclusionNamespace").Return([]byte(nil)).Maybe() + mockDA.On("HasForcedInclusionNamespace").Return(false).Maybe() // Create genesis gen := genesis.Genesis{ @@ -52,14 +64,8 @@ func setupDASubmitterTest(t *testing.T) (*DASubmitter, store.Store, cache.Manage } // Create DA submitter - daClient := da.NewClient(da.Config{ - DA: dummyDA, - Logger: zerolog.Nop(), - Namespace: cfg.DA.Namespace, - DataNamespace: cfg.DA.DataNamespace, - }) daSubmitter := NewDASubmitter( - daClient, + mockDA, cfg, gen, common.DefaultBlockOptions(), @@ -67,7 +73,7 @@ func setupDASubmitterTest(t *testing.T) (*DASubmitter, store.Store, cache.Manage zerolog.Nop(), ) - return daSubmitter, st, cm, dummyDA, gen + return daSubmitter, st, cm, mockDA, gen } func createTestSigner(t *testing.T) ([]byte, crypto.PubKey, signer.Signer) { @@ -100,14 +106,11 @@ func TestNewDASubmitterSetsVisualizerWhenEnabled(t *testing.T) { cfg.RPC.EnableDAVisualization = true cfg.Node.Aggregator = true - dummyDA := coreda.NewDummyDA(10_000_000, 10*time.Millisecond) - - daClient := da.NewClient(da.Config{ - DA: dummyDA, - Logger: zerolog.Nop(), - Namespace: cfg.DA.Namespace, - DataNamespace: cfg.DA.DataNamespace, - }) + daClient := mocks.NewMockClient(t) + daClient.On("GetHeaderNamespace").Return(datypes.NamespaceFromString(cfg.DA.Namespace).Bytes()).Maybe() + daClient.On("GetDataNamespace").Return(datypes.NamespaceFromString(cfg.DA.DataNamespace).Bytes()).Maybe() + daClient.On("GetForcedInclusionNamespace").Return([]byte(nil)).Maybe() + daClient.On("HasForcedInclusionNamespace").Return(false).Maybe() NewDASubmitter( daClient, cfg, @@ -121,8 +124,20 @@ func TestNewDASubmitterSetsVisualizerWhenEnabled(t *testing.T) { } func TestDASubmitter_SubmitHeaders_Success(t *testing.T) { - submitter, st, cm, _, gen := setupDASubmitterTest(t) + submitter, st, cm, mockDA, gen := setupDASubmitterTest(t) ctx := context.Background() + headerNamespace := datypes.NamespaceFromString(testHeaderNamespace).Bytes() + + mockDA.On( + "Submit", + mock.Anything, + mock.AnythingOfType("[][]uint8"), + mock.AnythingOfType("float64"), + headerNamespace, + mock.Anything, + ).Return(func(_ context.Context, blobs [][]byte, _ float64, _ []byte, _ []byte) datypes.ResultSubmit { + return datypes.ResultSubmit{BaseResult: datypes.BaseResult{Code: datypes.StatusSuccess, SubmittedCount: uint64(len(blobs)), Height: 1}} + }).Once() // Create test signer addr, pub, signer := createTestSigner(t) @@ -211,17 +226,30 @@ func TestDASubmitter_SubmitHeaders_Success(t *testing.T) { } func TestDASubmitter_SubmitHeaders_NoPendingHeaders(t *testing.T) { - submitter, _, cm, _, _ := setupDASubmitterTest(t) + submitter, _, cm, mockDA, _ := setupDASubmitterTest(t) ctx := context.Background() // Submit headers when none are pending err := submitter.SubmitHeaders(ctx, cm) require.NoError(t, err) // Should succeed with no action + mockDA.AssertNotCalled(t, "Submit", mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything) } func TestDASubmitter_SubmitData_Success(t *testing.T) { - submitter, st, cm, _, gen := setupDASubmitterTest(t) + submitter, st, cm, mockDA, gen := setupDASubmitterTest(t) ctx := context.Background() + dataNamespace := datypes.NamespaceFromString(testDataNamespace).Bytes() + + mockDA.On( + "Submit", + mock.Anything, + mock.AnythingOfType("[][]uint8"), + mock.AnythingOfType("float64"), + dataNamespace, + mock.Anything, + ).Return(func(_ context.Context, blobs [][]byte, _ float64, _ []byte, _ []byte) datypes.ResultSubmit { + return datypes.ResultSubmit{BaseResult: datypes.BaseResult{Code: datypes.StatusSuccess, SubmittedCount: uint64(len(blobs)), Height: 2}} + }).Once() // Create test signer addr, pub, signer := createTestSigner(t) @@ -306,7 +334,7 @@ func TestDASubmitter_SubmitData_Success(t *testing.T) { } func TestDASubmitter_SubmitData_SkipsEmptyData(t *testing.T) { - submitter, st, cm, _, gen := setupDASubmitterTest(t) + submitter, st, cm, mockDA, gen := setupDASubmitterTest(t) ctx := context.Background() // Create test signer @@ -348,6 +376,7 @@ func TestDASubmitter_SubmitData_SkipsEmptyData(t *testing.T) { // Submit data - should succeed but skip empty data err = submitter.SubmitData(ctx, cm, signer, gen) require.NoError(t, err) + mockDA.AssertNotCalled(t, "Submit", mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything) // Empty data should not be marked as DA included (it's implicitly included) _, ok := cm.GetDataDAIncluded(emptyData.DACommitment().String()) @@ -355,7 +384,7 @@ func TestDASubmitter_SubmitData_SkipsEmptyData(t *testing.T) { } func TestDASubmitter_SubmitData_NoPendingData(t *testing.T) { - submitter, _, cm, _, gen := setupDASubmitterTest(t) + submitter, _, cm, mockDA, gen := setupDASubmitterTest(t) ctx := context.Background() // Create test signer @@ -364,10 +393,11 @@ func TestDASubmitter_SubmitData_NoPendingData(t *testing.T) { // Submit data when none are pending err := submitter.SubmitData(ctx, cm, signer, gen) require.NoError(t, err) // Should succeed with no action + mockDA.AssertNotCalled(t, "Submit", mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything) } func TestDASubmitter_SubmitData_NilSigner(t *testing.T) { - submitter, st, cm, _, gen := setupDASubmitterTest(t) + submitter, st, cm, mockDA, gen := setupDASubmitterTest(t) ctx := context.Background() // Create test data with transactions @@ -403,6 +433,7 @@ func TestDASubmitter_SubmitData_NilSigner(t *testing.T) { err = submitter.SubmitData(ctx, cm, nil, gen) require.Error(t, err) assert.Contains(t, err.Error(), "signer is nil") + mockDA.AssertNotCalled(t, "Submit", mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything) } func TestDASubmitter_CreateSignedData(t *testing.T) { diff --git a/block/internal/submitting/submitter_test.go b/block/internal/submitting/submitter_test.go index c1df11bf51..b3a778a4eb 100644 --- a/block/internal/submitting/submitter_test.go +++ b/block/internal/submitting/submitter_test.go @@ -18,7 +18,6 @@ import ( "github.com/evstack/ev-node/block/internal/cache" "github.com/evstack/ev-node/block/internal/common" - "github.com/evstack/ev-node/block/internal/da" "github.com/evstack/ev-node/pkg/config" "github.com/evstack/ev-node/pkg/genesis" "github.com/evstack/ev-node/pkg/rpc/server" @@ -162,12 +161,12 @@ func TestSubmitter_setSequencerHeightToDAHeight(t *testing.T) { cfg.DA.Namespace = "test-ns" cfg.DA.DataNamespace = "test-data-ns" metrics := common.NopMetrics() - daClient := da.NewClient(da.Config{ - DA: nil, - Logger: zerolog.Nop(), - Namespace: cfg.DA.Namespace, - DataNamespace: cfg.DA.DataNamespace, - }) + daClient := testmocks.NewMockClient(t) + // Namespace getters may be called implicitly; allow optional returns + daClient.On("GetHeaderNamespace").Return([]byte(cfg.DA.Namespace)).Maybe() + daClient.On("GetDataNamespace").Return([]byte(cfg.DA.DataNamespace)).Maybe() + daClient.On("GetForcedInclusionNamespace").Return([]byte(nil)).Maybe() + daClient.On("HasForcedInclusionNamespace").Return(false).Maybe() daSub := NewDASubmitter(daClient, cfg, genesis.Genesis{}, common.BlockOptions{}, metrics, zerolog.Nop()) s := NewSubmitter(mockStore, nil, cm, metrics, cfg, genesis.Genesis{}, daSub, nil, zerolog.Nop(), nil) s.ctx = ctx @@ -247,12 +246,11 @@ func TestSubmitter_processDAInclusionLoop_advances(t *testing.T) { exec.On("SetFinal", mock.Anything, uint64(1)).Return(nil).Once() exec.On("SetFinal", mock.Anything, uint64(2)).Return(nil).Once() - daClient := da.NewClient(da.Config{ - DA: nil, - Logger: zerolog.Nop(), - Namespace: cfg.DA.Namespace, - DataNamespace: cfg.DA.DataNamespace, - }) + daClient := testmocks.NewMockClient(t) + daClient.On("GetHeaderNamespace").Return([]byte(cfg.DA.Namespace)).Maybe() + daClient.On("GetDataNamespace").Return([]byte(cfg.DA.DataNamespace)).Maybe() + daClient.On("GetForcedInclusionNamespace").Return([]byte(nil)).Maybe() + daClient.On("HasForcedInclusionNamespace").Return(false).Maybe() daSub := NewDASubmitter(daClient, cfg, genesis.Genesis{}, common.BlockOptions{}, metrics, zerolog.Nop()) s := NewSubmitter(st, exec, cm, metrics, cfg, genesis.Genesis{}, daSub, nil, zerolog.Nop(), nil) @@ -438,12 +436,11 @@ func TestSubmitter_CacheClearedOnHeightInclusion(t *testing.T) { exec.On("SetFinal", mock.Anything, uint64(1)).Return(nil).Once() exec.On("SetFinal", mock.Anything, uint64(2)).Return(nil).Once() - daClient := da.NewClient(da.Config{ - DA: nil, - Logger: zerolog.Nop(), - Namespace: cfg.DA.Namespace, - DataNamespace: cfg.DA.DataNamespace, - }) + daClient := testmocks.NewMockClient(t) + daClient.On("GetHeaderNamespace").Return([]byte(cfg.DA.Namespace)).Maybe() + daClient.On("GetDataNamespace").Return([]byte(cfg.DA.DataNamespace)).Maybe() + daClient.On("GetForcedInclusionNamespace").Return([]byte(nil)).Maybe() + daClient.On("HasForcedInclusionNamespace").Return(false).Maybe() daSub := NewDASubmitter(daClient, cfg, genesis.Genesis{}, common.BlockOptions{}, metrics, zerolog.Nop()) s := NewSubmitter(st, exec, cm, metrics, cfg, genesis.Genesis{}, daSub, nil, zerolog.Nop(), nil) diff --git a/block/internal/syncing/da_retriever.go b/block/internal/syncing/da_retriever.go index 9325d4d3bd..e8996f64d5 100644 --- a/block/internal/syncing/da_retriever.go +++ b/block/internal/syncing/da_retriever.go @@ -12,7 +12,7 @@ import ( "github.com/evstack/ev-node/block/internal/cache" "github.com/evstack/ev-node/block/internal/common" "github.com/evstack/ev-node/block/internal/da" - coreda "github.com/evstack/ev-node/core/da" + datypes "github.com/evstack/ev-node/pkg/da/types" "github.com/evstack/ev-node/pkg/genesis" "github.com/evstack/ev-node/types" pb "github.com/evstack/ev-node/types/pb/evnode/v1" @@ -71,45 +71,45 @@ func (r *daRetriever) RetrieveFromDA(ctx context.Context, daHeight uint64) ([]co } // fetchBlobs retrieves blobs from both header and data namespaces -func (r *daRetriever) fetchBlobs(ctx context.Context, daHeight uint64) (coreda.ResultRetrieve, error) { +func (r *daRetriever) fetchBlobs(ctx context.Context, daHeight uint64) (datypes.ResultRetrieve, error) { // Retrieve from both namespaces using the DA client - headerRes := r.client.RetrieveHeaders(ctx, daHeight) + headerRes := r.client.Retrieve(ctx, daHeight, r.client.GetHeaderNamespace()) // If namespaces are the same, return header result if bytes.Equal(r.client.GetHeaderNamespace(), r.client.GetDataNamespace()) { return headerRes, r.validateBlobResponse(headerRes, daHeight) } - dataRes := r.client.RetrieveData(ctx, daHeight) + dataRes := r.client.Retrieve(ctx, daHeight, r.client.GetDataNamespace()) // Validate responses headerErr := r.validateBlobResponse(headerRes, daHeight) // ignoring error not found, as data can have data - if headerErr != nil && !errors.Is(headerErr, coreda.ErrBlobNotFound) { + if headerErr != nil && !errors.Is(headerErr, datypes.ErrBlobNotFound) { return headerRes, headerErr } dataErr := r.validateBlobResponse(dataRes, daHeight) // ignoring error not found, as header can have data - if dataErr != nil && !errors.Is(dataErr, coreda.ErrBlobNotFound) { + if dataErr != nil && !errors.Is(dataErr, datypes.ErrBlobNotFound) { return dataRes, dataErr } // Combine successful results - combinedResult := coreda.ResultRetrieve{ - BaseResult: coreda.BaseResult{ - Code: coreda.StatusSuccess, + combinedResult := datypes.ResultRetrieve{ + BaseResult: datypes.BaseResult{ + Code: datypes.StatusSuccess, Height: daHeight, }, Data: make([][]byte, 0), } - if headerRes.Code == coreda.StatusSuccess { + if headerRes.Code == datypes.StatusSuccess { combinedResult.Data = append(combinedResult.Data, headerRes.Data...) combinedResult.IDs = append(combinedResult.IDs, headerRes.IDs...) } - if dataRes.Code == coreda.StatusSuccess { + if dataRes.Code == datypes.StatusSuccess { combinedResult.Data = append(combinedResult.Data, dataRes.Data...) combinedResult.IDs = append(combinedResult.IDs, dataRes.IDs...) } @@ -117,9 +117,9 @@ func (r *daRetriever) fetchBlobs(ctx context.Context, daHeight uint64) (coreda.R // Re-throw error not found if both were not found. if len(combinedResult.Data) == 0 && len(combinedResult.IDs) == 0 { r.logger.Debug().Uint64("da_height", daHeight).Msg("no blob data found") - combinedResult.Code = coreda.StatusNotFound - combinedResult.Message = coreda.ErrBlobNotFound.Error() - return combinedResult, coreda.ErrBlobNotFound + combinedResult.Code = datypes.StatusNotFound + combinedResult.Message = datypes.ErrBlobNotFound.Error() + return combinedResult, datypes.ErrBlobNotFound } return combinedResult, nil @@ -127,15 +127,15 @@ func (r *daRetriever) fetchBlobs(ctx context.Context, daHeight uint64) (coreda.R // validateBlobResponse validates a blob response from DA layer // those are the only error code returned by da.RetrieveWithHelpers -func (r *daRetriever) validateBlobResponse(res coreda.ResultRetrieve, daHeight uint64) error { +func (r *daRetriever) validateBlobResponse(res datypes.ResultRetrieve, daHeight uint64) error { switch res.Code { - case coreda.StatusError: + case datypes.StatusError: return fmt.Errorf("DA retrieval failed: %s", res.Message) - case coreda.StatusHeightFromFuture: - return fmt.Errorf("%w: height from future", coreda.ErrHeightFromFuture) - case coreda.StatusNotFound: - return fmt.Errorf("%w: blob not found", coreda.ErrBlobNotFound) - case coreda.StatusSuccess: + case datypes.StatusHeightFromFuture: + return fmt.Errorf("%w: height from future", datypes.ErrHeightFromFuture) + case datypes.StatusNotFound: + return fmt.Errorf("%w: blob not found", datypes.ErrBlobNotFound) + case datypes.StatusSuccess: r.logger.Debug().Uint64("da_height", daHeight).Msg("successfully retrieved from DA") return nil default: diff --git a/block/internal/syncing/da_retriever_test.go b/block/internal/syncing/da_retriever_test.go index 4f43492737..110f62acc7 100644 --- a/block/internal/syncing/da_retriever_test.go +++ b/block/internal/syncing/da_retriever_test.go @@ -1,7 +1,6 @@ package syncing import ( - "bytes" "context" "errors" "fmt" @@ -16,17 +15,16 @@ import ( "github.com/evstack/ev-node/block/internal/cache" "github.com/evstack/ev-node/block/internal/common" - "github.com/evstack/ev-node/block/internal/da" - coreda "github.com/evstack/ev-node/core/da" "github.com/evstack/ev-node/pkg/config" + datypes "github.com/evstack/ev-node/pkg/da/types" "github.com/evstack/ev-node/pkg/genesis" signerpkg "github.com/evstack/ev-node/pkg/signer" - testmocks "github.com/evstack/ev-node/test/mocks" + "github.com/evstack/ev-node/test/mocks" "github.com/evstack/ev-node/types" ) // newTestDARetriever creates a DA retriever for testing with the given DA implementation -func newTestDARetriever(t *testing.T, mockDA coreda.DA, cfg config.Config, gen genesis.Genesis) *daRetriever { +func newTestDARetriever(t *testing.T, mockClient *mocks.MockClient, cfg config.Config, gen genesis.Genesis) *daRetriever { t.Helper() if cfg.DA.Namespace == "" { cfg.DA.Namespace = "test-ns" @@ -38,14 +36,16 @@ func newTestDARetriever(t *testing.T, mockDA coreda.DA, cfg config.Config, gen g cm, err := cache.NewCacheManager(cfg, zerolog.Nop()) require.NoError(t, err) - daClient := da.NewClient(da.Config{ - DA: mockDA, - Logger: zerolog.Nop(), - Namespace: cfg.DA.Namespace, - DataNamespace: cfg.DA.DataNamespace, - }) + if mockClient == nil { + mockClient = mocks.NewMockClient(t) + } + // default namespace helpers + mockClient.On("GetHeaderNamespace").Return([]byte(cfg.DA.Namespace)).Maybe() + mockClient.On("GetDataNamespace").Return([]byte(cfg.DA.DataNamespace)).Maybe() + mockClient.On("GetForcedInclusionNamespace").Return([]byte(nil)).Maybe() + mockClient.On("HasForcedInclusionNamespace").Return(false).Maybe() - return NewDARetriever(daClient, cm, gen, zerolog.Nop()) + return NewDARetriever(mockClient, cm, gen, zerolog.Nop()) } // makeSignedDataBytes builds SignedData containing the provided Data and returns its binary encoding @@ -73,51 +73,61 @@ func makeSignedDataBytesWithTime(t *testing.T, chainID string, height uint64, pr } func TestDARetriever_RetrieveFromDA_Invalid(t *testing.T) { - mockDA := testmocks.NewMockDA(t) - - mockDA.EXPECT().GetIDs(mock.Anything, mock.Anything, mock.Anything). - Return(nil, errors.New("just invalid")).Maybe() - - r := newTestDARetriever(t, mockDA, config.DefaultConfig(), genesis.Genesis{}) + client := mocks.NewMockClient(t) + client.On("GetHeaderNamespace").Return([]byte("test-ns")).Maybe() + client.On("GetDataNamespace").Return([]byte("test-data-ns")).Maybe() + client.On("GetForcedInclusionNamespace").Return([]byte(nil)).Maybe() + client.On("HasForcedInclusionNamespace").Return(false).Maybe() + client.On("Retrieve", mock.Anything, uint64(42), []byte("test-ns")).Return(datypes.ResultRetrieve{BaseResult: datypes.BaseResult{Code: datypes.StatusSuccess}}).Once() + client.On("Retrieve", mock.Anything, uint64(42), []byte("test-data-ns")).Return(datypes.ResultRetrieve{BaseResult: datypes.BaseResult{Code: datypes.StatusError, Message: "just invalid"}}).Once() + + r := newTestDARetriever(t, client, config.DefaultConfig(), genesis.Genesis{}) events, err := r.RetrieveFromDA(context.Background(), 42) assert.Error(t, err) assert.Len(t, events, 0) } func TestDARetriever_RetrieveFromDA_NotFound(t *testing.T) { - mockDA := testmocks.NewMockDA(t) - - // GetIDs returns ErrBlobNotFound -> helper maps to StatusNotFound - mockDA.EXPECT().GetIDs(mock.Anything, mock.Anything, mock.Anything). - Return(nil, fmt.Errorf("%s: whatever", coreda.ErrBlobNotFound.Error())).Maybe() - - r := newTestDARetriever(t, mockDA, config.DefaultConfig(), genesis.Genesis{}) + client := mocks.NewMockClient(t) + client.On("GetHeaderNamespace").Return([]byte("test-ns")).Maybe() + client.On("GetDataNamespace").Return([]byte("test-data-ns")).Maybe() + client.On("GetForcedInclusionNamespace").Return([]byte(nil)).Maybe() + client.On("HasForcedInclusionNamespace").Return(false).Maybe() + client.On("Retrieve", mock.Anything, uint64(42), []byte("test-ns")).Return(datypes.ResultRetrieve{BaseResult: datypes.BaseResult{Code: datypes.StatusSuccess}}).Once() + client.On("Retrieve", mock.Anything, uint64(42), []byte("test-data-ns")).Return(datypes.ResultRetrieve{BaseResult: datypes.BaseResult{Code: datypes.StatusNotFound, Message: fmt.Sprintf("%s: whatever", datypes.ErrBlobNotFound.Error())}}).Once() + + r := newTestDARetriever(t, client, config.DefaultConfig(), genesis.Genesis{}) events, err := r.RetrieveFromDA(context.Background(), 42) - assert.True(t, errors.Is(err, coreda.ErrBlobNotFound)) + assert.True(t, errors.Is(err, datypes.ErrBlobNotFound)) assert.Len(t, events, 0) } func TestDARetriever_RetrieveFromDA_HeightFromFuture(t *testing.T) { - mockDA := testmocks.NewMockDA(t) - // GetIDs returns ErrHeightFromFuture -> helper maps to StatusHeightFromFuture, fetchBlobs returns error - mockDA.EXPECT().GetIDs(mock.Anything, mock.Anything, mock.Anything). - Return(nil, fmt.Errorf("%s: later", coreda.ErrHeightFromFuture.Error())).Maybe() - - r := newTestDARetriever(t, mockDA, config.DefaultConfig(), genesis.Genesis{}) + client := mocks.NewMockClient(t) + client.On("GetHeaderNamespace").Return([]byte("test-ns")).Maybe() + client.On("GetDataNamespace").Return([]byte("test-data-ns")).Maybe() + client.On("GetForcedInclusionNamespace").Return([]byte(nil)).Maybe() + client.On("HasForcedInclusionNamespace").Return(false).Maybe() + client.On("Retrieve", mock.Anything, uint64(1000), []byte("test-ns")).Return(datypes.ResultRetrieve{BaseResult: datypes.BaseResult{Code: datypes.StatusSuccess}}).Once() + client.On("Retrieve", mock.Anything, uint64(1000), []byte("test-data-ns")).Return(datypes.ResultRetrieve{BaseResult: datypes.BaseResult{Code: datypes.StatusHeightFromFuture, Message: fmt.Sprintf("%s: later", datypes.ErrHeightFromFuture.Error())}}).Once() + + r := newTestDARetriever(t, client, config.DefaultConfig(), genesis.Genesis{}) events, derr := r.RetrieveFromDA(context.Background(), 1000) assert.Error(t, derr) - assert.True(t, errors.Is(derr, coreda.ErrHeightFromFuture)) + assert.True(t, errors.Is(derr, datypes.ErrHeightFromFuture)) assert.Nil(t, events) } func TestDARetriever_RetrieveFromDA_TimeoutFast(t *testing.T) { - mockDA := testmocks.NewMockDA(t) - - // Mock GetIDs to immediately return context deadline exceeded - mockDA.EXPECT().GetIDs(mock.Anything, mock.Anything, mock.Anything). - Return(nil, context.DeadlineExceeded).Maybe() + client := mocks.NewMockClient(t) + client.On("GetHeaderNamespace").Return([]byte("test-ns")).Maybe() + client.On("GetDataNamespace").Return([]byte("test-data-ns")).Maybe() + client.On("GetForcedInclusionNamespace").Return([]byte(nil)).Maybe() + client.On("HasForcedInclusionNamespace").Return(false).Maybe() + client.On("Retrieve", mock.Anything, uint64(42), []byte("test-ns")).Return(datypes.ResultRetrieve{BaseResult: datypes.BaseResult{Code: datypes.StatusError, Message: context.DeadlineExceeded.Error()}}).Once() + client.On("Retrieve", mock.Anything, uint64(42), []byte("test-data-ns")).Return(datypes.ResultRetrieve{BaseResult: datypes.BaseResult{Code: datypes.StatusContextDeadline, Message: context.DeadlineExceeded.Error()}}).Once() - r := newTestDARetriever(t, mockDA, config.DefaultConfig(), genesis.Genesis{}) + r := newTestDARetriever(t, client, config.DefaultConfig(), genesis.Genesis{}) events, err := r.RetrieveFromDA(context.Background(), 42) @@ -213,15 +223,15 @@ func TestDARetriever_tryDecodeData_InvalidSignatureOrProposer(t *testing.T) { func TestDARetriever_validateBlobResponse(t *testing.T) { r := &daRetriever{logger: zerolog.Nop()} // StatusSuccess -> nil - err := r.validateBlobResponse(coreda.ResultRetrieve{BaseResult: coreda.BaseResult{Code: coreda.StatusSuccess}}, 1) + err := r.validateBlobResponse(datypes.ResultRetrieve{BaseResult: datypes.BaseResult{Code: datypes.StatusSuccess}}, 1) assert.NoError(t, err) // StatusError -> error - err = r.validateBlobResponse(coreda.ResultRetrieve{BaseResult: coreda.BaseResult{Code: coreda.StatusError, Message: "fail"}}, 1) + err = r.validateBlobResponse(datypes.ResultRetrieve{BaseResult: datypes.BaseResult{Code: datypes.StatusError, Message: "fail"}}, 1) assert.Error(t, err) // StatusHeightFromFuture -> specific error - err = r.validateBlobResponse(coreda.ResultRetrieve{BaseResult: coreda.BaseResult{Code: coreda.StatusHeightFromFuture}}, 1) + err = r.validateBlobResponse(datypes.ResultRetrieve{BaseResult: datypes.BaseResult{Code: datypes.StatusHeightFromFuture}}, 1) assert.Error(t, err) - assert.True(t, errors.Is(err, coreda.ErrHeightFromFuture)) + assert.True(t, errors.Is(err, datypes.ErrHeightFromFuture)) } func TestDARetriever_RetrieveFromDA_TwoNamespaces_Success(t *testing.T) { @@ -237,22 +247,21 @@ func TestDARetriever_RetrieveFromDA_TwoNamespaces_Success(t *testing.T) { cfg.DA.Namespace = "nsHdr" cfg.DA.DataNamespace = "nsData" - namespaceBz := coreda.NamespaceFromString(cfg.DA.GetNamespace()).Bytes() - namespaceDataBz := coreda.NamespaceFromString(cfg.DA.GetDataNamespace()).Bytes() - - mockDA := testmocks.NewMockDA(t) - // Expect GetIDs for both namespaces - mockDA.EXPECT().GetIDs(mock.Anything, uint64(1234), mock.MatchedBy(func(ns []byte) bool { return bytes.Equal(ns, namespaceBz) })). - Return(&coreda.GetIDsResult{IDs: [][]byte{[]byte("h1")}, Timestamp: time.Now()}, nil).Once() - mockDA.EXPECT().Get(mock.Anything, mock.Anything, mock.MatchedBy(func(ns []byte) bool { return bytes.Equal(ns, namespaceBz) })). - Return([][]byte{hdrBin}, nil).Once() - - mockDA.EXPECT().GetIDs(mock.Anything, uint64(1234), mock.MatchedBy(func(ns []byte) bool { return bytes.Equal(ns, namespaceDataBz) })). - Return(&coreda.GetIDsResult{IDs: [][]byte{[]byte("d1")}, Timestamp: time.Now()}, nil).Once() - mockDA.EXPECT().Get(mock.Anything, mock.Anything, mock.MatchedBy(func(ns []byte) bool { return bytes.Equal(ns, namespaceDataBz) })). - Return([][]byte{dataBin}, nil).Once() - - r := newTestDARetriever(t, mockDA, cfg, gen) + client := mocks.NewMockClient(t) + client.On("GetHeaderNamespace").Return([]byte("nsHdr")).Maybe() + client.On("GetDataNamespace").Return([]byte("nsData")).Maybe() + client.On("GetForcedInclusionNamespace").Return([]byte(nil)).Maybe() + client.On("HasForcedInclusionNamespace").Return(false).Maybe() + client.On("Retrieve", mock.Anything, uint64(1234), []byte("nsHdr")).Return(datypes.ResultRetrieve{ + BaseResult: datypes.BaseResult{Code: datypes.StatusSuccess, IDs: [][]byte{[]byte("h1")}, Timestamp: time.Now()}, + Data: [][]byte{hdrBin}, + }).Once() + client.On("Retrieve", mock.Anything, uint64(1234), []byte("nsData")).Return(datypes.ResultRetrieve{ + BaseResult: datypes.BaseResult{Code: datypes.StatusSuccess, IDs: [][]byte{[]byte("d1")}, Timestamp: time.Now()}, + Data: [][]byte{dataBin}, + }).Once() + + r := newTestDARetriever(t, client, cfg, gen) events, derr := r.RetrieveFromDA(context.Background(), 1234) require.NoError(t, derr) diff --git a/block/internal/syncing/syncer.go b/block/internal/syncing/syncer.go index f65d7ba66a..812b40b206 100644 --- a/block/internal/syncing/syncer.go +++ b/block/internal/syncing/syncer.go @@ -13,18 +13,16 @@ import ( "sync/atomic" "time" + coreexecutor "github.com/evstack/ev-node/core/execution" pubsub "github.com/libp2p/go-libp2p-pubsub" "github.com/rs/zerolog" "golang.org/x/sync/errgroup" - coreda "github.com/evstack/ev-node/core/da" - coreexecutor "github.com/evstack/ev-node/core/execution" - seqcommon "github.com/evstack/ev-node/sequencers/common" - "github.com/evstack/ev-node/block/internal/cache" "github.com/evstack/ev-node/block/internal/common" "github.com/evstack/ev-node/block/internal/da" "github.com/evstack/ev-node/pkg/config" + datypes "github.com/evstack/ev-node/pkg/da/types" "github.com/evstack/ev-node/pkg/genesis" "github.com/evstack/ev-node/pkg/store" "github.com/evstack/ev-node/types" @@ -355,10 +353,10 @@ func (s *Syncer) fetchDAUntilCaughtUp() error { events, err := s.daRetriever.RetrieveFromDA(s.ctx, daHeight) if err != nil { switch { - case errors.Is(err, coreda.ErrBlobNotFound): + case errors.Is(err, datypes.ErrBlobNotFound): s.daRetrieverHeight.Store(daHeight + 1) continue // Fetch next height immediately - case errors.Is(err, coreda.ErrHeightFromFuture): + case errors.Is(err, datypes.ErrHeightFromFuture): s.logger.Debug().Err(err).Uint64("da_height", daHeight).Msg("DA is ahead of local target; backing off future height requests") return nil // Caught up default: @@ -737,7 +735,7 @@ func hashTx(tx []byte) string { // It estimates fullness based on total data size. // This is a heuristic - actual limits may vary by execution layer. func (s *Syncer) calculateBlockFullness(data *types.Data) float64 { - const maxDataSize = seqcommon.AbsoluteMaxBlobSize + const maxDataSize = common.DefaultMaxBlobSize var fullness float64 count := 0 diff --git a/block/internal/syncing/syncer_backoff_test.go b/block/internal/syncing/syncer_backoff_test.go index 65f2586966..6df2e577a0 100644 --- a/block/internal/syncing/syncer_backoff_test.go +++ b/block/internal/syncing/syncer_backoff_test.go @@ -15,9 +15,9 @@ import ( "github.com/evstack/ev-node/block/internal/cache" "github.com/evstack/ev-node/block/internal/common" - coreda "github.com/evstack/ev-node/core/da" "github.com/evstack/ev-node/core/execution" "github.com/evstack/ev-node/pkg/config" + datypes "github.com/evstack/ev-node/pkg/da/types" "github.com/evstack/ev-node/pkg/genesis" "github.com/evstack/ev-node/pkg/store" extmocks "github.com/evstack/ev-node/test/mocks/external" @@ -41,13 +41,13 @@ func TestSyncer_BackoffOnDAError(t *testing.T) { }, "height_from_future_triggers_backoff": { daBlockTime: 500 * time.Millisecond, - error: coreda.ErrHeightFromFuture, + error: datypes.ErrHeightFromFuture, expectsBackoff: true, description: "Height from future should trigger backoff", }, "blob_not_found_no_backoff": { daBlockTime: 1 * time.Second, - error: coreda.ErrBlobNotFound, + error: datypes.ErrBlobNotFound, expectsBackoff: false, description: "ErrBlobNotFound should not trigger backoff", }, @@ -111,7 +111,7 @@ func TestSyncer_BackoffOnDAError(t *testing.T) { // Cancel to end test cancel() }). - Return(nil, coreda.ErrBlobNotFound).Once() + Return(nil, datypes.ErrBlobNotFound).Once() } else { // For ErrBlobNotFound, DA height should increment daRetriever.On("RetrieveFromDA", mock.Anything, uint64(101)). @@ -120,7 +120,7 @@ func TestSyncer_BackoffOnDAError(t *testing.T) { callCount++ cancel() }). - Return(nil, coreda.ErrBlobNotFound).Once() + Return(nil, datypes.ErrBlobNotFound).Once() } // Run sync loop @@ -223,7 +223,7 @@ func TestSyncer_BackoffResetOnSuccess(t *testing.T) { callTimes = append(callTimes, time.Now()) cancel() }). - Return(nil, coreda.ErrBlobNotFound).Once() + Return(nil, datypes.ErrBlobNotFound).Once() // Start process loop to handle events go syncer.processLoop() @@ -292,7 +292,7 @@ func TestSyncer_BackoffBehaviorIntegration(t *testing.T) { Run(func(args mock.Arguments) { callTimes = append(callTimes, time.Now()) }). - Return(nil, coreda.ErrBlobNotFound).Once() + Return(nil, datypes.ErrBlobNotFound).Once() // Third call - should continue without delay (DA height incremented) daRetriever.On("RetrieveFromDA", mock.Anything, uint64(101)). @@ -300,7 +300,7 @@ func TestSyncer_BackoffBehaviorIntegration(t *testing.T) { callTimes = append(callTimes, time.Now()) cancel() }). - Return(nil, coreda.ErrBlobNotFound).Once() + Return(nil, datypes.ErrBlobNotFound).Once() go syncer.processLoop() syncer.startSyncWorkers() diff --git a/block/internal/syncing/syncer_forced_inclusion_test.go b/block/internal/syncing/syncer_forced_inclusion_test.go index 0467ecc692..111eb9e5ad 100644 --- a/block/internal/syncing/syncer_forced_inclusion_test.go +++ b/block/internal/syncing/syncer_forced_inclusion_test.go @@ -1,7 +1,6 @@ package syncing import ( - "bytes" "context" "sync/atomic" "testing" @@ -15,9 +14,9 @@ import ( "github.com/evstack/ev-node/block/internal/cache" "github.com/evstack/ev-node/block/internal/common" - "github.com/evstack/ev-node/block/internal/da" - coreda "github.com/evstack/ev-node/core/da" + da "github.com/evstack/ev-node/block/internal/da" "github.com/evstack/ev-node/pkg/config" + datypes "github.com/evstack/ev-node/pkg/da/types" "github.com/evstack/ev-node/pkg/genesis" "github.com/evstack/ev-node/pkg/store" testmocks "github.com/evstack/ev-node/test/mocks" @@ -365,22 +364,18 @@ func TestVerifyForcedInclusionTxs_AllTransactionsIncluded(t *testing.T) { mockExec.EXPECT().InitChain(mock.Anything, mock.Anything, uint64(1), "tchain"). Return([]byte("app0"), uint64(1024), nil).Once() - mockDA := testmocks.NewMockDA(t) - - daClient := da.NewClient(da.Config{ - DA: mockDA, - Logger: zerolog.Nop(), - Namespace: cfg.DA.Namespace, - DataNamespace: cfg.DA.DataNamespace, - ForcedInclusionNamespace: cfg.DA.ForcedInclusionNamespace, - }) - daRetriever := NewDARetriever(daClient, cm, gen, zerolog.Nop()) - fiRetriever := da.NewForcedInclusionRetriever(daClient, gen, zerolog.Nop()) + client := testmocks.NewMockClient(t) + client.On("GetHeaderNamespace").Return([]byte(cfg.DA.Namespace)).Maybe() + client.On("GetDataNamespace").Return([]byte(cfg.DA.DataNamespace)).Maybe() + client.On("GetForcedInclusionNamespace").Return([]byte(cfg.DA.ForcedInclusionNamespace)).Maybe() + client.On("HasForcedInclusionNamespace").Return(true).Maybe() + daRetriever := NewDARetriever(client, cm, gen, zerolog.Nop()) + fiRetriever := da.NewForcedInclusionRetriever(client, gen, zerolog.Nop()) s := NewSyncer( st, mockExec, - daClient, + client, cm, common.NopMetrics(), cfg, @@ -398,21 +393,13 @@ func TestVerifyForcedInclusionTxs_AllTransactionsIncluded(t *testing.T) { s.ctx = context.Background() // Mock DA to return forced inclusion transactions - namespaceForcedInclusionBz := coreda.NamespaceFromString(cfg.DA.GetForcedInclusionNamespace()).Bytes() - // Create forced inclusion transaction blob (SignedData) in DA dataBin, _ := makeSignedDataBytes(t, gen.ChainID, 10, addr, pub, signer, 2) - // With DAStartHeight=0, epoch size=1, daHeight=0 -> epoch boundaries are [0, 0] - // Check epoch start only (end check is skipped when same as start) - mockDA.EXPECT().GetIDs(mock.Anything, uint64(0), mock.MatchedBy(func(ns []byte) bool { - return bytes.Equal(ns, namespaceForcedInclusionBz) - })).Return(&coreda.GetIDsResult{IDs: [][]byte{[]byte("fi1")}, Timestamp: time.Now()}, nil).Once() - - // Fetch epoch start data - mockDA.EXPECT().Get(mock.Anything, mock.Anything, mock.MatchedBy(func(ns []byte) bool { - return bytes.Equal(ns, namespaceForcedInclusionBz) - })).Return([][]byte{dataBin}, nil).Once() + client.On("Retrieve", mock.Anything, uint64(0), []byte("nsForcedInclusion")).Return(datypes.ResultRetrieve{ + BaseResult: datypes.BaseResult{Code: datypes.StatusSuccess, IDs: [][]byte{[]byte("fi1")}, Timestamp: time.Now()}, + Data: [][]byte{dataBin}, + }).Once() // Create block data that includes the forced transaction blob data := makeData(gen.ChainID, 1, 1) @@ -450,22 +437,18 @@ func TestVerifyForcedInclusionTxs_MissingTransactions(t *testing.T) { mockExec.EXPECT().InitChain(mock.Anything, mock.Anything, uint64(1), "tchain"). Return([]byte("app0"), uint64(1024), nil).Once() - mockDA := testmocks.NewMockDA(t) - - daClient := da.NewClient(da.Config{ - DA: mockDA, - Logger: zerolog.Nop(), - Namespace: cfg.DA.Namespace, - DataNamespace: cfg.DA.DataNamespace, - ForcedInclusionNamespace: cfg.DA.ForcedInclusionNamespace, - }) - daRetriever := NewDARetriever(daClient, cm, gen, zerolog.Nop()) - fiRetriever := da.NewForcedInclusionRetriever(daClient, gen, zerolog.Nop()) + client := testmocks.NewMockClient(t) + client.On("GetHeaderNamespace").Return([]byte(cfg.DA.Namespace)).Maybe() + client.On("GetDataNamespace").Return([]byte(cfg.DA.DataNamespace)).Maybe() + client.On("GetForcedInclusionNamespace").Return([]byte(cfg.DA.ForcedInclusionNamespace)).Maybe() + client.On("HasForcedInclusionNamespace").Return(true).Maybe() + daRetriever := NewDARetriever(client, cm, gen, zerolog.Nop()) + fiRetriever := da.NewForcedInclusionRetriever(client, gen, zerolog.Nop()) s := NewSyncer( st, mockExec, - daClient, + client, cm, common.NopMetrics(), cfg, @@ -483,21 +466,14 @@ func TestVerifyForcedInclusionTxs_MissingTransactions(t *testing.T) { s.ctx = context.Background() // Mock DA to return forced inclusion transactions - namespaceForcedInclusionBz := coreda.NamespaceFromString(cfg.DA.GetForcedInclusionNamespace()).Bytes() - // Create forced inclusion transaction blob (SignedData) in DA dataBin, _ := makeSignedDataBytes(t, gen.ChainID, 10, addr, pub, signer, 2) // With DAStartHeight=0, epoch size=1, daHeight=0 -> epoch boundaries are [0, 0] - // Check epoch start only (end check is skipped when same as start) - mockDA.EXPECT().GetIDs(mock.Anything, uint64(0), mock.MatchedBy(func(ns []byte) bool { - return bytes.Equal(ns, namespaceForcedInclusionBz) - })).Return(&coreda.GetIDsResult{IDs: [][]byte{[]byte("fi1")}, Timestamp: time.Now()}, nil).Once() - - // Fetch epoch start data - mockDA.EXPECT().Get(mock.Anything, mock.Anything, mock.MatchedBy(func(ns []byte) bool { - return bytes.Equal(ns, namespaceForcedInclusionBz) - })).Return([][]byte{dataBin}, nil).Once() + client.On("Retrieve", mock.Anything, uint64(0), []byte("nsForcedInclusion")).Return(datypes.ResultRetrieve{ + BaseResult: datypes.BaseResult{Code: datypes.StatusSuccess, IDs: [][]byte{[]byte("fi1")}, Timestamp: time.Now()}, + Data: [][]byte{dataBin}, + }).Once() // Create block data that does NOT include the forced transaction blob data := makeData(gen.ChainID, 1, 2) @@ -512,22 +488,22 @@ func TestVerifyForcedInclusionTxs_MissingTransactions(t *testing.T) { require.NoError(t, err) // Mock DA for next epoch to return no forced inclusion transactions - mockDA.EXPECT().GetIDs(mock.Anything, uint64(1), mock.MatchedBy(func(ns []byte) bool { - return bytes.Equal(ns, namespaceForcedInclusionBz) - })).Return(&coreda.GetIDsResult{IDs: [][]byte{}, Timestamp: time.Now()}, nil).Once() + client.On("Retrieve", mock.Anything, uint64(1), []byte("nsForcedInclusion")).Return(datypes.ResultRetrieve{ + BaseResult: datypes.BaseResult{Code: datypes.StatusNotFound, Timestamp: time.Now()}, + }).Once() // Move to next epoch but still within grace period currentState.DAHeight = 1 // Move to epoch end (epoch was [0, 0]) data2 := makeData(gen.ChainID, 2, 1) - data2.Txs[0] = types.Tx([]byte("regular_tx_3")) + data2.Txs[0] = []byte("regular_tx_3") err = s.verifyForcedInclusionTxs(currentState, data2) require.NoError(t, err) // Should pass since DAHeight=1 equals grace boundary, not past it // Mock DA for height 2 to return no forced inclusion transactions - mockDA.EXPECT().GetIDs(mock.Anything, uint64(2), mock.MatchedBy(func(ns []byte) bool { - return bytes.Equal(ns, namespaceForcedInclusionBz) - })).Return(&coreda.GetIDsResult{IDs: [][]byte{}, Timestamp: time.Now()}, nil).Once() + client.On("Retrieve", mock.Anything, uint64(2), []byte("nsForcedInclusion")).Return(datypes.ResultRetrieve{ + BaseResult: datypes.BaseResult{Code: datypes.StatusNotFound, Timestamp: time.Now()}, + }).Once() // Now move past grace boundary - should fail if tx still not included currentState.DAHeight = 2 // Move past grace boundary (graceBoundary = 0 + 1*1 = 1) @@ -564,22 +540,18 @@ func TestVerifyForcedInclusionTxs_PartiallyIncluded(t *testing.T) { mockExec.EXPECT().InitChain(mock.Anything, mock.Anything, uint64(1), "tchain"). Return([]byte("app0"), uint64(1024), nil).Once() - mockDA := testmocks.NewMockDA(t) - - daClient := da.NewClient(da.Config{ - DA: mockDA, - Logger: zerolog.Nop(), - Namespace: cfg.DA.Namespace, - DataNamespace: cfg.DA.DataNamespace, - ForcedInclusionNamespace: cfg.DA.ForcedInclusionNamespace, - }) - daRetriever := NewDARetriever(daClient, cm, gen, zerolog.Nop()) - fiRetriever := da.NewForcedInclusionRetriever(daClient, gen, zerolog.Nop()) + client := testmocks.NewMockClient(t) + client.On("GetHeaderNamespace").Return([]byte(cfg.DA.Namespace)).Maybe() + client.On("GetDataNamespace").Return([]byte(cfg.DA.DataNamespace)).Maybe() + client.On("GetForcedInclusionNamespace").Return([]byte(cfg.DA.ForcedInclusionNamespace)).Maybe() + client.On("HasForcedInclusionNamespace").Return(true).Maybe() + daRetriever := NewDARetriever(client, cm, gen, zerolog.Nop()) + fiRetriever := da.NewForcedInclusionRetriever(client, gen, zerolog.Nop()) s := NewSyncer( st, mockExec, - daClient, + client, cm, common.NopMetrics(), cfg, @@ -596,23 +568,15 @@ func TestVerifyForcedInclusionTxs_PartiallyIncluded(t *testing.T) { require.NoError(t, s.initializeState()) s.ctx = context.Background() - // Mock DA to return two forced inclusion transaction blobs - namespaceForcedInclusionBz := coreda.NamespaceFromString(cfg.DA.GetForcedInclusionNamespace()).Bytes() - // Create two forced inclusion transaction blobs in DA dataBin1, _ := makeSignedDataBytes(t, gen.ChainID, 10, addr, pub, signer, 2) dataBin2, _ := makeSignedDataBytes(t, gen.ChainID, 11, addr, pub, signer, 1) // With DAStartHeight=0, epoch size=1, daHeight=0 -> epoch boundaries are [0, 0] - // Check epoch start only (end check is skipped when same as start) - mockDA.EXPECT().GetIDs(mock.Anything, uint64(0), mock.MatchedBy(func(ns []byte) bool { - return bytes.Equal(ns, namespaceForcedInclusionBz) - })).Return(&coreda.GetIDsResult{IDs: [][]byte{[]byte("fi1"), []byte("fi2")}, Timestamp: time.Now()}, nil).Once() - - // Fetch epoch start data - mockDA.EXPECT().Get(mock.Anything, mock.Anything, mock.MatchedBy(func(ns []byte) bool { - return bytes.Equal(ns, namespaceForcedInclusionBz) - })).Return([][]byte{dataBin1, dataBin2}, nil).Once() + client.On("Retrieve", mock.Anything, uint64(0), []byte("nsForcedInclusion")).Return(datypes.ResultRetrieve{ + BaseResult: datypes.BaseResult{Code: datypes.StatusSuccess, IDs: [][]byte{[]byte("fi1"), []byte("fi2")}, Timestamp: time.Now()}, + Data: [][]byte{dataBin1, dataBin2}, + }).Once() // Create block data that includes only one of the forced transaction blobs data := makeData(gen.ChainID, 1, 2) @@ -628,9 +592,9 @@ func TestVerifyForcedInclusionTxs_PartiallyIncluded(t *testing.T) { require.NoError(t, err) // Mock DA for next epoch to return no forced inclusion transactions - mockDA.EXPECT().GetIDs(mock.Anything, uint64(1), mock.MatchedBy(func(ns []byte) bool { - return bytes.Equal(ns, namespaceForcedInclusionBz) - })).Return(&coreda.GetIDsResult{IDs: [][]byte{}, Timestamp: time.Now()}, nil).Once() + client.On("Retrieve", mock.Anything, uint64(1), []byte("nsForcedInclusion")).Return(datypes.ResultRetrieve{ + BaseResult: datypes.BaseResult{Code: datypes.StatusNotFound, Timestamp: time.Now()}, + }).Once() // Move to DAHeight=1 (still within grace period since graceBoundary = 0 + 1*1 = 1) currentState.DAHeight = 1 @@ -642,9 +606,9 @@ func TestVerifyForcedInclusionTxs_PartiallyIncluded(t *testing.T) { require.NoError(t, err) // Mock DA for height 2 (when we move to DAHeight 2) - mockDA.EXPECT().GetIDs(mock.Anything, uint64(2), mock.MatchedBy(func(ns []byte) bool { - return bytes.Equal(ns, namespaceForcedInclusionBz) - })).Return(&coreda.GetIDsResult{IDs: [][]byte{}, Timestamp: time.Now()}, nil).Once() + client.On("Retrieve", mock.Anything, uint64(2), []byte("nsForcedInclusion")).Return(datypes.ResultRetrieve{ + BaseResult: datypes.BaseResult{Code: datypes.StatusNotFound, Timestamp: time.Now()}, + }).Once() // Now simulate moving past grace boundary - should fail if dataBin2 still not included // With basePeriod=1 and DAEpochForcedInclusion=1, graceBoundary = 0 + (1*1) = 1 @@ -683,22 +647,18 @@ func TestVerifyForcedInclusionTxs_NoForcedTransactions(t *testing.T) { mockExec.EXPECT().InitChain(mock.Anything, mock.Anything, uint64(1), "tchain"). Return([]byte("app0"), uint64(1024), nil).Once() - mockDA := testmocks.NewMockDA(t) - - daClient := da.NewClient(da.Config{ - DA: mockDA, - Logger: zerolog.Nop(), - Namespace: cfg.DA.Namespace, - DataNamespace: cfg.DA.DataNamespace, - ForcedInclusionNamespace: cfg.DA.ForcedInclusionNamespace, - }) - daRetriever := NewDARetriever(daClient, cm, gen, zerolog.Nop()) - fiRetriever := da.NewForcedInclusionRetriever(daClient, gen, zerolog.Nop()) + client := testmocks.NewMockClient(t) + client.On("GetHeaderNamespace").Return([]byte(cfg.DA.Namespace)).Maybe() + client.On("GetDataNamespace").Return([]byte(cfg.DA.DataNamespace)).Maybe() + client.On("GetForcedInclusionNamespace").Return([]byte(cfg.DA.ForcedInclusionNamespace)).Maybe() + client.On("HasForcedInclusionNamespace").Return(true).Maybe() + daRetriever := NewDARetriever(client, cm, gen, zerolog.Nop()) + fiRetriever := da.NewForcedInclusionRetriever(client, gen, zerolog.Nop()) s := NewSyncer( st, mockExec, - daClient, + client, cm, common.NopMetrics(), cfg, @@ -715,14 +675,10 @@ func TestVerifyForcedInclusionTxs_NoForcedTransactions(t *testing.T) { require.NoError(t, s.initializeState()) s.ctx = context.Background() - // Mock DA to return no forced inclusion transactions - namespaceForcedInclusionBz := coreda.NamespaceFromString(cfg.DA.GetForcedInclusionNamespace()).Bytes() - - // With DAStartHeight=0, epoch size=1, daHeight=0 -> epoch boundaries are [0, 0] - // Check epoch start only (end check is skipped when same as start) - mockDA.EXPECT().GetIDs(mock.Anything, uint64(0), mock.MatchedBy(func(ns []byte) bool { - return bytes.Equal(ns, namespaceForcedInclusionBz) - })).Return(&coreda.GetIDsResult{IDs: [][]byte{}, Timestamp: time.Now()}, nil).Once() + // Mock DA to return no forced inclusion transactions at height 0 + client.On("Retrieve", mock.Anything, uint64(0), []byte("nsForcedInclusion")).Return(datypes.ResultRetrieve{ + BaseResult: datypes.BaseResult{Code: datypes.StatusNotFound, Timestamp: time.Now()}, + }).Once() // Create block data data := makeData(gen.ChainID, 1, 2) @@ -757,22 +713,18 @@ func TestVerifyForcedInclusionTxs_NamespaceNotConfigured(t *testing.T) { mockExec.EXPECT().InitChain(mock.Anything, mock.Anything, uint64(1), "tchain"). Return([]byte("app0"), uint64(1024), nil).Once() - mockDA := testmocks.NewMockDA(t) - - daClient := da.NewClient(da.Config{ - DA: mockDA, - Logger: zerolog.Nop(), - Namespace: cfg.DA.Namespace, - DataNamespace: cfg.DA.DataNamespace, - // No ForcedInclusionNamespace - not configured - }) - daRetriever := NewDARetriever(daClient, cm, gen, zerolog.Nop()) - fiRetriever := da.NewForcedInclusionRetriever(daClient, gen, zerolog.Nop()) + client := testmocks.NewMockClient(t) + client.On("GetHeaderNamespace").Return([]byte(cfg.DA.Namespace)).Maybe() + client.On("GetDataNamespace").Return([]byte(cfg.DA.DataNamespace)).Maybe() + client.On("GetForcedInclusionNamespace").Return([]byte(nil)).Maybe() + client.On("HasForcedInclusionNamespace").Return(false).Maybe() + daRetriever := NewDARetriever(client, cm, gen, zerolog.Nop()) + fiRetriever := da.NewForcedInclusionRetriever(client, gen, zerolog.Nop()) s := NewSyncer( st, mockExec, - daClient, + client, cm, common.NopMetrics(), cfg, @@ -826,22 +778,18 @@ func TestVerifyForcedInclusionTxs_DeferralWithinEpoch(t *testing.T) { mockExec.EXPECT().InitChain(mock.Anything, mock.Anything, uint64(1), "tchain"). Return([]byte("app0"), uint64(1024), nil).Once() - mockDA := testmocks.NewMockDA(t) - - daClient := da.NewClient(da.Config{ - DA: mockDA, - Logger: zerolog.Nop(), - Namespace: cfg.DA.Namespace, - DataNamespace: cfg.DA.DataNamespace, - ForcedInclusionNamespace: cfg.DA.ForcedInclusionNamespace, - }) - daRetriever := NewDARetriever(daClient, cm, gen, zerolog.Nop()) - fiRetriever := da.NewForcedInclusionRetriever(daClient, gen, zerolog.Nop()) + client := testmocks.NewMockClient(t) + client.On("GetHeaderNamespace").Return([]byte(cfg.DA.Namespace)).Maybe() + client.On("GetDataNamespace").Return([]byte(cfg.DA.DataNamespace)).Maybe() + client.On("GetForcedInclusionNamespace").Return([]byte(cfg.DA.ForcedInclusionNamespace)).Maybe() + client.On("HasForcedInclusionNamespace").Return(true).Maybe() + daRetriever := NewDARetriever(client, cm, gen, zerolog.Nop()) + fiRetriever := da.NewForcedInclusionRetriever(client, gen, zerolog.Nop()) s := NewSyncer( st, mockExec, - daClient, + client, cm, common.NopMetrics(), cfg, @@ -858,8 +806,6 @@ func TestVerifyForcedInclusionTxs_DeferralWithinEpoch(t *testing.T) { require.NoError(t, s.initializeState()) s.ctx = context.Background() - namespaceForcedInclusionBz := coreda.NamespaceFromString(cfg.DA.GetForcedInclusionNamespace()).Bytes() - // Create forced inclusion transaction blobs dataBin1, _ := makeSignedDataBytes(t, gen.ChainID, 10, addr, pub, signer, 2) dataBin2, _ := makeSignedDataBytes(t, gen.ChainID, 11, addr, pub, signer, 1) @@ -868,27 +814,17 @@ func TestVerifyForcedInclusionTxs_DeferralWithinEpoch(t *testing.T) { // Epoch boundaries: [100, 104] (epoch size is 5) // The retriever will fetch all heights in the epoch: 100, 101, 102, 103, 104 - // Height 100 (epoch start) - mockDA.EXPECT().GetIDs(mock.Anything, uint64(100), mock.MatchedBy(func(ns []byte) bool { - return bytes.Equal(ns, namespaceForcedInclusionBz) - })).Return(&coreda.GetIDsResult{IDs: [][]byte{[]byte("fi1"), []byte("fi2")}, Timestamp: time.Now()}, nil).Once() + client.On("Retrieve", mock.Anything, uint64(100), []byte("nsForcedInclusion")).Return(datypes.ResultRetrieve{ + BaseResult: datypes.BaseResult{Code: datypes.StatusSuccess, IDs: [][]byte{[]byte("fi1"), []byte("fi2")}, Timestamp: time.Now()}, + Data: [][]byte{dataBin1, dataBin2}, + }).Once() - mockDA.EXPECT().Get(mock.Anything, mock.Anything, mock.MatchedBy(func(ns []byte) bool { - return bytes.Equal(ns, namespaceForcedInclusionBz) - })).Return([][]byte{dataBin1, dataBin2}, nil).Once() - - // Heights 101, 102, 103 (intermediate heights in epoch) - for height := uint64(101); height <= 103; height++ { - mockDA.EXPECT().GetIDs(mock.Anything, height, mock.MatchedBy(func(ns []byte) bool { - return bytes.Equal(ns, namespaceForcedInclusionBz) - })).Return(&coreda.GetIDsResult{IDs: [][]byte{}, Timestamp: time.Now()}, nil).Once() + for height := uint64(101); height <= 104; height++ { + client.On("Retrieve", mock.Anything, height, []byte("nsForcedInclusion")).Return(datypes.ResultRetrieve{ + BaseResult: datypes.BaseResult{Code: datypes.StatusNotFound, Timestamp: time.Now()}, + }).Once() } - // Height 104 (epoch end) - mockDA.EXPECT().GetIDs(mock.Anything, uint64(104), mock.MatchedBy(func(ns []byte) bool { - return bytes.Equal(ns, namespaceForcedInclusionBz) - })).Return(&coreda.GetIDsResult{IDs: [][]byte{}, Timestamp: time.Now()}, nil).Once() - // First block only includes dataBin1 (dataBin2 deferred due to size constraints) data1 := makeData(gen.ChainID, 1, 2) data1.Txs[0] = types.Tx(dataBin1) @@ -909,23 +845,16 @@ func TestVerifyForcedInclusionTxs_DeferralWithinEpoch(t *testing.T) { }) // Mock DA for second verification at same epoch (height 104 - epoch end) - mockDA.EXPECT().GetIDs(mock.Anything, uint64(100), mock.MatchedBy(func(ns []byte) bool { - return bytes.Equal(ns, namespaceForcedInclusionBz) - })).Return(&coreda.GetIDsResult{IDs: [][]byte{[]byte("fi1"), []byte("fi2")}, Timestamp: time.Now()}, nil).Once() - - mockDA.EXPECT().Get(mock.Anything, mock.Anything, mock.MatchedBy(func(ns []byte) bool { - return bytes.Equal(ns, namespaceForcedInclusionBz) - })).Return([][]byte{dataBin1, dataBin2}, nil).Once() - - for height := uint64(101); height <= 103; height++ { - mockDA.EXPECT().GetIDs(mock.Anything, height, mock.MatchedBy(func(ns []byte) bool { - return bytes.Equal(ns, namespaceForcedInclusionBz) - })).Return(&coreda.GetIDsResult{IDs: [][]byte{}, Timestamp: time.Now()}, nil).Once() + for height := uint64(101); height <= 104; height++ { + client.On("Retrieve", mock.Anything, height, []byte("nsForcedInclusion")).Return(datypes.ResultRetrieve{ + BaseResult: datypes.BaseResult{Code: datypes.StatusNotFound, Timestamp: time.Now()}, + }).Once() } - mockDA.EXPECT().GetIDs(mock.Anything, uint64(104), mock.MatchedBy(func(ns []byte) bool { - return bytes.Equal(ns, namespaceForcedInclusionBz) - })).Return(&coreda.GetIDsResult{IDs: [][]byte{}, Timestamp: time.Now()}, nil).Once() + client.On("Retrieve", mock.Anything, uint64(100), []byte("nsForcedInclusion")).Return(datypes.ResultRetrieve{ + BaseResult: datypes.BaseResult{Code: datypes.StatusSuccess, IDs: [][]byte{[]byte("fi1"), []byte("fi2")}, Timestamp: time.Now()}, + Data: [][]byte{dataBin1, dataBin2}, + }).Once() // Second block includes BOTH the previously included dataBin1 AND the deferred dataBin2 // This simulates the block containing both forced inclusion txs @@ -972,22 +901,18 @@ func TestVerifyForcedInclusionTxs_MaliciousAfterEpochEnd(t *testing.T) { mockExec.EXPECT().InitChain(mock.Anything, mock.Anything, uint64(1), "tchain"). Return([]byte("app0"), uint64(1024), nil).Once() - mockDA := testmocks.NewMockDA(t) - - daClient := da.NewClient(da.Config{ - DA: mockDA, - Logger: zerolog.Nop(), - Namespace: cfg.DA.Namespace, - DataNamespace: cfg.DA.DataNamespace, - ForcedInclusionNamespace: cfg.DA.ForcedInclusionNamespace, - }) - daRetriever := NewDARetriever(daClient, cm, gen, zerolog.Nop()) - fiRetriever := da.NewForcedInclusionRetriever(daClient, gen, zerolog.Nop()) + client := testmocks.NewMockClient(t) + client.On("GetHeaderNamespace").Return([]byte(cfg.DA.Namespace)).Maybe() + client.On("GetDataNamespace").Return([]byte(cfg.DA.DataNamespace)).Maybe() + client.On("GetForcedInclusionNamespace").Return([]byte(cfg.DA.ForcedInclusionNamespace)).Maybe() + client.On("HasForcedInclusionNamespace").Return(true).Maybe() + daRetriever := NewDARetriever(client, cm, gen, zerolog.Nop()) + fiRetriever := da.NewForcedInclusionRetriever(client, gen, zerolog.Nop()) s := NewSyncer( st, mockExec, - daClient, + client, cm, common.NopMetrics(), cfg, @@ -1004,8 +929,6 @@ func TestVerifyForcedInclusionTxs_MaliciousAfterEpochEnd(t *testing.T) { require.NoError(t, s.initializeState()) s.ctx = context.Background() - namespaceForcedInclusionBz := coreda.NamespaceFromString(cfg.DA.GetForcedInclusionNamespace()).Bytes() - // Create forced inclusion transaction blob dataBin, _ := makeSignedDataBytes(t, gen.ChainID, 10, addr, pub, signer, 2) @@ -1013,24 +936,18 @@ func TestVerifyForcedInclusionTxs_MaliciousAfterEpochEnd(t *testing.T) { // Epoch boundaries: [100, 102] (epoch size is 3) // The retriever will fetch heights 100, 101, 102 - // Height 100 (epoch start) - mockDA.EXPECT().GetIDs(mock.Anything, uint64(100), mock.MatchedBy(func(ns []byte) bool { - return bytes.Equal(ns, namespaceForcedInclusionBz) - })).Return(&coreda.GetIDsResult{IDs: [][]byte{[]byte("fi1")}, Timestamp: time.Now()}, nil).Once() - - mockDA.EXPECT().Get(mock.Anything, mock.Anything, mock.MatchedBy(func(ns []byte) bool { - return bytes.Equal(ns, namespaceForcedInclusionBz) - })).Return([][]byte{dataBin}, nil).Once() + client.On("Retrieve", mock.Anything, uint64(100), []byte("nsForcedInclusion")).Return(datypes.ResultRetrieve{ + BaseResult: datypes.BaseResult{Code: datypes.StatusSuccess, IDs: [][]byte{[]byte("fi1")}, Timestamp: time.Now()}, + Data: [][]byte{dataBin}, + }).Once() - // Height 101 (intermediate) - mockDA.EXPECT().GetIDs(mock.Anything, uint64(101), mock.MatchedBy(func(ns []byte) bool { - return bytes.Equal(ns, namespaceForcedInclusionBz) - })).Return(&coreda.GetIDsResult{IDs: [][]byte{}, Timestamp: time.Now()}, nil).Once() + client.On("Retrieve", mock.Anything, uint64(101), []byte("nsForcedInclusion")).Return(datypes.ResultRetrieve{ + BaseResult: datypes.BaseResult{Code: datypes.StatusNotFound, Timestamp: time.Now()}, + }).Once() - // Height 102 (epoch end) - mockDA.EXPECT().GetIDs(mock.Anything, uint64(102), mock.MatchedBy(func(ns []byte) bool { - return bytes.Equal(ns, namespaceForcedInclusionBz) - })).Return(&coreda.GetIDsResult{IDs: [][]byte{}, Timestamp: time.Now()}, nil).Once() + client.On("Retrieve", mock.Anything, uint64(102), []byte("nsForcedInclusion")).Return(datypes.ResultRetrieve{ + BaseResult: datypes.BaseResult{Code: datypes.StatusNotFound, Timestamp: time.Now()}, + }).Once() // First block doesn't include the forced inclusion tx data1 := makeData(gen.ChainID, 1, 1) @@ -1072,22 +989,19 @@ func TestVerifyForcedInclusionTxs_SmoothingExceedsEpoch(t *testing.T) { mockExec.EXPECT().InitChain(mock.Anything, mock.Anything, uint64(1), "tchain"). Return([]byte("app0"), uint64(1024), nil).Once() - mockDA := testmocks.NewMockDA(t) + client := testmocks.NewMockClient(t) + client.On("GetHeaderNamespace").Return([]byte(cfg.DA.Namespace)).Maybe() + client.On("GetDataNamespace").Return([]byte(cfg.DA.DataNamespace)).Maybe() + client.On("GetForcedInclusionNamespace").Return([]byte(cfg.DA.ForcedInclusionNamespace)).Maybe() + client.On("HasForcedInclusionNamespace").Return(true).Maybe() - daClient := da.NewClient(da.Config{ - DA: mockDA, - Logger: zerolog.Nop(), - Namespace: cfg.DA.Namespace, - DataNamespace: cfg.DA.DataNamespace, - ForcedInclusionNamespace: cfg.DA.ForcedInclusionNamespace, - }) - daRetriever := NewDARetriever(daClient, cm, gen, zerolog.Nop()) - fiRetriever := da.NewForcedInclusionRetriever(daClient, gen, zerolog.Nop()) + daRetriever := NewDARetriever(client, cm, gen, zerolog.Nop()) + fiRetriever := da.NewForcedInclusionRetriever(client, gen, zerolog.Nop()) s := NewSyncer( st, mockExec, - daClient, + client, cm, common.NopMetrics(), cfg, @@ -1104,30 +1018,28 @@ func TestVerifyForcedInclusionTxs_SmoothingExceedsEpoch(t *testing.T) { require.NoError(t, s.initializeState()) s.ctx = context.Background() - namespaceForcedInclusionBz := coreda.NamespaceFromString(cfg.DA.GetForcedInclusionNamespace()).Bytes() - // Create 3 forced inclusion transactions dataBin1, _ := makeSignedDataBytes(t, gen.ChainID, 10, addr, pub, signer, 2) dataBin2, _ := makeSignedDataBytes(t, gen.ChainID, 11, addr, pub, signer, 2) dataBin3, _ := makeSignedDataBytes(t, gen.ChainID, 12, addr, pub, signer, 2) // Mock DA retrieval for Epoch 1: [100, 102] - mockDA.EXPECT().GetIDs(mock.Anything, uint64(100), mock.MatchedBy(func(ns []byte) bool { - return bytes.Equal(ns, namespaceForcedInclusionBz) - })).Return(&coreda.GetIDsResult{ - IDs: [][]byte{[]byte("fi1"), []byte("fi2"), []byte("fi3")}, - Timestamp: time.Now(), - }, nil).Once() - - mockDA.EXPECT().Get(mock.Anything, mock.Anything, mock.MatchedBy(func(ns []byte) bool { - return bytes.Equal(ns, namespaceForcedInclusionBz) - })).Return([][]byte{dataBin1, dataBin2, dataBin3}, nil).Once() - - for height := uint64(101); height <= 102; height++ { - mockDA.EXPECT().GetIDs(mock.Anything, height, mock.MatchedBy(func(ns []byte) bool { - return bytes.Equal(ns, namespaceForcedInclusionBz) - })).Return(&coreda.GetIDsResult{IDs: [][]byte{}, Timestamp: time.Now()}, nil).Once() - } + client.On("Retrieve", mock.Anything, uint64(100), []byte("nsForcedInclusion")).Return(datypes.ResultRetrieve{ + BaseResult: datypes.BaseResult{ + Code: datypes.StatusSuccess, + IDs: [][]byte{[]byte("fi1"), []byte("fi2"), []byte("fi3")}, + Timestamp: time.Now(), + }, + Data: [][]byte{dataBin1, dataBin2, dataBin3}, + }).Once() + + client.On("Retrieve", mock.Anything, uint64(101), []byte("nsForcedInclusion")).Return(datypes.ResultRetrieve{ + BaseResult: datypes.BaseResult{Code: datypes.StatusNotFound, Timestamp: time.Now()}, + }).Once() + + client.On("Retrieve", mock.Anything, uint64(102), []byte("nsForcedInclusion")).Return(datypes.ResultRetrieve{ + BaseResult: datypes.BaseResult{Code: datypes.StatusNotFound, Timestamp: time.Now()}, + }).Once() // Block at DA height 102 (epoch end): Only includes 2 of 3 txs // The third tx remains pending - legitimate within the epoch diff --git a/block/internal/syncing/syncer_test.go b/block/internal/syncing/syncer_test.go index 65ce4d7e8a..21b012cf21 100644 --- a/block/internal/syncing/syncer_test.go +++ b/block/internal/syncing/syncer_test.go @@ -10,8 +10,8 @@ import ( "testing" "time" - coreda "github.com/evstack/ev-node/core/da" "github.com/evstack/ev-node/core/execution" + datypes "github.com/evstack/ev-node/pkg/da/types" "github.com/evstack/ev-node/pkg/genesis" signerpkg "github.com/evstack/ev-node/pkg/signer" "github.com/evstack/ev-node/pkg/signer/noop" @@ -412,7 +412,7 @@ func TestSyncLoopPersistState(t *testing.T) { }, 1*time.Second, 10*time.Millisecond) cancel() }). - Return(nil, coreda.ErrHeightFromFuture) + Return(nil, datypes.ErrHeightFromFuture) go syncerInst1.processLoop() syncerInst1.startSyncWorkers() diff --git a/block/public.go b/block/public.go index 2326107beb..c8765d29ec 100644 --- a/block/public.go +++ b/block/public.go @@ -5,8 +5,8 @@ import ( "github.com/evstack/ev-node/block/internal/common" "github.com/evstack/ev-node/block/internal/da" - coreda "github.com/evstack/ev-node/core/da" "github.com/evstack/ev-node/pkg/config" + blobrpc "github.com/evstack/ev-node/pkg/da/jsonrpc" "github.com/evstack/ev-node/pkg/genesis" "github.com/rs/zerolog" ) @@ -35,20 +35,27 @@ func NopMetrics() *Metrics { // DAClient is the interface representing the DA client for public use. type DAClient = da.Client -// NewDAClient creates a new DA client with configuration +// DAVerifier is the interface for DA proof verification operations. +type DAVerifier = da.Verifier + +// FullDAClient combines DAClient and DAVerifier interfaces. +// This is the complete interface implemented by the concrete DA client. +type FullDAClient = da.FullClient + +// NewDAClient creates a new DA client backed by the blob JSON-RPC API. +// The returned client implements both DAClient and DAVerifier interfaces. func NewDAClient( - daLayer coreda.DA, + blobRPC *blobrpc.Client, config config.Config, logger zerolog.Logger, -) DAClient { +) FullDAClient { return da.NewClient(da.Config{ - DA: daLayer, + DA: blobRPC, Logger: logger, Namespace: config.DA.GetNamespace(), DefaultTimeout: config.DA.RequestTimeout.Duration, DataNamespace: config.DA.GetDataNamespace(), ForcedInclusionNamespace: config.DA.GetForcedInclusionNamespace(), - RetrieveBatchSize: config.DA.RetrieveBatchSize, }) } diff --git a/core/README.md b/core/README.md index 5afbbef6fe..8f30a3a20f 100644 --- a/core/README.md +++ b/core/README.md @@ -49,58 +49,6 @@ type Sequencer interface { } ``` -### Data Availability (DA) - -The `DA` interface specifies how data is submitted to and retrieved from the underlying data availability layer. - -```go -// core/da/da.go - -// DA defines the interface for the data availability layer. -type DA interface { - // MaxBlobSize returns the max blob size. - MaxBlobSize(ctx context.Context) (uint64, error) - // Get returns Blobs for given IDs. - Get(ctx context.Context, ids []ID, namespace []byte) ([]Blob, error) - // GetIDs returns IDs of Blobs at given height. - GetIDs(ctx context.Context, height uint64, namespace []byte) (*GetIDsResult, error) - // GetProofs returns inclusion proofs for Blobs specified by IDs at given height. - GetProofs(ctx context.Context, ids []ID, namespace []byte) ([]Proof, error) - // Commit creates commitments for Blobs. Submit uses Commit internally. - Commit(ctx context.Context, blobs []Blob, namespace []byte) ([]Commitment, error) - // Submit submits Blobs to DA layer. - Submit(ctx context.Context, blobs []Blob, gasPrice float64, namespace []byte) ([]ID, error) - // Validate validates Commitments against the corresponding Proofs. This should be possible without retrieving the Blobs. - Validate(ctx context.Context, ids []ID, proofs []Proof, namespace []byte) ([]bool, error) - // GasPrice returns the gas price. - GasPrice(ctx context.Context) (float64, error) - // GasMultiplier returns the gas multiplier. - GasMultiplier(ctx context.Context) (float64, error) -} -``` - -The `Client` interface provides a higher-level abstraction for interacting with the DA layer, often used by nodes. - -```go -// core/da/client.go - -// Client is the interface for the DA layer client. -type Client interface { - // Submit submits block data to DA layer. - Submit(ctx context.Context, data [][]byte, maxBlobSize uint64, gasPrice float64) ResultSubmit - // Retrieve retrieves block data from DA layer. - Retrieve(ctx context.Context, dataLayerHeight uint64) ResultRetrieve - // MaxBlobSize returns the maximum blob size for the DA layer. - MaxBlobSize(ctx context.Context) (uint64, error) - // GasPrice returns the gas price for the DA layer. - GasPrice(ctx context.Context) (float64, error) - // GasMultiplier returns the gas multiplier for the DA layer. - GasMultiplier(ctx context.Context) (float64, error) - // GetNamespace returns the namespace for the DA layer. - GetNamespace(ctx context.Context) ([]byte, error) -} -``` - ## Contributing The `core` package is central to Evolve's architecture. Modifications here can have wide-ranging effects. Please adhere to the following guidelines when contributing: diff --git a/core/da/dummy.go b/core/da/dummy.go deleted file mode 100644 index a66622bd77..0000000000 --- a/core/da/dummy.go +++ /dev/null @@ -1,243 +0,0 @@ -package da - -import ( - "bytes" - "context" - "crypto/sha256" - "errors" - "fmt" - "sync" - "time" -) - -var _ DA = (*DummyDA)(nil) - -// DummyDA is a simple in-memory implementation of the DA interface for testing purposes. -type DummyDA struct { - mu sync.RWMutex - blobs map[string]Blob - commitments map[string]Commitment - proofs map[string]Proof - blobsByHeight map[uint64][]ID - timestampsByHeight map[uint64]time.Time - namespaceByID map[string][]byte // Track namespace for each blob ID - maxBlobSize uint64 - - // DA height simulation - currentHeight uint64 - blockTime time.Duration - stopCh chan struct{} - - // Simulated failure support - submitShouldFail bool -} - -var ErrHeightFromFutureStr = fmt.Errorf("given height is from the future") - -// NewDummyDA creates a new instance of DummyDA with the specified maximum blob size and block time. -func NewDummyDA(maxBlobSize uint64, blockTime time.Duration) *DummyDA { - return &DummyDA{ - blobs: make(map[string]Blob), - commitments: make(map[string]Commitment), - proofs: make(map[string]Proof), - blobsByHeight: make(map[uint64][]ID), - timestampsByHeight: make(map[uint64]time.Time), - namespaceByID: make(map[string][]byte), - maxBlobSize: maxBlobSize, - blockTime: blockTime, - stopCh: make(chan struct{}), - currentHeight: 0, - } -} - -// StartHeightTicker starts a goroutine that increments currentHeight every blockTime. -func (d *DummyDA) StartHeightTicker() { - go func() { - ticker := time.NewTicker(d.blockTime) - defer ticker.Stop() - for { - select { - case <-ticker.C: - d.mu.Lock() - d.currentHeight++ - d.mu.Unlock() - case <-d.stopCh: - return - } - } - }() -} - -// StopHeightTicker stops the height ticker goroutine. -func (d *DummyDA) StopHeightTicker() { - close(d.stopCh) -} - -// Get returns blobs for the given IDs. -func (d *DummyDA) Get(ctx context.Context, ids []ID, namespace []byte) ([]Blob, error) { - d.mu.RLock() - defer d.mu.RUnlock() - - blobs := make([]Blob, 0, len(ids)) - for _, id := range ids { - blob, exists := d.blobs[string(id)] - if !exists { - return nil, ErrBlobNotFound // Use the specific error type - } - blobs = append(blobs, blob) - } - return blobs, nil -} - -// GetIDs returns IDs of all blobs at the given height. -func (d *DummyDA) GetIDs(ctx context.Context, height uint64, namespace []byte) (*GetIDsResult, error) { - d.mu.RLock() - defer d.mu.RUnlock() - - if height > d.currentHeight { - return nil, fmt.Errorf("%w: requested %d, current %d", ErrHeightFromFutureStr, height, d.currentHeight) - } - - ids, exists := d.blobsByHeight[height] - if !exists { - return &GetIDsResult{ - IDs: []ID{}, - Timestamp: time.Now(), - }, nil - } - - // Filter IDs by namespace - filteredIDs := make([]ID, 0) - for _, id := range ids { - idStr := string(id) - if ns, exists := d.namespaceByID[idStr]; exists && bytes.Equal(ns, namespace) { - filteredIDs = append(filteredIDs, id) - } - } - - return &GetIDsResult{ - IDs: filteredIDs, - Timestamp: d.timestampsByHeight[height], - }, nil -} - -// GetProofs returns proofs for the given IDs. -func (d *DummyDA) GetProofs(ctx context.Context, ids []ID, namespace []byte) ([]Proof, error) { - d.mu.RLock() - defer d.mu.RUnlock() - - proofs := make([]Proof, 0, len(ids)) - for _, id := range ids { - proof, exists := d.proofs[string(id)] - if !exists { - return nil, errors.New("proof not found") - } - proofs = append(proofs, proof) - } - return proofs, nil -} - -// Commit creates commitments for the given blobs. -func (d *DummyDA) Commit(ctx context.Context, blobs []Blob, namespace []byte) ([]Commitment, error) { - d.mu.Lock() - defer d.mu.Unlock() - - commitments := make([]Commitment, 0, len(blobs)) - for _, blob := range blobs { - // For simplicity, we use the blob itself as the commitment - commitment := blob - commitments = append(commitments, commitment) - } - return commitments, nil -} - -// Submit submits blobs to the DA layer. -func (d *DummyDA) Submit(ctx context.Context, blobs []Blob, gasPrice float64, namespace []byte) ([]ID, error) { - return d.SubmitWithOptions(ctx, blobs, gasPrice, namespace, nil) -} - -// SetSubmitFailure simulates DA layer going down by making Submit calls fail -func (d *DummyDA) SetSubmitFailure(shouldFail bool) { - d.mu.Lock() - defer d.mu.Unlock() - d.submitShouldFail = shouldFail -} - -// SubmitWithOptions submits blobs to the DA layer with additional options. -func (d *DummyDA) SubmitWithOptions(ctx context.Context, blobs []Blob, gasPrice float64, namespace []byte, options []byte) ([]ID, error) { - d.mu.Lock() - defer d.mu.Unlock() - - // Check if we should simulate failure - if d.submitShouldFail { - return nil, errors.New("simulated DA layer failure") - } - - height := d.currentHeight + 1 - ids := make([]ID, 0, len(blobs)) - var currentSize uint64 - - for _, blob := range blobs { // Use _ instead of i - blobLen := uint64(len(blob)) - // Check individual blob size first - if blobLen > d.maxBlobSize { - // Mimic DAClient behavior: if the first blob is too large, return error. - // Otherwise, we would have submitted the previous fitting blobs. - // Since DummyDA processes all at once, we return error if any *individual* blob is too large. - // A more complex dummy could simulate partial submission based on cumulative size. - // For now, error out if any single blob is too big. - return nil, ErrBlobSizeOverLimit // Use specific error type - } - - // Check cumulative batch size - if currentSize+blobLen > d.maxBlobSize { - // Stop processing blobs for this batch, return IDs collected so far - // d.logger.Info("DummyDA: Blob size limit reached for batch", "maxBlobSize", d.maxBlobSize, "index", i, "currentSize", currentSize, "nextBlobSize", blobLen) // Removed logger call - break - } - currentSize += blobLen - - // Create a commitment using SHA-256 hash - bz := sha256.Sum256(blob) - commitment := bz[:] - - // Create ID from height and commitment - id := makeID(height, commitment) - idStr := string(id) - - d.blobs[idStr] = blob - d.commitments[idStr] = commitment - d.proofs[idStr] = commitment // Simple proof - d.namespaceByID[idStr] = namespace // Store namespace for this blob - - ids = append(ids, id) - } - - // Add the IDs to the blobsByHeight map if they don't already exist - if existingIDs, exists := d.blobsByHeight[height]; exists { - d.blobsByHeight[height] = append(existingIDs, ids...) - } else { - d.blobsByHeight[height] = ids - } - d.timestampsByHeight[height] = time.Now() - - return ids, nil -} - -// Validate validates commitments against proofs. -func (d *DummyDA) Validate(ctx context.Context, ids []ID, proofs []Proof, namespace []byte) ([]bool, error) { - d.mu.RLock() - defer d.mu.RUnlock() - - if len(ids) != len(proofs) { - return nil, errors.New("number of IDs and proofs must match") - } - - results := make([]bool, len(ids)) - for i, id := range ids { - _, exists := d.blobs[string(id)] - results[i] = exists - } - - return results, nil -} diff --git a/core/da/dummy_test.go b/core/da/dummy_test.go deleted file mode 100644 index 9538aacc83..0000000000 --- a/core/da/dummy_test.go +++ /dev/null @@ -1,137 +0,0 @@ -package da - -import ( - "context" - "fmt" - "testing" - "time" -) - -func TestDummyDA(t *testing.T) { - testDABlockTime := 100 * time.Millisecond - // Create a new DummyDA instance with a max blob size of 1024 bytes - dummyDA := NewDummyDA(1024, testDABlockTime) - dummyDA.StartHeightTicker() - defer dummyDA.StopHeightTicker() - // Height is always 0 - ctx := context.Background() - - // Test Submit - blobs := []Blob{ - []byte("test blob 1"), - []byte("test blob 2"), - } - ids, err := dummyDA.Submit(ctx, blobs, 0, nil) - if err != nil { - t.Fatalf("Submit failed: %v", err) - } - err = waitForFirstDAHeight(ctx, dummyDA) // Wait for height to increment - if err != nil { - t.Fatalf("waitForFirstDAHeight failed: %v", err) - } - if len(ids) != len(blobs) { - t.Errorf("Expected %d IDs, got %d", len(blobs), len(ids)) - } - - // Test Get - retrievedBlobs, err := dummyDA.Get(ctx, ids, nil) - if err != nil { - t.Fatalf("Get failed: %v", err) - } - if len(retrievedBlobs) != len(blobs) { - t.Errorf("Expected %d blobs, got %d", len(blobs), len(retrievedBlobs)) - } - for i, blob := range blobs { - if string(retrievedBlobs[i]) != string(blob) { - t.Errorf("Expected blob %q, got %q", string(blob), string(retrievedBlobs[i])) - } - } - - // Test GetIDs - result, err := dummyDA.GetIDs(ctx, 1, nil) - if err != nil { - t.Fatalf("GetIDs failed: %v", err) - } - if len(result.IDs) != len(ids) { - t.Errorf("Expected %d IDs, got %d", len(ids), len(result.IDs)) - } - - // Test Commit - commitments, err := dummyDA.Commit(ctx, blobs, nil) - if err != nil { - t.Fatalf("Commit failed: %v", err) - } - if len(commitments) != len(blobs) { - t.Errorf("Expected %d commitments, got %d", len(blobs), len(commitments)) - } - - // Test GetProofs - proofs, err := dummyDA.GetProofs(ctx, ids, nil) - if err != nil { - t.Fatalf("GetProofs failed: %v", err) - } - if len(proofs) != len(ids) { - t.Errorf("Expected %d proofs, got %d", len(ids), len(proofs)) - } - - // Test Validate - validations, err := dummyDA.Validate(ctx, ids, proofs, nil) - if err != nil { - t.Fatalf("Validate failed: %v", err) - } - if len(validations) != len(ids) { - t.Errorf("Expected %d validations, got %d", len(ids), len(validations)) - } - for _, valid := range validations { - if !valid { - t.Errorf("Expected validation to be true") - } - } - - // Test error case: blob size exceeds maximum - largeBlob := make([]byte, 2048) // Larger than our max of 1024 - _, err = dummyDA.Submit(ctx, []Blob{largeBlob}, 0, nil) - if err == nil { - t.Errorf("Expected error for blob exceeding max size, got nil") - } -} - -func waitForFirstDAHeight(ctx context.Context, da *DummyDA) error { - return waitForAtLeastDAHeight(ctx, da, 1) -} - -// waitForAtLeastDAHeight waits for the DummyDA to reach at least the given height -func waitForAtLeastDAHeight(ctx context.Context, da *DummyDA, targetHeight uint64) error { - // Read current height at the start - da.mu.RLock() - current := da.currentHeight - da.mu.RUnlock() - - if current >= targetHeight { - return nil - } - - delta := targetHeight - current - - // Dynamically set pollInterval and timeout based on delta - pollInterval := da.blockTime / 2 - timeout := da.blockTime * time.Duration(delta+2) - - deadline := time.Now().Add(timeout) - for { - da.mu.RLock() - current = da.currentHeight - da.mu.RUnlock() - if current >= targetHeight { - return nil - } - if time.Now().After(deadline) { - return fmt.Errorf("timeout waiting for DA height %d, current %d", targetHeight, current) - } - select { - case <-ctx.Done(): - return ctx.Err() - case <-time.After(pollInterval): - } - } -} diff --git a/core/da/namespace.go b/core/da/namespace.go deleted file mode 100644 index 057bb29365..0000000000 --- a/core/da/namespace.go +++ /dev/null @@ -1,129 +0,0 @@ -package da - -import ( - "crypto/sha256" - "encoding/hex" - "fmt" - "strings" -) - -// Implemented in accordance to https://celestiaorg.github.io/celestia-app/namespace.html - -const ( - // NamespaceVersionIndex is the index of the namespace version in the byte slice - NamespaceVersionIndex = 0 - // NamespaceVersionSize is the size of the namespace version in bytes - NamespaceVersionSize = 1 - // NamespaceIDSize is the size of the namespace ID in bytes - NamespaceIDSize = 28 - // NamespaceSize is the total size of a namespace (version + ID) in bytes - NamespaceSize = NamespaceVersionSize + NamespaceIDSize - - // NamespaceVersionZero is the only supported user-specifiable namespace version - NamespaceVersionZero = uint8(0) - // NamespaceVersionMax is the max namespace version - NamespaceVersionMax = uint8(255) - - // NamespaceVersionZeroPrefixSize is the number of leading zero bytes required for version 0 - NamespaceVersionZeroPrefixSize = 18 - // NamespaceVersionZeroDataSize is the number of data bytes available for version 0 - NamespaceVersionZeroDataSize = 10 -) - -// Namespace represents a Celestia namespace -type Namespace struct { - Version uint8 - ID [NamespaceIDSize]byte -} - -// Bytes returns the namespace as a byte slice -func (n Namespace) Bytes() []byte { - result := make([]byte, NamespaceSize) - result[NamespaceVersionIndex] = n.Version - copy(result[NamespaceVersionSize:], n.ID[:]) - return result -} - -// IsValidForVersion0 checks if the namespace is valid for version 0 -// Version 0 requires the first 18 bytes of the ID to be zero -func (n Namespace) IsValidForVersion0() bool { - if n.Version != NamespaceVersionZero { - return false - } - - for i := range NamespaceVersionZeroPrefixSize { - if n.ID[i] != 0 { - return false - } - } - return true -} - -// NewNamespaceV0 creates a new version 0 namespace from the provided data -// The data should be up to 10 bytes and will be placed in the last 10 bytes of the ID -// The first 18 bytes will be zeros as required by the specification -func NewNamespaceV0(data []byte) (*Namespace, error) { - if len(data) > NamespaceVersionZeroDataSize { - return nil, fmt.Errorf("data too long for version 0 namespace: got %d bytes, max %d", - len(data), NamespaceVersionZeroDataSize) - } - - ns := &Namespace{ - Version: NamespaceVersionZero, - } - - // The first 18 bytes are already zero (Go zero-initializes) - // Copy the data to the last 10 bytes - copy(ns.ID[NamespaceVersionZeroPrefixSize:], data) - - return ns, nil -} - -// NamespaceFromBytes creates a namespace from a 29-byte slice -func NamespaceFromBytes(b []byte) (*Namespace, error) { - if len(b) != NamespaceSize { - return nil, fmt.Errorf("invalid namespace size: expected %d, got %d", NamespaceSize, len(b)) - } - - ns := &Namespace{ - Version: b[NamespaceVersionIndex], - } - copy(ns.ID[:], b[NamespaceVersionSize:]) - - // Validate if it's version 0 - if ns.Version == NamespaceVersionZero && !ns.IsValidForVersion0() { - return nil, fmt.Errorf("invalid version 0 namespace: first %d bytes of ID must be zero", - NamespaceVersionZeroPrefixSize) - } - - return ns, nil -} - -// NamespaceFromString creates a version 0 namespace from a string identifier -// The string is hashed and the first 10 bytes of the hash are used as the namespace data -func NamespaceFromString(s string) *Namespace { - // Hash the string to get consistent bytes - hash := sha256.Sum256([]byte(s)) - - // Use the first 10 bytes of the hash for the namespace data - ns, _ := NewNamespaceV0(hash[:NamespaceVersionZeroDataSize]) - return ns -} - -// HexString returns the hex representation of the namespace -func (n Namespace) HexString() string { - return "0x" + hex.EncodeToString(n.Bytes()) -} - -// ParseHexNamespace parses a hex string into a namespace -func ParseHexNamespace(hexStr string) (*Namespace, error) { - // Remove 0x prefix if present - hexStr = strings.TrimPrefix(hexStr, "0x") - - b, err := hex.DecodeString(hexStr) - if err != nil { - return nil, fmt.Errorf("invalid hex string: %w", err) - } - - return NamespaceFromBytes(b) -} diff --git a/core/da/types.go b/core/da/types.go deleted file mode 100644 index 4229f99879..0000000000 --- a/core/da/types.go +++ /dev/null @@ -1,126 +0,0 @@ -package da - -import ( - "context" - "encoding/binary" - "fmt" - "time" -) - -// DA defines very generic interface for interaction with Data Availability layers. -type DA interface { - // Get returns Blob for each given ID, or an error. - // - // Error should be returned if ID is not formatted properly, there is no Blob for given ID or any other client-level - // error occurred (dropped connection, timeout, etc). - Get(ctx context.Context, ids []ID, namespace []byte) ([]Blob, error) - - // GetIDs returns IDs of all Blobs located in DA at given height. - GetIDs(ctx context.Context, height uint64, namespace []byte) (*GetIDsResult, error) - - // GetProofs returns inclusion Proofs for Blobs specified by their IDs. - GetProofs(ctx context.Context, ids []ID, namespace []byte) ([]Proof, error) - - // Commit creates a Commitment for each given Blob. - Commit(ctx context.Context, blobs []Blob, namespace []byte) ([]Commitment, error) - - // Submit submits the Blobs to Data Availability layer. - // - // This method is synchronous. Upon successful submission to Data Availability layer, it returns the IDs identifying blobs - // in DA. - Submit(ctx context.Context, blobs []Blob, gasPrice float64, namespace []byte) ([]ID, error) - - // SubmitWithOptions submits the Blobs to Data Availability layer with additional options. - SubmitWithOptions(ctx context.Context, blobs []Blob, gasPrice float64, namespace []byte, options []byte) ([]ID, error) - - // Validate validates Commitments against the corresponding Proofs. This should be possible without retrieving the Blobs. - Validate(ctx context.Context, ids []ID, proofs []Proof, namespace []byte) ([]bool, error) -} - -// Blob is the data submitted/received from DA interface. -type Blob = []byte - -// ID should contain serialized data required by the implementation to find blob in Data Availability layer. -type ID = []byte - -// Commitment should contain serialized cryptographic commitment to Blob value. -type Commitment = []byte - -// Proof should contain serialized proof of inclusion (publication) of Blob in Data Availability layer. -type Proof = []byte - -// GetIDsResult holds the result of GetIDs call: IDs and timestamp of corresponding block. -type GetIDsResult struct { - IDs []ID - Timestamp time.Time -} - -// ResultSubmit contains information returned from DA layer after block headers/data submission. -type ResultSubmit struct { - BaseResult -} - -// ResultRetrieveHeaders contains batch of block headers returned from DA layer client. -type ResultRetrieve struct { - BaseResult - // Data is the block data retrieved from Data Availability Layer. - // If Code is not equal to StatusSuccess, it has to be nil. - Data [][]byte -} - -// StatusCode is a type for DA layer return status. -// TODO: define an enum of different non-happy-path cases -// that might need to be handled by Evolve independent of -// the underlying DA chain. -type StatusCode uint64 - -// Data Availability return codes. -const ( - StatusUnknown StatusCode = iota - StatusSuccess - StatusNotFound - StatusNotIncludedInBlock - StatusAlreadyInMempool - StatusTooBig - StatusContextDeadline - StatusError - StatusIncorrectAccountSequence - StatusContextCanceled - StatusHeightFromFuture -) - -// BaseResult contains basic information returned by DA layer. -type BaseResult struct { - // Code is to determine if the action succeeded. - Code StatusCode - // Message may contain DA layer specific information (like DA block height/hash, detailed error message, etc) - Message string - // Height is the height of the block on Data Availability Layer for given result. - Height uint64 - // SubmittedCount is the number of successfully submitted blocks. - SubmittedCount uint64 - // BlobSize is the size of the blob submitted. - BlobSize uint64 - // IDs is the list of IDs of the blobs submitted. - IDs [][]byte - // Timestamp is the timestamp of the posted data on Data Availability Layer. - Timestamp time.Time -} - -// makeID creates an ID from a height and a commitment. -func makeID(height uint64, commitment []byte) []byte { - id := make([]byte, len(commitment)+8) - binary.LittleEndian.PutUint64(id, height) - copy(id[8:], commitment) - return id -} - -// SplitID splits an ID into a height and a commitment. -// if len(id) <= 8, it returns 0 and nil. -func SplitID(id []byte) (uint64, []byte, error) { - if len(id) <= 8 { - return 0, nil, fmt.Errorf("invalid ID length: %d", len(id)) - } - commitment := id[8:] - return binary.LittleEndian.Uint64(id[:8]), commitment, nil -} diff --git a/da/go.mod b/da/go.mod deleted file mode 100644 index c73094512b..0000000000 --- a/da/go.mod +++ /dev/null @@ -1,30 +0,0 @@ -module github.com/evstack/ev-node/da - -go 1.24.6 - -require ( - github.com/evstack/ev-node/core v1.0.0-beta.5 - github.com/filecoin-project/go-jsonrpc v0.9.0 - github.com/rs/zerolog v1.34.0 - github.com/stretchr/testify v1.11.1 -) - -require ( - github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect - github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 // indirect - github.com/google/go-cmp v0.7.0 // indirect - github.com/google/uuid v1.6.0 // indirect - github.com/gorilla/websocket v1.5.3 // indirect - github.com/ipfs/go-log/v2 v2.9.0 // indirect - github.com/mattn/go-colorable v0.1.14 // indirect - github.com/mattn/go-isatty v0.0.20 // indirect - github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect - github.com/stretchr/objx v0.5.2 // indirect - go.opencensus.io v0.24.0 // indirect - go.uber.org/multierr v1.11.0 // indirect - go.uber.org/zap v1.27.0 // indirect - golang.org/x/sys v0.38.0 // indirect - golang.org/x/xerrors v0.0.0-20240903120638-7835f813f4da // indirect - gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect - gopkg.in/yaml.v3 v3.0.1 // indirect -) diff --git a/da/go.sum b/da/go.sum deleted file mode 100644 index 023694d9c2..0000000000 --- a/da/go.sum +++ /dev/null @@ -1,149 +0,0 @@ -cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= -github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= -github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= -github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= -github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= -github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= -github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= -github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= -github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= -github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= -github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= -github.com/evstack/ev-node/core v1.0.0-beta.5 h1:lgxE8XiF3U9pcFgh7xuKMgsOGvLBGRyd9kc9MR4WL0o= -github.com/evstack/ev-node/core v1.0.0-beta.5/go.mod h1:n2w/LhYQTPsi48m6lMj16YiIqsaQw6gxwjyJvR+B3sY= -github.com/filecoin-project/go-jsonrpc v0.9.0 h1:G47qEF52w7GholpI21vPSTVBFvsrip6geIoqNiqyZtQ= -github.com/filecoin-project/go-jsonrpc v0.9.0/go.mod h1:OG7kVBVh/AbDFHIwx7Kw0l9ARmKOS6gGOr0LbdBpbLc= -github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= -github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= -github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= -github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 h1:f+oWsMOmNPc8JmEHVZIycC7hBoQxHH9pNKQORJNozsQ= -github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8/go.mod h1:wcDNUvekVysuuOpQKo3191zZyTpiI6se1N1ULghS0sw= -github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= -github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= -github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= -github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= -github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= -github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= -github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= -github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= -github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= -github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= -github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= -github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= -github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= -github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= -github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= -github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -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/ipfs/go-log/v2 v2.9.0 h1:l4b06AwVXwldIzbVPZy5z7sKp9lHFTX0KWfTBCtHaOk= -github.com/ipfs/go-log/v2 v2.9.0/go.mod h1:UhIYAwMV7Nb4ZmihUxfIRM2Istw/y9cAk3xaK+4Zs2c= -github.com/kr/pretty v0.2.1 h1:Fmg33tUaq4/8ym9TJN1x7sLJnHVwhP33CNkpYV/7rwI= -github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= -github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= -github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= -github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= -github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= -github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= -github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE= -github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8= -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/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= -github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= -github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= -github.com/rs/xid v1.6.0/go.mod h1:7XoLgs4eV+QndskICGsho+ADou8ySMSjJKDIan90Nz0= -github.com/rs/zerolog v1.34.0 h1:k43nTLIwcTVQAncfCw4KZ2VY6ukYoZaBPNOE8txlOeY= -github.com/rs/zerolog v1.34.0/go.mod h1:bJsvje4Z08ROH4Nhs5iH600c3IkWhwp44iRc54W6wYQ= -github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= -github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= -github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY= -github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= -github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= -github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= -github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= -github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= -go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0= -go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo= -go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= -go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= -go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= -go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= -go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8= -go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E= -golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= -golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= -golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= -golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= -golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= -golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/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= -golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= -golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= -golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.38.0 h1:3yZWxaJjBmCWXqhN1qh02AkOnCQ1poK6oF+a7xWL6Gc= -golang.org/x/sys v0.38.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= -golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= -golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= -golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= -golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= -golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20240903120638-7835f813f4da h1:noIWHXmPHxILtqtCOPIhSt0ABwskkZKjD3bXGnZGpNY= -golang.org/x/xerrors v0.0.0-20240903120638-7835f813f4da/go.mod h1:NDW/Ps6MPRej6fsCIbMTohpP40sJ/P/vI1MoTEGwX90= -google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= -google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= -google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= -google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= -google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= -google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= -google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= -google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= -google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= -google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc= -google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= -google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= -google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= -google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= -google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= -google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= -google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= -google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= -google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= -gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= -gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= -gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= -gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= -honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= diff --git a/da/internal/mocks/da.go b/da/internal/mocks/da.go deleted file mode 100644 index bb3ad63391..0000000000 --- a/da/internal/mocks/da.go +++ /dev/null @@ -1,581 +0,0 @@ -// Code generated by mockery; DO NOT EDIT. -// github.com/vektra/mockery -// template: testify - -package mocks - -import ( - "context" - - "github.com/evstack/ev-node/core/da" - mock "github.com/stretchr/testify/mock" -) - -// NewMockDA creates a new instance of MockDA. 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 NewMockDA(t interface { - mock.TestingT - Cleanup(func()) -}) *MockDA { - mock := &MockDA{} - mock.Mock.Test(t) - - t.Cleanup(func() { mock.AssertExpectations(t) }) - - return mock -} - -// MockDA is an autogenerated mock type for the DA type -type MockDA struct { - mock.Mock -} - -type MockDA_Expecter struct { - mock *mock.Mock -} - -func (_m *MockDA) EXPECT() *MockDA_Expecter { - return &MockDA_Expecter{mock: &_m.Mock} -} - -// Commit provides a mock function for the type MockDA -func (_mock *MockDA) Commit(ctx context.Context, blobs []da.Blob, namespace []byte) ([]da.Commitment, error) { - ret := _mock.Called(ctx, blobs, namespace) - - if len(ret) == 0 { - panic("no return value specified for Commit") - } - - var r0 []da.Commitment - var r1 error - if returnFunc, ok := ret.Get(0).(func(context.Context, []da.Blob, []byte) ([]da.Commitment, error)); ok { - return returnFunc(ctx, blobs, namespace) - } - if returnFunc, ok := ret.Get(0).(func(context.Context, []da.Blob, []byte) []da.Commitment); ok { - r0 = returnFunc(ctx, blobs, namespace) - } else { - if ret.Get(0) != nil { - r0 = ret.Get(0).([]da.Commitment) - } - } - if returnFunc, ok := ret.Get(1).(func(context.Context, []da.Blob, []byte) error); ok { - r1 = returnFunc(ctx, blobs, namespace) - } else { - r1 = ret.Error(1) - } - return r0, r1 -} - -// MockDA_Commit_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Commit' -type MockDA_Commit_Call struct { - *mock.Call -} - -// Commit is a helper method to define mock.On call -// - ctx context.Context -// - blobs []da.Blob -// - namespace []byte -func (_e *MockDA_Expecter) Commit(ctx interface{}, blobs interface{}, namespace interface{}) *MockDA_Commit_Call { - return &MockDA_Commit_Call{Call: _e.mock.On("Commit", ctx, blobs, namespace)} -} - -func (_c *MockDA_Commit_Call) Run(run func(ctx context.Context, blobs []da.Blob, namespace []byte)) *MockDA_Commit_Call { - _c.Call.Run(func(args mock.Arguments) { - var arg0 context.Context - if args[0] != nil { - arg0 = args[0].(context.Context) - } - var arg1 []da.Blob - if args[1] != nil { - arg1 = args[1].([]da.Blob) - } - var arg2 []byte - if args[2] != nil { - arg2 = args[2].([]byte) - } - run( - arg0, - arg1, - arg2, - ) - }) - return _c -} - -func (_c *MockDA_Commit_Call) Return(vs []da.Commitment, err error) *MockDA_Commit_Call { - _c.Call.Return(vs, err) - return _c -} - -func (_c *MockDA_Commit_Call) RunAndReturn(run func(ctx context.Context, blobs []da.Blob, namespace []byte) ([]da.Commitment, error)) *MockDA_Commit_Call { - _c.Call.Return(run) - return _c -} - -// Get provides a mock function for the type MockDA -func (_mock *MockDA) Get(ctx context.Context, ids []da.ID, namespace []byte) ([]da.Blob, error) { - ret := _mock.Called(ctx, ids, namespace) - - if len(ret) == 0 { - panic("no return value specified for Get") - } - - var r0 []da.Blob - var r1 error - if returnFunc, ok := ret.Get(0).(func(context.Context, []da.ID, []byte) ([]da.Blob, error)); ok { - return returnFunc(ctx, ids, namespace) - } - if returnFunc, ok := ret.Get(0).(func(context.Context, []da.ID, []byte) []da.Blob); ok { - r0 = returnFunc(ctx, ids, namespace) - } else { - if ret.Get(0) != nil { - r0 = ret.Get(0).([]da.Blob) - } - } - if returnFunc, ok := ret.Get(1).(func(context.Context, []da.ID, []byte) error); ok { - r1 = returnFunc(ctx, ids, namespace) - } else { - r1 = ret.Error(1) - } - return r0, r1 -} - -// MockDA_Get_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Get' -type MockDA_Get_Call struct { - *mock.Call -} - -// Get is a helper method to define mock.On call -// - ctx context.Context -// - ids []da.ID -// - namespace []byte -func (_e *MockDA_Expecter) Get(ctx interface{}, ids interface{}, namespace interface{}) *MockDA_Get_Call { - return &MockDA_Get_Call{Call: _e.mock.On("Get", ctx, ids, namespace)} -} - -func (_c *MockDA_Get_Call) Run(run func(ctx context.Context, ids []da.ID, namespace []byte)) *MockDA_Get_Call { - _c.Call.Run(func(args mock.Arguments) { - var arg0 context.Context - if args[0] != nil { - arg0 = args[0].(context.Context) - } - var arg1 []da.ID - if args[1] != nil { - arg1 = args[1].([]da.ID) - } - var arg2 []byte - if args[2] != nil { - arg2 = args[2].([]byte) - } - run( - arg0, - arg1, - arg2, - ) - }) - return _c -} - -func (_c *MockDA_Get_Call) Return(vs []da.Blob, err error) *MockDA_Get_Call { - _c.Call.Return(vs, err) - return _c -} - -func (_c *MockDA_Get_Call) RunAndReturn(run func(ctx context.Context, ids []da.ID, namespace []byte) ([]da.Blob, error)) *MockDA_Get_Call { - _c.Call.Return(run) - return _c -} - -// GetIDs provides a mock function for the type MockDA -func (_mock *MockDA) GetIDs(ctx context.Context, height uint64, namespace []byte) (*da.GetIDsResult, error) { - ret := _mock.Called(ctx, height, namespace) - - if len(ret) == 0 { - panic("no return value specified for GetIDs") - } - - var r0 *da.GetIDsResult - var r1 error - if returnFunc, ok := ret.Get(0).(func(context.Context, uint64, []byte) (*da.GetIDsResult, error)); ok { - return returnFunc(ctx, height, namespace) - } - if returnFunc, ok := ret.Get(0).(func(context.Context, uint64, []byte) *da.GetIDsResult); ok { - r0 = returnFunc(ctx, height, namespace) - } else { - if ret.Get(0) != nil { - r0 = ret.Get(0).(*da.GetIDsResult) - } - } - if returnFunc, ok := ret.Get(1).(func(context.Context, uint64, []byte) error); ok { - r1 = returnFunc(ctx, height, namespace) - } else { - r1 = ret.Error(1) - } - return r0, r1 -} - -// MockDA_GetIDs_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GetIDs' -type MockDA_GetIDs_Call struct { - *mock.Call -} - -// GetIDs is a helper method to define mock.On call -// - ctx context.Context -// - height uint64 -// - namespace []byte -func (_e *MockDA_Expecter) GetIDs(ctx interface{}, height interface{}, namespace interface{}) *MockDA_GetIDs_Call { - return &MockDA_GetIDs_Call{Call: _e.mock.On("GetIDs", ctx, height, namespace)} -} - -func (_c *MockDA_GetIDs_Call) Run(run func(ctx context.Context, height uint64, namespace []byte)) *MockDA_GetIDs_Call { - _c.Call.Run(func(args mock.Arguments) { - var arg0 context.Context - if args[0] != nil { - arg0 = args[0].(context.Context) - } - var arg1 uint64 - if args[1] != nil { - arg1 = args[1].(uint64) - } - var arg2 []byte - if args[2] != nil { - arg2 = args[2].([]byte) - } - run( - arg0, - arg1, - arg2, - ) - }) - return _c -} - -func (_c *MockDA_GetIDs_Call) Return(getIDsResult *da.GetIDsResult, err error) *MockDA_GetIDs_Call { - _c.Call.Return(getIDsResult, err) - return _c -} - -func (_c *MockDA_GetIDs_Call) RunAndReturn(run func(ctx context.Context, height uint64, namespace []byte) (*da.GetIDsResult, error)) *MockDA_GetIDs_Call { - _c.Call.Return(run) - return _c -} - -// GetProofs provides a mock function for the type MockDA -func (_mock *MockDA) GetProofs(ctx context.Context, ids []da.ID, namespace []byte) ([]da.Proof, error) { - ret := _mock.Called(ctx, ids, namespace) - - if len(ret) == 0 { - panic("no return value specified for GetProofs") - } - - var r0 []da.Proof - var r1 error - if returnFunc, ok := ret.Get(0).(func(context.Context, []da.ID, []byte) ([]da.Proof, error)); ok { - return returnFunc(ctx, ids, namespace) - } - if returnFunc, ok := ret.Get(0).(func(context.Context, []da.ID, []byte) []da.Proof); ok { - r0 = returnFunc(ctx, ids, namespace) - } else { - if ret.Get(0) != nil { - r0 = ret.Get(0).([]da.Proof) - } - } - if returnFunc, ok := ret.Get(1).(func(context.Context, []da.ID, []byte) error); ok { - r1 = returnFunc(ctx, ids, namespace) - } else { - r1 = ret.Error(1) - } - return r0, r1 -} - -// MockDA_GetProofs_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GetProofs' -type MockDA_GetProofs_Call struct { - *mock.Call -} - -// GetProofs is a helper method to define mock.On call -// - ctx context.Context -// - ids []da.ID -// - namespace []byte -func (_e *MockDA_Expecter) GetProofs(ctx interface{}, ids interface{}, namespace interface{}) *MockDA_GetProofs_Call { - return &MockDA_GetProofs_Call{Call: _e.mock.On("GetProofs", ctx, ids, namespace)} -} - -func (_c *MockDA_GetProofs_Call) Run(run func(ctx context.Context, ids []da.ID, namespace []byte)) *MockDA_GetProofs_Call { - _c.Call.Run(func(args mock.Arguments) { - var arg0 context.Context - if args[0] != nil { - arg0 = args[0].(context.Context) - } - var arg1 []da.ID - if args[1] != nil { - arg1 = args[1].([]da.ID) - } - var arg2 []byte - if args[2] != nil { - arg2 = args[2].([]byte) - } - run( - arg0, - arg1, - arg2, - ) - }) - return _c -} - -func (_c *MockDA_GetProofs_Call) Return(vs []da.Proof, err error) *MockDA_GetProofs_Call { - _c.Call.Return(vs, err) - return _c -} - -func (_c *MockDA_GetProofs_Call) RunAndReturn(run func(ctx context.Context, ids []da.ID, namespace []byte) ([]da.Proof, error)) *MockDA_GetProofs_Call { - _c.Call.Return(run) - return _c -} - -// Submit provides a mock function for the type MockDA -func (_mock *MockDA) Submit(ctx context.Context, blobs []da.Blob, gasPrice float64, namespace []byte) ([]da.ID, error) { - ret := _mock.Called(ctx, blobs, gasPrice, namespace) - - if len(ret) == 0 { - panic("no return value specified for Submit") - } - - var r0 []da.ID - var r1 error - if returnFunc, ok := ret.Get(0).(func(context.Context, []da.Blob, float64, []byte) ([]da.ID, error)); ok { - return returnFunc(ctx, blobs, gasPrice, namespace) - } - if returnFunc, ok := ret.Get(0).(func(context.Context, []da.Blob, float64, []byte) []da.ID); ok { - r0 = returnFunc(ctx, blobs, gasPrice, namespace) - } else { - if ret.Get(0) != nil { - r0 = ret.Get(0).([]da.ID) - } - } - if returnFunc, ok := ret.Get(1).(func(context.Context, []da.Blob, float64, []byte) error); ok { - r1 = returnFunc(ctx, blobs, gasPrice, namespace) - } else { - r1 = ret.Error(1) - } - return r0, r1 -} - -// MockDA_Submit_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Submit' -type MockDA_Submit_Call struct { - *mock.Call -} - -// Submit is a helper method to define mock.On call -// - ctx context.Context -// - blobs []da.Blob -// - gasPrice float64 -// - namespace []byte -func (_e *MockDA_Expecter) Submit(ctx interface{}, blobs interface{}, gasPrice interface{}, namespace interface{}) *MockDA_Submit_Call { - return &MockDA_Submit_Call{Call: _e.mock.On("Submit", ctx, blobs, gasPrice, namespace)} -} - -func (_c *MockDA_Submit_Call) Run(run func(ctx context.Context, blobs []da.Blob, gasPrice float64, namespace []byte)) *MockDA_Submit_Call { - _c.Call.Run(func(args mock.Arguments) { - var arg0 context.Context - if args[0] != nil { - arg0 = args[0].(context.Context) - } - var arg1 []da.Blob - if args[1] != nil { - arg1 = args[1].([]da.Blob) - } - var arg2 float64 - if args[2] != nil { - arg2 = args[2].(float64) - } - var arg3 []byte - if args[3] != nil { - arg3 = args[3].([]byte) - } - run( - arg0, - arg1, - arg2, - arg3, - ) - }) - return _c -} - -func (_c *MockDA_Submit_Call) Return(vs []da.ID, err error) *MockDA_Submit_Call { - _c.Call.Return(vs, err) - return _c -} - -func (_c *MockDA_Submit_Call) RunAndReturn(run func(ctx context.Context, blobs []da.Blob, gasPrice float64, namespace []byte) ([]da.ID, error)) *MockDA_Submit_Call { - _c.Call.Return(run) - return _c -} - -// SubmitWithOptions provides a mock function for the type MockDA -func (_mock *MockDA) SubmitWithOptions(ctx context.Context, blobs []da.Blob, gasPrice float64, namespace []byte, options []byte) ([]da.ID, error) { - ret := _mock.Called(ctx, blobs, gasPrice, namespace, options) - - if len(ret) == 0 { - panic("no return value specified for SubmitWithOptions") - } - - var r0 []da.ID - var r1 error - if returnFunc, ok := ret.Get(0).(func(context.Context, []da.Blob, float64, []byte, []byte) ([]da.ID, error)); ok { - return returnFunc(ctx, blobs, gasPrice, namespace, options) - } - if returnFunc, ok := ret.Get(0).(func(context.Context, []da.Blob, float64, []byte, []byte) []da.ID); ok { - r0 = returnFunc(ctx, blobs, gasPrice, namespace, options) - } else { - if ret.Get(0) != nil { - r0 = ret.Get(0).([]da.ID) - } - } - if returnFunc, ok := ret.Get(1).(func(context.Context, []da.Blob, float64, []byte, []byte) error); ok { - r1 = returnFunc(ctx, blobs, gasPrice, namespace, options) - } else { - r1 = ret.Error(1) - } - return r0, r1 -} - -// MockDA_SubmitWithOptions_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'SubmitWithOptions' -type MockDA_SubmitWithOptions_Call struct { - *mock.Call -} - -// SubmitWithOptions is a helper method to define mock.On call -// - ctx context.Context -// - blobs []da.Blob -// - gasPrice float64 -// - namespace []byte -// - options []byte -func (_e *MockDA_Expecter) SubmitWithOptions(ctx interface{}, blobs interface{}, gasPrice interface{}, namespace interface{}, options interface{}) *MockDA_SubmitWithOptions_Call { - return &MockDA_SubmitWithOptions_Call{Call: _e.mock.On("SubmitWithOptions", ctx, blobs, gasPrice, namespace, options)} -} - -func (_c *MockDA_SubmitWithOptions_Call) Run(run func(ctx context.Context, blobs []da.Blob, gasPrice float64, namespace []byte, options []byte)) *MockDA_SubmitWithOptions_Call { - _c.Call.Run(func(args mock.Arguments) { - var arg0 context.Context - if args[0] != nil { - arg0 = args[0].(context.Context) - } - var arg1 []da.Blob - if args[1] != nil { - arg1 = args[1].([]da.Blob) - } - var arg2 float64 - if args[2] != nil { - arg2 = args[2].(float64) - } - var arg3 []byte - if args[3] != nil { - arg3 = args[3].([]byte) - } - var arg4 []byte - if args[4] != nil { - arg4 = args[4].([]byte) - } - run( - arg0, - arg1, - arg2, - arg3, - arg4, - ) - }) - return _c -} - -func (_c *MockDA_SubmitWithOptions_Call) Return(vs []da.ID, err error) *MockDA_SubmitWithOptions_Call { - _c.Call.Return(vs, err) - return _c -} - -func (_c *MockDA_SubmitWithOptions_Call) RunAndReturn(run func(ctx context.Context, blobs []da.Blob, gasPrice float64, namespace []byte, options []byte) ([]da.ID, error)) *MockDA_SubmitWithOptions_Call { - _c.Call.Return(run) - return _c -} - -// Validate provides a mock function for the type MockDA -func (_mock *MockDA) Validate(ctx context.Context, ids []da.ID, proofs []da.Proof, namespace []byte) ([]bool, error) { - ret := _mock.Called(ctx, ids, proofs, namespace) - - if len(ret) == 0 { - panic("no return value specified for Validate") - } - - var r0 []bool - var r1 error - if returnFunc, ok := ret.Get(0).(func(context.Context, []da.ID, []da.Proof, []byte) ([]bool, error)); ok { - return returnFunc(ctx, ids, proofs, namespace) - } - if returnFunc, ok := ret.Get(0).(func(context.Context, []da.ID, []da.Proof, []byte) []bool); ok { - r0 = returnFunc(ctx, ids, proofs, namespace) - } else { - if ret.Get(0) != nil { - r0 = ret.Get(0).([]bool) - } - } - if returnFunc, ok := ret.Get(1).(func(context.Context, []da.ID, []da.Proof, []byte) error); ok { - r1 = returnFunc(ctx, ids, proofs, namespace) - } else { - r1 = ret.Error(1) - } - return r0, r1 -} - -// MockDA_Validate_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Validate' -type MockDA_Validate_Call struct { - *mock.Call -} - -// Validate is a helper method to define mock.On call -// - ctx context.Context -// - ids []da.ID -// - proofs []da.Proof -// - namespace []byte -func (_e *MockDA_Expecter) Validate(ctx interface{}, ids interface{}, proofs interface{}, namespace interface{}) *MockDA_Validate_Call { - return &MockDA_Validate_Call{Call: _e.mock.On("Validate", ctx, ids, proofs, namespace)} -} - -func (_c *MockDA_Validate_Call) Run(run func(ctx context.Context, ids []da.ID, proofs []da.Proof, namespace []byte)) *MockDA_Validate_Call { - _c.Call.Run(func(args mock.Arguments) { - var arg0 context.Context - if args[0] != nil { - arg0 = args[0].(context.Context) - } - var arg1 []da.ID - if args[1] != nil { - arg1 = args[1].([]da.ID) - } - var arg2 []da.Proof - if args[2] != nil { - arg2 = args[2].([]da.Proof) - } - var arg3 []byte - if args[3] != nil { - arg3 = args[3].([]byte) - } - run( - arg0, - arg1, - arg2, - arg3, - ) - }) - return _c -} - -func (_c *MockDA_Validate_Call) Return(bools []bool, err error) *MockDA_Validate_Call { - _c.Call.Return(bools, err) - return _c -} - -func (_c *MockDA_Validate_Call) RunAndReturn(run func(ctx context.Context, ids []da.ID, proofs []da.Proof, namespace []byte) ([]bool, error)) *MockDA_Validate_Call { - _c.Call.Return(run) - return _c -} diff --git a/da/jsonrpc/client.go b/da/jsonrpc/client.go deleted file mode 100644 index 9803ebcd49..0000000000 --- a/da/jsonrpc/client.go +++ /dev/null @@ -1,241 +0,0 @@ -package jsonrpc - -import ( - "context" - "encoding/hex" - "fmt" - "net/http" - "strings" - - "github.com/filecoin-project/go-jsonrpc" - "github.com/rs/zerolog" - - "github.com/evstack/ev-node/core/da" -) - -//go:generate mockgen -destination=mocks/api.go -package=mocks . Module -type Module interface { - da.DA -} - -// API defines the jsonrpc service module API -type API struct { - Logger zerolog.Logger - MaxBlobSize uint64 - Internal struct { - Get func(ctx context.Context, ids []da.ID, ns []byte) ([]da.Blob, error) `perm:"read"` - GetIDs func(ctx context.Context, height uint64, ns []byte) (*da.GetIDsResult, error) `perm:"read"` - GetProofs func(ctx context.Context, ids []da.ID, ns []byte) ([]da.Proof, error) `perm:"read"` - Commit func(ctx context.Context, blobs []da.Blob, ns []byte) ([]da.Commitment, error) `perm:"read"` - Validate func(context.Context, []da.ID, []da.Proof, []byte) ([]bool, error) `perm:"read"` - Submit func(context.Context, []da.Blob, float64, []byte) ([]da.ID, error) `perm:"write"` - SubmitWithOptions func(context.Context, []da.Blob, float64, []byte, []byte) ([]da.ID, error) `perm:"write"` - } -} - -// Get returns Blob for each given ID, or an error. -func (api *API) Get(ctx context.Context, ids []da.ID, ns []byte) ([]da.Blob, error) { - api.Logger.Debug().Str("method", "Get").Int("num_ids", len(ids)).Str("namespace", hex.EncodeToString(ns)).Msg("Making RPC call") - res, err := api.Internal.Get(ctx, ids, ns) - if err != nil { - if strings.Contains(err.Error(), context.Canceled.Error()) { - api.Logger.Debug().Str("method", "Get").Msg("RPC call canceled due to context cancellation") - return res, context.Canceled - } - api.Logger.Error().Err(err).Str("method", "Get").Msg("RPC call failed") - // Wrap error for context, potentially using the translated error from the RPC library - return nil, fmt.Errorf("failed to get blobs: %w", err) - } - api.Logger.Debug().Str("method", "Get").Int("num_blobs_returned", len(res)).Msg("RPC call successful") - return res, nil -} - -// GetIDs returns IDs of all Blobs located in DA at given height. -func (api *API) GetIDs(ctx context.Context, height uint64, ns []byte) (*da.GetIDsResult, error) { - api.Logger.Debug().Str("method", "GetIDs").Uint64("height", height).Str("namespace", hex.EncodeToString(ns)).Msg("Making RPC call") - res, err := api.Internal.GetIDs(ctx, height, ns) - if err != nil { - // Using strings.contains since JSON RPC serialization doesn't preserve error wrapping - // Check if the error is specifically BlobNotFound, otherwise log and return - if strings.Contains(err.Error(), da.ErrBlobNotFound.Error()) { // Use the error variable directly - api.Logger.Debug().Str("method", "GetIDs").Uint64("height", height).Msg("RPC call indicates blobs not found") - return nil, err // Return the specific ErrBlobNotFound - } - if strings.Contains(err.Error(), da.ErrHeightFromFuture.Error()) { - api.Logger.Debug().Str("method", "GetIDs").Uint64("height", height).Msg("RPC call indicates height from future") - return nil, err // Return the specific ErrHeightFromFuture - } - if strings.Contains(err.Error(), context.Canceled.Error()) { - api.Logger.Debug().Str("method", "GetIDs").Msg("RPC call canceled due to context cancellation") - return res, context.Canceled - } - api.Logger.Error().Err(err).Str("method", "GetIDs").Msg("RPC call failed") - return nil, err - } - - // Handle cases where the RPC call succeeds but returns no IDs - if res == nil || len(res.IDs) == 0 { - api.Logger.Debug().Str("method", "GetIDs").Uint64("height", height).Msg("RPC call successful but no IDs found") - return nil, da.ErrBlobNotFound // Return specific error for not found (use variable directly) - } - - api.Logger.Debug().Str("method", "GetIDs").Msg("RPC call successful") - return res, nil -} - -// GetProofs returns inclusion Proofs for Blobs specified by their IDs. -func (api *API) GetProofs(ctx context.Context, ids []da.ID, ns []byte) ([]da.Proof, error) { - api.Logger.Debug().Str("method", "GetProofs").Int("num_ids", len(ids)).Str("namespace", hex.EncodeToString(ns)).Msg("Making RPC call") - res, err := api.Internal.GetProofs(ctx, ids, ns) - if err != nil { - api.Logger.Error().Err(err).Str("method", "GetProofs").Msg("RPC call failed") - } else { - api.Logger.Debug().Str("method", "GetProofs").Int("num_proofs_returned", len(res)).Msg("RPC call successful") - } - return res, err -} - -// Commit creates a Commitment for each given Blob. -func (api *API) Commit(ctx context.Context, blobs []da.Blob, ns []byte) ([]da.Commitment, error) { - api.Logger.Debug().Str("method", "Commit").Int("num_blobs", len(blobs)).Str("namespace", hex.EncodeToString(ns)).Msg("Making RPC call") - res, err := api.Internal.Commit(ctx, blobs, ns) - if err != nil { - api.Logger.Error().Err(err).Str("method", "Commit").Msg("RPC call failed") - } else { - api.Logger.Debug().Str("method", "Commit").Int("num_commitments_returned", len(res)).Msg("RPC call successful") - } - return res, err -} - -// Validate validates Commitments against the corresponding Proofs. This should be possible without retrieving the Blobs. -func (api *API) Validate(ctx context.Context, ids []da.ID, proofs []da.Proof, ns []byte) ([]bool, error) { - api.Logger.Debug().Str("method", "Validate").Int("num_ids", len(ids)).Int("num_proofs", len(proofs)).Str("namespace", hex.EncodeToString(ns)).Msg("Making RPC call") - res, err := api.Internal.Validate(ctx, ids, proofs, ns) - if err != nil { - api.Logger.Error().Err(err).Str("method", "Validate").Msg("RPC call failed") - } else { - api.Logger.Debug().Str("method", "Validate").Int("num_results_returned", len(res)).Msg("RPC call successful") - } - return res, err -} - -// Submit submits the Blobs to Data Availability layer. -func (api *API) Submit(ctx context.Context, blobs []da.Blob, gasPrice float64, ns []byte) ([]da.ID, error) { - api.Logger.Debug().Str("method", "Submit").Int("num_blobs", len(blobs)).Float64("gas_price", gasPrice).Str("namespace", hex.EncodeToString(ns)).Msg("Making RPC call") - res, err := api.Internal.Submit(ctx, blobs, gasPrice, ns) - if err != nil { - if strings.Contains(err.Error(), context.Canceled.Error()) { - api.Logger.Debug().Str("method", "Submit").Msg("RPC call canceled due to context cancellation") - return res, context.Canceled - } - api.Logger.Error().Err(err).Str("method", "Submit").Bytes("namespace", ns).Msg("RPC call failed") - } else { - api.Logger.Debug().Str("method", "Submit").Int("num_ids_returned", len(res)).Msg("RPC call successful") - } - return res, err -} - -// SubmitWithOptions submits the Blobs to Data Availability layer with additional options. -// It validates the entire batch against MaxBlobSize before submission. -// If any blob or the total batch size exceeds limits, it returns ErrBlobSizeOverLimit. -func (api *API) SubmitWithOptions(ctx context.Context, inputBlobs []da.Blob, gasPrice float64, ns []byte, options []byte) ([]da.ID, error) { - maxBlobSize := api.MaxBlobSize - - if len(inputBlobs) == 0 { - return []da.ID{}, nil - } - - // Validate each blob individually and calculate total size - var totalSize uint64 - for i, blob := range inputBlobs { - blobLen := uint64(len(blob)) - if blobLen > maxBlobSize { - api.Logger.Warn().Int("index", i).Uint64("blobSize", blobLen).Uint64("maxBlobSize", maxBlobSize).Msg("Individual blob exceeds MaxBlobSize") - return nil, da.ErrBlobSizeOverLimit - } - totalSize += blobLen - } - - // Validate total batch size - if totalSize > maxBlobSize { - return nil, da.ErrBlobSizeOverLimit - } - - api.Logger.Debug().Str("method", "SubmitWithOptions").Int("num_blobs", len(inputBlobs)).Uint64("total_size", totalSize).Float64("gas_price", gasPrice).Str("namespace", hex.EncodeToString(ns)).Msg("Making RPC call") - res, err := api.Internal.SubmitWithOptions(ctx, inputBlobs, gasPrice, ns, options) - if err != nil { - if strings.Contains(err.Error(), context.Canceled.Error()) { - api.Logger.Debug().Str("method", "SubmitWithOptions").Msg("RPC call canceled due to context cancellation") - return res, context.Canceled - } - api.Logger.Error().Err(err).Str("method", "SubmitWithOptions").Msg("RPC call failed") - } else { - api.Logger.Debug().Str("method", "SubmitWithOptions").Int("num_ids_returned", len(res)).Msg("RPC call successful") - } - - return res, err -} - -// Client is the jsonrpc client -type Client struct { - DA API - closer multiClientCloser -} - -// multiClientCloser is a wrapper struct to close clients across multiple namespaces. -type multiClientCloser struct { - closers []jsonrpc.ClientCloser -} - -// register adds a new closer to the multiClientCloser -func (m *multiClientCloser) register(closer jsonrpc.ClientCloser) { - m.closers = append(m.closers, closer) -} - -// closeAll closes all saved clients. -func (m *multiClientCloser) closeAll() { - for _, closer := range m.closers { - closer() - } -} - -// Close closes the connections to all namespaces registered on the staticClient. -func (c *Client) Close() { - c.closer.closeAll() -} - -// NewClient creates a new Client with one connection per namespace with the -// given token as the authorization token. -func NewClient(ctx context.Context, logger zerolog.Logger, addr, token string, maxBlobSize uint64) (*Client, error) { - authHeader := http.Header{"Authorization": []string{fmt.Sprintf("Bearer %s", token)}} - return newClient(ctx, logger, addr, authHeader, maxBlobSize) -} - -func newClient(ctx context.Context, logger zerolog.Logger, addr string, authHeader http.Header, maxBlobSize uint64) (*Client, error) { - var multiCloser multiClientCloser - var client Client - client.DA.Logger = logger - client.DA.MaxBlobSize = maxBlobSize - - errs := getKnownErrorsMapping() - for name, module := range moduleMap(&client) { - closer, err := jsonrpc.NewMergeClient(ctx, addr, name, []interface{}{module}, authHeader, jsonrpc.WithErrors(errs)) - if err != nil { - // If an error occurs, close any previously opened connections - multiCloser.closeAll() - return nil, err - } - multiCloser.register(closer) - } - - client.closer = multiCloser // Assign the multiCloser to the client - - return &client, nil -} - -func moduleMap(client *Client) map[string]interface{} { - // TODO: this duplication of strings many times across the codebase can be avoided with issue #1176 - return map[string]interface{}{ - "da": &client.DA.Internal, - } -} diff --git a/da/jsonrpc/client_test.go b/da/jsonrpc/client_test.go deleted file mode 100644 index af32882ea9..0000000000 --- a/da/jsonrpc/client_test.go +++ /dev/null @@ -1,125 +0,0 @@ -package jsonrpc - -import ( - "context" - "testing" - - "github.com/rs/zerolog" - "github.com/stretchr/testify/assert" - - "github.com/evstack/ev-node/core/da" -) - -// TestSubmitWithOptions_SizeValidation tests the corrected behavior of SubmitWithOptions -// where it validates the entire batch before submission and returns ErrBlobSizeOverLimit -// if the batch is too large, instead of silently dropping blobs. -func TestSubmitWithOptions_SizeValidation(t *testing.T) { - logger := zerolog.Nop() - - testCases := []struct { - name string - maxBlobSize uint64 - inputBlobs []da.Blob - expectError bool - expectedError error - description string - }{ - { - name: "Empty input", - maxBlobSize: 1000, - inputBlobs: []da.Blob{}, - expectError: false, - description: "Empty input should return empty result without error", - }, - { - name: "Single blob within limit", - maxBlobSize: 1000, - inputBlobs: []da.Blob{make([]byte, 500)}, - expectError: false, - description: "Single blob smaller than limit should succeed", - }, - { - name: "Single blob exceeds limit", - maxBlobSize: 1000, - inputBlobs: []da.Blob{make([]byte, 1500)}, - expectError: true, - expectedError: da.ErrBlobSizeOverLimit, - description: "Single blob larger than limit should fail", - }, - { - name: "Multiple blobs within limit", - maxBlobSize: 1000, - inputBlobs: []da.Blob{make([]byte, 300), make([]byte, 400), make([]byte, 200)}, - expectError: false, - description: "Multiple blobs totaling less than limit should succeed", - }, - { - name: "Multiple blobs exceed total limit", - maxBlobSize: 1000, - inputBlobs: []da.Blob{make([]byte, 400), make([]byte, 400), make([]byte, 400)}, - expectError: true, - expectedError: da.ErrBlobSizeOverLimit, - description: "Multiple blobs totaling more than limit should fail completely", - }, - { - name: "Mixed: some blobs fit, total exceeds limit", - maxBlobSize: 1000, - inputBlobs: []da.Blob{make([]byte, 100), make([]byte, 200), make([]byte, 800)}, - expectError: true, - expectedError: da.ErrBlobSizeOverLimit, - description: "Should fail completely, not partially submit blobs that fit", - }, - { - name: "One blob exceeds limit individually", - maxBlobSize: 1000, - inputBlobs: []da.Blob{make([]byte, 300), make([]byte, 1500), make([]byte, 200)}, - expectError: true, - expectedError: da.ErrBlobSizeOverLimit, - description: "Should fail if any individual blob exceeds limit", - }, - } - - for _, tc := range testCases { - t.Run(tc.name, func(t *testing.T) { - // Create API with test configuration - api := &API{ - Logger: logger, - MaxBlobSize: tc.maxBlobSize, - } - - // Mock the Internal.SubmitWithOptions to always succeed if called - // This tests that our validation logic works before reaching the actual RPC call - mockCalled := false - api.Internal.SubmitWithOptions = func(ctx context.Context, blobs []da.Blob, gasPrice float64, namespace []byte, options []byte) ([]da.ID, error) { - mockCalled = true - // Return mock IDs for successful submissions - ids := make([]da.ID, len(blobs)) - for i := range blobs { - ids[i] = []byte{byte(i)} - } - return ids, nil - } - - // Call SubmitWithOptions - ctx := context.Background() - result, err := api.SubmitWithOptions(ctx, tc.inputBlobs, 1.0, []byte("test"), nil) - - // Verify expectations - if tc.expectError { - assert.Error(t, err, tc.description) - if tc.expectedError != nil { - assert.ErrorIs(t, err, tc.expectedError, tc.description) - } - assert.Nil(t, result, "Result should be nil on error") - assert.False(t, mockCalled, "Internal RPC should not be called when validation fails") - } else { - assert.NoError(t, err, tc.description) - assert.NotNil(t, result, "Result should not be nil on success") - if len(tc.inputBlobs) > 0 { - assert.True(t, mockCalled, "Internal RPC should be called for valid submissions") - assert.Len(t, result, len(tc.inputBlobs), "Should return IDs for all submitted blobs") - } - } - }) - } -} diff --git a/da/jsonrpc/errors.go b/da/jsonrpc/errors.go deleted file mode 100644 index c81040e899..0000000000 --- a/da/jsonrpc/errors.go +++ /dev/null @@ -1,21 +0,0 @@ -package jsonrpc - -import ( - "github.com/filecoin-project/go-jsonrpc" - - coreda "github.com/evstack/ev-node/core/da" -) - -// getKnownErrorsMapping returns a mapping of known error codes to their corresponding error types. -func getKnownErrorsMapping() jsonrpc.Errors { - errs := jsonrpc.NewErrors() - errs.Register(jsonrpc.ErrorCode(coreda.StatusNotFound), &coreda.ErrBlobNotFound) - errs.Register(jsonrpc.ErrorCode(coreda.StatusTooBig), &coreda.ErrBlobSizeOverLimit) - errs.Register(jsonrpc.ErrorCode(coreda.StatusContextDeadline), &coreda.ErrTxTimedOut) - errs.Register(jsonrpc.ErrorCode(coreda.StatusAlreadyInMempool), &coreda.ErrTxAlreadyInMempool) - errs.Register(jsonrpc.ErrorCode(coreda.StatusIncorrectAccountSequence), &coreda.ErrTxIncorrectAccountSequence) - errs.Register(jsonrpc.ErrorCode(coreda.StatusContextDeadline), &coreda.ErrContextDeadline) - errs.Register(jsonrpc.ErrorCode(coreda.StatusContextCanceled), &coreda.ErrContextCanceled) - errs.Register(jsonrpc.ErrorCode(coreda.StatusHeightFromFuture), &coreda.ErrHeightFromFuture) - return errs -} diff --git a/da/jsonrpc/proxy_test.go b/da/jsonrpc/proxy_test.go deleted file mode 100644 index 1ab623c037..0000000000 --- a/da/jsonrpc/proxy_test.go +++ /dev/null @@ -1,351 +0,0 @@ -package jsonrpc_test - -import ( - "bytes" - "context" - "errors" - "fmt" - "strings" - "sync" - "testing" - "time" - - "github.com/evstack/ev-node/da/internal/mocks" - proxy "github.com/evstack/ev-node/da/jsonrpc" - "github.com/rs/zerolog" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/mock" - "github.com/stretchr/testify/require" - - coreda "github.com/evstack/ev-node/core/da" -) - -const ( - // ServerHost is the listen host for the test JSONRPC server - ServerHost = "localhost" - // ServerPort is the listen port for the test JSONRPC server - ServerPort = "3450" - // ClientURL is the url to dial for the test JSONRPC client - ClientURL = "http://localhost:3450" - - testMaxBlobSize = 100 - - DefaultMaxBlobSize = 2 * 1024 * 1024 // 2MB -) - -// testNamespace is a 15-byte namespace that will be hex encoded to 30 chars and truncated to 29 -var testNamespace = []byte("test-namespace1") - -// TestProxy runs the go-da DA test suite against the JSONRPC service -// NOTE: This test requires a test JSONRPC service to run on the port -// 3450 which is chosen to be sufficiently distinct from the default port - -func getTestDABlockTime() time.Duration { - return 100 * time.Millisecond -} - -func TestProxy(t *testing.T) { - dummy := coreda.NewDummyDA(100_000, getTestDABlockTime()) - dummy.StartHeightTicker() - logger := zerolog.Nop() - server := proxy.NewServer(logger, ServerHost, ServerPort, dummy) - err := server.Start(context.Background()) - require.NoError(t, err) - defer func() { - if err := server.Stop(context.Background()); err != nil { - require.NoError(t, err) - } - }() - - client, err := proxy.NewClient(context.Background(), logger, ClientURL, "74657374", DefaultMaxBlobSize) - require.NoError(t, err) - - t.Run("Basic DA test", func(t *testing.T) { - BasicDATest(t, &client.DA) - }) - t.Run("Get IDs and all data", func(t *testing.T) { - GetIDsTest(t, &client.DA) - }) - t.Run("Check Errors", func(t *testing.T) { - CheckErrors(t, &client.DA) - }) - t.Run("Concurrent read/write test", func(t *testing.T) { - ConcurrentReadWriteTest(t, &client.DA) - }) - t.Run("Given height is from the future", func(t *testing.T) { - HeightFromFutureTest(t, &client.DA) - }) - dummy.StopHeightTicker() -} - -// BasicDATest tests round trip of messages to DA and back. -func BasicDATest(t *testing.T, d coreda.DA) { - msg1 := []byte("message 1") - msg2 := []byte("message 2") - - ctx := t.Context() - id1, err := d.Submit(ctx, []coreda.Blob{msg1}, 0, testNamespace) - assert.NoError(t, err) - assert.NotEmpty(t, id1) - - id2, err := d.Submit(ctx, []coreda.Blob{msg2}, 0, testNamespace) - assert.NoError(t, err) - assert.NotEmpty(t, id2) - - time.Sleep(getTestDABlockTime()) - - id3, err := d.SubmitWithOptions(ctx, []coreda.Blob{msg1}, 0, testNamespace, []byte("random options")) - assert.NoError(t, err) - assert.NotEmpty(t, id3) - - assert.NotEqual(t, id1, id2) - assert.NotEqual(t, id1, id3) - - ret, err := d.Get(ctx, id1, testNamespace) - assert.NoError(t, err) - assert.Equal(t, []coreda.Blob{msg1}, ret) - - commitment1, err := d.Commit(ctx, []coreda.Blob{msg1}, []byte{}) - assert.NoError(t, err) - assert.NotEmpty(t, commitment1) - - commitment2, err := d.Commit(ctx, []coreda.Blob{msg2}, []byte{}) - assert.NoError(t, err) - assert.NotEmpty(t, commitment2) - - ids := []coreda.ID{id1[0], id2[0], id3[0]} - proofs, err := d.GetProofs(ctx, ids, testNamespace) - assert.NoError(t, err) - assert.NotEmpty(t, proofs) - oks, err := d.Validate(ctx, ids, proofs, testNamespace) - assert.NoError(t, err) - assert.NotEmpty(t, oks) - for _, ok := range oks { - assert.True(t, ok) - } -} - -// CheckErrors ensures that errors are handled properly by DA. -func CheckErrors(t *testing.T, d coreda.DA) { - ctx := t.Context() - blob, err := d.Get(ctx, []coreda.ID{[]byte("invalid blob id")}, testNamespace) - assert.Error(t, err) - assert.ErrorContains(t, err, coreda.ErrBlobNotFound.Error()) - assert.Empty(t, blob) -} - -// GetIDsTest tests iteration over DA -func GetIDsTest(t *testing.T, d coreda.DA) { - msgs := []coreda.Blob{[]byte("msg1"), []byte("msg2"), []byte("msg3")} - - ctx := t.Context() - ids, err := d.Submit(ctx, msgs, 0, testNamespace) - time.Sleep(getTestDABlockTime()) - assert.NoError(t, err) - assert.Len(t, ids, len(msgs)) - found := false - end := time.Now().Add(1 * time.Second) - - // To Keep It Simple: we assume working with DA used exclusively for this test (mock, devnet, etc) - // As we're the only user, we don't need to handle external data (that could be submitted in real world). - // There is no notion of height, so we need to scan the DA to get test data back. - for i := uint64(1); !found && !time.Now().After(end); i++ { - ret, err := d.GetIDs(ctx, i, testNamespace) - if err != nil { - if strings.Contains(err.Error(), coreda.ErrHeightFromFuture.Error()) { - break - } - t.Error("failed to get IDs:", err) - } - assert.NotNil(t, ret) - assert.NotZero(t, ret.Timestamp) - if len(ret.IDs) > 0 { - blobs, err := d.Get(ctx, ret.IDs, testNamespace) - assert.NoError(t, err) - - // Submit ensures atomicity of batch, so it makes sense to compare actual blobs (bodies) only when lengths - // of slices is the same. - if len(blobs) >= len(msgs) { - found = true - for _, msg := range msgs { - msgFound := false - for _, blob := range blobs { - if bytes.Equal(blob, msg) { - msgFound = true - break - } - } - if !msgFound { - found = false - break - } - } - } - } - } - - assert.True(t, found) -} - -// ConcurrentReadWriteTest tests the use of mutex lock in DummyDA by calling separate methods that use `d.data` and making sure there's no race conditions -func ConcurrentReadWriteTest(t *testing.T, d coreda.DA) { - var wg sync.WaitGroup - ctx, cancel := context.WithTimeout(t.Context(), 5*time.Second) - defer cancel() - - writeDone := make(chan struct{}) - - wg.Add(1) - go func() { - defer wg.Done() - for i := uint64(1); i <= 50; i++ { - _, err := d.Submit(ctx, []coreda.Blob{[]byte(fmt.Sprintf("test-%d", i))}, 0, []byte("test")) - assert.NoError(t, err) - } - close(writeDone) - }() - - wg.Add(1) - go func() { - defer wg.Done() - for { - select { - case <-writeDone: - return - default: - _, _ = d.GetIDs(ctx, 0, []byte("test")) - } - } - }() - - wg.Wait() -} - -// HeightFromFutureTest tests the case when the given height is from the future -func HeightFromFutureTest(t *testing.T, d coreda.DA) { - ctx := t.Context() - _, err := d.GetIDs(ctx, 999999999, []byte("test")) - assert.Error(t, err) - // Specifically check if the error contains the error message ErrHeightFromFuture - assert.ErrorContains(t, err, coreda.ErrHeightFromFuture.Error()) -} - -// TestSubmitWithOptions tests the SubmitWithOptions method with various scenarios -func TestSubmitWithOptions(t *testing.T) { - ctx := context.Background() - testNamespace := "options_test" - // The client will convert the namespace string to a proper Celestia namespace - // using SHA256 hashing and version 0 format (1 version byte + 28 ID bytes) - namespace := coreda.NamespaceFromString(testNamespace) - encodedNamespace := namespace.Bytes() - testOptions := []byte("test_options") - gasPrice := 0.0 - - // Helper function to create a client with a mocked internal API - createMockedClient := func(internalAPI *mocks.MockDA) *proxy.Client { - client := &proxy.Client{} - client.DA.Internal.SubmitWithOptions = internalAPI.SubmitWithOptions - client.DA.MaxBlobSize = testMaxBlobSize - client.DA.Logger = zerolog.Nop() - // Test verbosity no longer needed with Nop logger - return client - } - - t.Run("Happy Path - All blobs fit", func(t *testing.T) { - mockAPI := mocks.NewMockDA(t) - client := createMockedClient(mockAPI) - - blobs := []coreda.Blob{[]byte("blob1"), []byte("blob2")} - expectedIDs := []coreda.ID{[]byte("id1"), []byte("id2")} - - mockAPI.On("SubmitWithOptions", ctx, blobs, gasPrice, encodedNamespace, testOptions).Return(expectedIDs, nil).Once() - - ids, err := client.DA.SubmitWithOptions(ctx, blobs, gasPrice, encodedNamespace, testOptions) - - require.NoError(t, err) - assert.Equal(t, expectedIDs, ids) - mockAPI.AssertExpectations(t) - }) - - t.Run("Single Blob Too Large", func(t *testing.T) { - mockAPI := mocks.NewMockDA(t) - client := createMockedClient(mockAPI) - - largerBlob := make([]byte, testMaxBlobSize+1) - blobs := []coreda.Blob{largerBlob, []byte("this blob is definitely too large")} - - _, err := client.DA.SubmitWithOptions(ctx, blobs, gasPrice, encodedNamespace, testOptions) - - require.Error(t, err) - mockAPI.AssertExpectations(t) - }) - - t.Run("Total Size Exceeded", func(t *testing.T) { - mockAPI := mocks.NewMockDA(t) - client := createMockedClient(mockAPI) - - blobsizes := make([]byte, testMaxBlobSize/3) - blobsizesOver := make([]byte, testMaxBlobSize) - - blobs := []coreda.Blob{blobsizes, blobsizes, blobsizesOver} - - ids, err := client.DA.SubmitWithOptions(ctx, blobs, gasPrice, encodedNamespace, testOptions) - - require.Error(t, err) - assert.ErrorIs(t, err, coreda.ErrBlobSizeOverLimit) - assert.Nil(t, ids) - - // Should not call internal RPC when validation fails - mockAPI.AssertNotCalled(t, "SubmitWithOptions", mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything) - mockAPI.AssertExpectations(t) - }) - - t.Run("First Blob Too Large", func(t *testing.T) { - mockAPI := mocks.NewMockDA(t) - client := createMockedClient(mockAPI) - - largerBlob := make([]byte, testMaxBlobSize+1) - blobs := []coreda.Blob{largerBlob, []byte("small")} - - ids, err := client.DA.SubmitWithOptions(ctx, blobs, gasPrice, encodedNamespace, testOptions) - - require.Error(t, err) - assert.ErrorIs(t, err, coreda.ErrBlobSizeOverLimit) - assert.Nil(t, ids) - - mockAPI.AssertNotCalled(t, "SubmitWithOptions", mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything) - mockAPI.AssertExpectations(t) - }) - - t.Run("Empty Input Blobs", func(t *testing.T) { - mockAPI := mocks.NewMockDA(t) - client := createMockedClient(mockAPI) - - var blobs []coreda.Blob - - ids, err := client.DA.SubmitWithOptions(ctx, blobs, gasPrice, encodedNamespace, testOptions) - - require.NoError(t, err) - assert.Empty(t, ids) - - mockAPI.AssertNotCalled(t, "SubmitWithOptions", mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything) - mockAPI.AssertExpectations(t) - }) - - t.Run("Error During SubmitWithOptions RPC", func(t *testing.T) { - mockAPI := mocks.NewMockDA(t) - client := createMockedClient(mockAPI) - - blobs := []coreda.Blob{[]byte("blob1")} - expectedError := errors.New("rpc submit failed") - - mockAPI.On("SubmitWithOptions", ctx, blobs, gasPrice, encodedNamespace, testOptions).Return(nil, expectedError).Once() - - ids, err := client.DA.SubmitWithOptions(ctx, blobs, gasPrice, encodedNamespace, testOptions) - - require.Error(t, err) - assert.ErrorIs(t, err, expectedError) - assert.Nil(t, ids) - mockAPI.AssertExpectations(t) - }) -} diff --git a/da/jsonrpc/server.go b/da/jsonrpc/server.go deleted file mode 100644 index 456eefe908..0000000000 --- a/da/jsonrpc/server.go +++ /dev/null @@ -1,135 +0,0 @@ -package jsonrpc - -import ( - "context" - "net" - "net/http" - "sync/atomic" - "time" - - "github.com/filecoin-project/go-jsonrpc" - "github.com/rs/zerolog" - - "github.com/evstack/ev-node/core/da" -) - -// Server is a jsonrpc service that can serve the DA interface -type Server struct { - logger zerolog.Logger - srv *http.Server - rpc *jsonrpc.RPCServer - listener net.Listener - daImpl da.DA - - started atomic.Bool -} - -// serverInternalAPI provides the actual RPC methods. -type serverInternalAPI struct { - logger zerolog.Logger - daImpl da.DA -} - -// Get implements the RPC method. -func (s *serverInternalAPI) Get(ctx context.Context, ids []da.ID, ns []byte) ([]da.Blob, error) { - s.logger.Debug().Int("num_ids", len(ids)).Str("namespace", string(ns)).Msg("RPC server: Get called") - return s.daImpl.Get(ctx, ids, ns) -} - -// GetIDs implements the RPC method. -func (s *serverInternalAPI) GetIDs(ctx context.Context, height uint64, ns []byte) (*da.GetIDsResult, error) { - s.logger.Debug().Uint64("height", height).Str("namespace", string(ns)).Msg("RPC server: GetIDs called") - return s.daImpl.GetIDs(ctx, height, ns) -} - -// GetProofs implements the RPC method. -func (s *serverInternalAPI) GetProofs(ctx context.Context, ids []da.ID, ns []byte) ([]da.Proof, error) { - s.logger.Debug().Int("num_ids", len(ids)).Str("namespace", string(ns)).Msg("RPC server: GetProofs called") - return s.daImpl.GetProofs(ctx, ids, ns) -} - -// Commit implements the RPC method. -func (s *serverInternalAPI) Commit(ctx context.Context, blobs []da.Blob, ns []byte) ([]da.Commitment, error) { - s.logger.Debug().Int("num_blobs", len(blobs)).Str("namespace", string(ns)).Msg("RPC server: Commit called") - return s.daImpl.Commit(ctx, blobs, ns) -} - -// Validate implements the RPC method. -func (s *serverInternalAPI) Validate(ctx context.Context, ids []da.ID, proofs []da.Proof, ns []byte) ([]bool, error) { - s.logger.Debug().Int("num_ids", len(ids)).Int("num_proofs", len(proofs)).Str("namespace", string(ns)).Msg("RPC server: Validate called") - return s.daImpl.Validate(ctx, ids, proofs, ns) -} - -// Submit implements the RPC method. This is the primary submit method which includes options. -func (s *serverInternalAPI) Submit(ctx context.Context, blobs []da.Blob, gasPrice float64, ns []byte) ([]da.ID, error) { - s.logger.Debug().Int("num_blobs", len(blobs)).Float64("gas_price", gasPrice).Str("namespace", string(ns)).Msg("RPC server: Submit called") - return s.daImpl.Submit(ctx, blobs, gasPrice, ns) -} - -// SubmitWithOptions implements the RPC method. -func (s *serverInternalAPI) SubmitWithOptions(ctx context.Context, blobs []da.Blob, gasPrice float64, ns []byte, options []byte) ([]da.ID, error) { - s.logger.Debug().Int("num_blobs", len(blobs)).Float64("gas_price", gasPrice).Str("namespace", string(ns)).Str("options", string(options)).Msg("RPC server: SubmitWithOptions called") - return s.daImpl.SubmitWithOptions(ctx, blobs, gasPrice, ns, options) -} - -// NewServer accepts the host address port and the DA implementation to serve as a jsonrpc service -func NewServer(logger zerolog.Logger, address, port string, daImplementation da.DA) *Server { - rpc := jsonrpc.NewServer(jsonrpc.WithServerErrors(getKnownErrorsMapping())) - srv := &Server{ - rpc: rpc, - logger: logger, - daImpl: daImplementation, - srv: &http.Server{ - Addr: address + ":" + port, - ReadHeaderTimeout: 2 * time.Second, - }, - } - srv.srv.Handler = http.HandlerFunc(rpc.ServeHTTP) - - apiHandler := &serverInternalAPI{ - logger: logger, - daImpl: daImplementation, - } - - srv.rpc.Register("da", apiHandler) - return srv -} - -// Start starts the RPC Server. -// This function can be called multiple times concurrently -// Once started, subsequent calls are a no-op -func (s *Server) Start(context.Context) error { - couldStart := s.started.CompareAndSwap(false, true) - - if !couldStart { - s.logger.Warn().Msg("cannot start server: already started") - return nil - } - listener, err := net.Listen("tcp", s.srv.Addr) - if err != nil { - return err - } - s.listener = listener - s.logger.Info().Str("listening_on", s.srv.Addr).Msg("server started") - //nolint:errcheck - go s.srv.Serve(listener) - return nil -} - -// Stop stops the RPC Server. -// This function can be called multiple times concurrently -// Once stopped, subsequent calls are a no-op -func (s *Server) Stop(ctx context.Context) error { - couldStop := s.started.CompareAndSwap(true, false) - if !couldStop { - s.logger.Warn().Msg("cannot stop server: already stopped") - return nil - } - err := s.srv.Shutdown(ctx) - if err != nil { - return err - } - s.listener = nil - s.logger.Info().Msg("server stopped") - return nil -} diff --git a/docs/adr/adr-019-forced-inclusion-mechanism.md b/docs/adr/adr-019-forced-inclusion-mechanism.md index d589263f2c..ec025222e6 100644 --- a/docs/adr/adr-019-forced-inclusion-mechanism.md +++ b/docs/adr/adr-019-forced-inclusion-mechanism.md @@ -561,34 +561,6 @@ _Example 6: High Chain Activity (Extended Grace Period)_ - Result: Better operational reliability when block space is scarce ``` -#### Size Validation and Max Bytes Handling - -Both sequencers enforce strict size limits to prevent DoS and ensure batches never exceed the DA layer's limits: - -```go -// Size validation utilities -const AbsoluteMaxBlobSize = 1.5 * 1024 * 1024 // 1.5MB DA layer limit - -// ValidateBlobSize checks against absolute DA layer limit -func ValidateBlobSize(blob []byte) bool { - return uint64(len(blob)) <= AbsoluteMaxBlobSize -} - -// WouldExceedCumulativeSize checks against per-batch limit -func WouldExceedCumulativeSize(currentSize int, blobSize int, maxBytes uint64) bool { - return uint64(currentSize)+uint64(blobSize) > maxBytes -} -``` - -**Key Behaviors**: - -- **Absolute validation**: Blobs exceeding 2MB are permanently rejected -- **Batch size limits**: `req.MaxBytes` is NEVER exceeded in any batch -- **Transaction preservation**: - - Single sequencer: Trimmed batch txs returned to queue via `Prepend()` - - Based sequencer: Excess txs remain in `txQueue` for next batch - - Forced txs that don't fit go to `pendingForcedInclusionTxs` (single) or stay in `txQueue` (based) - #### Transaction Queue Management The based sequencer uses a simplified queue to handle transactions: diff --git a/docs/guides/da/local-da.md b/docs/guides/da/local-da.md index 740cfa4aca..912724c52e 100644 --- a/docs/guides/da/local-da.md +++ b/docs/guides/da/local-da.md @@ -7,7 +7,7 @@ import constants from '../../.vitepress/constants/constants.js' ## Introduction {#introduction} -This tutorial serves as a comprehensive guide for using the [local-da](https://github.com/evstack/ev-node/tree/main/da/cmd/local-da) with your chain. +This tutorial serves as a comprehensive guide for using the [local-da](../../../tools/local-da) with your chain. Before proceeding, ensure that you have completed the [build a chain](../gm-world.md) tutorial, which covers setting-up, building and running your chain. @@ -16,7 +16,7 @@ Before proceeding, ensure that you have completed the [build a chain](../gm-worl To set up a local DA network node on your machine, run the following script to install and start the local DA node: ```bash-vue -go install github.com/evstack/ev-node/da/cmd/local-da@latest +go install github.com/evstack/ev-node/tools/local-da@latest ``` This script will build and run the node, which will then listen on port `7980`. @@ -53,4 +53,4 @@ You should see the following log message indicating that your chain is connected ## Summary -By following these steps, you will set up a local DA network node and configure your chain to post data to it. This setup is useful for testing and development in a controlled environment. You can find more information on running the local-da binary [here](https://github.com/evstack/ev-node/blob/main/da/cmd/local-da/README.md) +By following these steps, you will set up a local DA network node and configure your chain to post data to it. This setup is useful for testing and development in a controlled environment. You can find more information in the [local-da README](../../../tools/local-da/README.md) diff --git a/docs/guides/full-node.md b/docs/guides/full-node.md index 0022f65082..753985033e 100644 --- a/docs/guides/full-node.md +++ b/docs/guides/full-node.md @@ -4,7 +4,7 @@ This guide covers how to set up a full node to run alongside a sequencer node in a Evolve-based blockchain network. A full node maintains a complete copy of the blockchain and helps validate transactions, improving the network's decentralization and security. -> **Note: The guide on how to run an evolve EVM full node can be found [in the evm section](./evm/single#setting-up-a-full-node).** +> **Note: The guide on how to run an evolve EVM full node can be found [in the evm section](./evm/single.md#setting-up-a-full-node).** ## Prerequisites @@ -41,7 +41,7 @@ cp $HOME/.$CHAIN_ID/config/genesis.json $HOME/.${CHAIN_ID}_fn/config/genesis.jso Identify the sequencer node's P2P address from its logs. It will look similar to: -``` +```text 1:55PM INF listening on address=/ip4/127.0.0.1/tcp/7676/p2p/12D3KooWJbD9TQoMSSSUyfhHMmgVY3LqCjxYFz8wQ92Qa6DAqtmh module=p2p ``` diff --git a/docs/guides/gm-world.md b/docs/guides/gm-world.md index 4ff7d24330..e28d746c68 100644 --- a/docs/guides/gm-world.md +++ b/docs/guides/gm-world.md @@ -106,7 +106,7 @@ First lets start the local DA network: ```bash cd gm -go tool github.com/evstack/ev-node/da/cmd/local-da +go run github.com/evstack/ev-node/tools/local-da ``` you should see logs like: diff --git a/docs/guides/quick-start.md b/docs/guides/quick-start.md index 691278f774..4d6bf012c3 100644 --- a/docs/guides/quick-start.md +++ b/docs/guides/quick-start.md @@ -57,7 +57,7 @@ Now that we have our testapp generated and installed, we can launch our chain al First lets start the local DA network: ```bash -go install github.com/evstack/ev-node/da/cmd/local-da@latest +go install github.com/evstack/ev-node/tools/local-da@latest local-da ``` diff --git a/docs/guides/use-tia-for-gas.md b/docs/guides/use-tia-for-gas.md index 24a3d77097..baa4d3a000 100644 --- a/docs/guides/use-tia-for-gas.md +++ b/docs/guides/use-tia-for-gas.md @@ -27,7 +27,7 @@ import constants from '../.vitepress/constants/constants.js' Your local DA network is already running if you followed the [quick start guide](./quick-start.md) or the [build a chain](./gm-world.md). If not, you can start it with the following command: ```bash -go install github.com/evstack/ev-node/da/cmd/local-da@latest +go install github.com/evstack/ev-node/tools/local-da@latest local-da ``` diff --git a/docs/learn/config.md b/docs/learn/config.md index 5d7de4103b..89d9248591 100644 --- a/docs/learn/config.md +++ b/docs/learn/config.md @@ -490,24 +490,6 @@ _Example:_ `--rollkit.da.mempool_ttl 30` _Default:_ `20` _Constant:_ `FlagDAMempoolTTL` -### DA Retrieve Batch Size - -**Description:** -Number of blob IDs requested per DA `Get` call when the node retrieves blocks from the DA layer. Smaller batches help unreliable DA RPC endpoints return data before the per-request timeout, while larger batches reduce the total number of round trips for fast DA nodes. - -**YAML:** - -```yaml -da: - retrieve_batch_size: 100 -``` - -**Command-line Flag:** -`--rollkit.da.retrieve_batch_size ` -_Example:_ `--rollkit.da.retrieve_batch_size 50` -_Default:_ `100` -_Constant:_ `FlagDARetrieveBatchSize` - ### DA Request Timeout **Description:** diff --git a/docs/learn/data-availability.md b/docs/learn/data-availability.md index 0a744ef945..93f1483cad 100644 --- a/docs/learn/data-availability.md +++ b/docs/learn/data-availability.md @@ -17,7 +17,7 @@ Evolve is designed to be DA-agnostic, meaning it can integrate with different da - **External Data Availability Layer (DA Interface):** - Used for production and secure deployments. - - Evolve can post block data to any external DA layer that implements the Evolve [DA interface](https://github.com/evstack/ev-node/blob/main/core/da/da.go#L11) (e.g., Celestia). + - Evolve can post block data to any external DA layer that implements the Evolve [DA client interface](https://github.com/evstack/ev-node/blob/main/block/public.go) (e.g., Celestia). - Anyone can verify that the data is available and reconstruct the chain state, depending on the guarantees of the chosen DA layer. ## Best Practices diff --git a/docs/learn/specs/block-manager.md b/docs/learn/specs/block-manager.md index ac47d47ab6..c97171f90e 100644 --- a/docs/learn/specs/block-manager.md +++ b/docs/learn/specs/block-manager.md @@ -167,8 +167,7 @@ The block components share a common configuration: | Namespace | da.Namespace | DA namespace ID for block submissions (deprecated, use HeaderNamespace and DataNamespace instead) | | HeaderNamespace | string | namespace ID for submitting headers to DA layer (automatically encoded by the node) | | DataNamespace | string | namespace ID for submitting data to DA layer (automatically encoded by the node) | -| RetrieveBatchSize | int | number of blob IDs fetched per DA `Get` call, trading off payload size vs. number of RPC round trips (default: 100) | -| RequestTimeout | duration | per-request timeout for DA `GetIDs`/`Get` calls; higher values tolerate slow DA nodes, lower values fail faster (default: 30s) | +| RequestTimeout | duration | per-request timeout for DA `GetIDs`/`Get` calls; higher values tolerate slow DA nodes, lower values fail faster (default: 30s) | ### Block Production (Executor Component) @@ -746,7 +745,7 @@ See [tutorial] for running a multi-node network with both aggregator and non-agg [9] [Lazy Aggregation with DA Layer Consistency ADR](../../adr/adr-021-lazy-aggregation.md) [defaultBlockTime]: https://github.com/evstack/ev-node/blob/main/pkg/config/defaults.go#L50 -[defaultDABlockTime]:https://github.com/evstack/ev-node/blob/main/pkg/config/defaults.go#L59 +[defaultDABlockTime]: https://github.com/evstack/ev-node/blob/main/pkg/config/defaults.go#L59 [defaultLazyBlockTime]: https://github.com/evstack/ev-node/blob/main/pkg/config/defaults.go#L52 [go-header]: https://github.com/celestiaorg/go-header [block-sync]: https://github.com/evstack/ev-node/blob/main/pkg/sync/sync_service.go diff --git a/docs/learn/specs/da.md b/docs/learn/specs/da.md index d9f5ce5da7..481a433852 100644 --- a/docs/learn/specs/da.md +++ b/docs/learn/specs/da.md @@ -59,5 +59,5 @@ The separation of headers and data into different namespaces provides several ad [2] [jsonrpc][jsonrpc] -[da-interface]: https://github.com/evstack/ev-node/blob/main/core/da/da.go#L11 -[jsonrpc]: https://github.com/evstack/ev-node/tree/main/da/jsonrpc +[da-interface]: https://github.com/evstack/ev-node/blob/main/block/public.go +[jsonrpc]: https://github.com/evstack/ev-node/tree/main/pkg/da/jsonrpc diff --git a/docs/learn/specs/full_node.md b/docs/learn/specs/full_node.md index b6196ffd10..f909536b50 100644 --- a/docs/learn/specs/full_node.md +++ b/docs/learn/specs/full_node.md @@ -102,6 +102,6 @@ See [full node] [Store]: https://github.com/evstack/ev-node/blob/main/pkg/store/store.go [store interface]: https://github.com/evstack/ev-node/blob/main/pkg/store/types.go [Block Components]: https://github.com/evstack/ev-node/blob/main/block/components.go -[dalc]: https://github.com/evstack/ev-node/blob/main/core/da/da.go +[dalc]: https://github.com/evstack/ev-node/blob/main/block/public.go [Header Sync Service]: https://github.com/evstack/ev-node/blob/main/pkg/sync/sync_service.go [Data Sync Service]: https://github.com/evstack/ev-node/blob/main/pkg/sync/sync_service.go diff --git a/go.mod b/go.mod index 6375309c42..9da24886a4 100644 --- a/go.mod +++ b/go.mod @@ -36,9 +36,6 @@ require ( gotest.tools/v3 v3.5.2 ) -// for reviewer, this module is planned to be removed in part 3 -replace github.com/evstack/ev-node/da => ./da - require ( github.com/benbjohnson/clock v1.3.5 // indirect github.com/beorn7/perks v1.0.1 // indirect diff --git a/node/full.go b/node/full.go index 6d03a87c04..7af73ad474 100644 --- a/node/full.go +++ b/node/full.go @@ -18,7 +18,6 @@ import ( "github.com/evstack/ev-node/block" - coreda "github.com/evstack/ev-node/core/da" coreexecutor "github.com/evstack/ev-node/core/execution" coresequencer "github.com/evstack/ev-node/core/sequencer" "github.com/evstack/ev-node/pkg/config" @@ -53,7 +52,7 @@ type FullNode struct { nodeConfig config.Config - da coreda.DA + daClient block.DAClient p2pClient *p2p.Client hSyncService *evsync.HeaderSyncService @@ -75,7 +74,7 @@ func newFullNode( database ds.Batching, exec coreexecutor.Executor, sequencer coresequencer.Sequencer, - da coreda.DA, + daClient block.DAClient, metricsProvider MetricsProvider, logger zerolog.Logger, nodeOpts NodeOptions, @@ -105,7 +104,7 @@ func newFullNode( rktStore, exec, sequencer, - da, + daClient, signer, headerSyncService, dataSyncService, @@ -119,7 +118,7 @@ func newFullNode( genesis, rktStore, exec, - da, + daClient, headerSyncService, dataSyncService, logger, @@ -136,7 +135,7 @@ func newFullNode( nodeConfig: nodeConfig, p2pClient: p2pClient, blockComponents: blockComponents, - da: da, + daClient: daClient, Store: rktStore, hSyncService: headerSyncService, dSyncService: dataSyncService, diff --git a/node/full_node.md b/node/full_node.md index f1f96e6e31..198422cd90 100644 --- a/node/full_node.md +++ b/node/full_node.md @@ -91,6 +91,6 @@ See [full node] [Store]: https://github.com/evstack/ev-node/blob/main/pkg/store/store.go [store interface]: https://github.com/evstack/ev-node/blob/main/pkg/store/types.go [Block Components]: https://github.com/evstack/ev-node/blob/main/block/components.go -[dalc]: https://github.com/evstack/ev-node/blob/main/core/da/da.go +[dalc]: https://github.com/evstack/ev-node/blob/main/block/public.go [Header Sync Service]: https://github.com/evstack/ev-node/blob/main/pkg/sync/sync_service.go [Block Sync Service]: https://github.com/evstack/ev-node/blob/main/pkg/sync/sync_service.go diff --git a/node/full_node_integration_test.go b/node/full_node_integration_test.go index c13d6dc0d7..c0aaf50506 100644 --- a/node/full_node_integration_test.go +++ b/node/full_node_integration_test.go @@ -125,7 +125,7 @@ func TestTxGossipingMultipleNodesDAIncluded(t *testing.T) { } } - blocksToWaitFor := uint64(5) + blocksToWaitFor := uint64(4) // Wait for all nodes to reach at least blocksToWaitFor blocks with DA inclusion for _, nodeItem := range nodes { requireEmptyChan(t, errChan) diff --git a/node/helpers_test.go b/node/helpers_test.go index e77744a4ec..9d586c3d24 100644 --- a/node/helpers_test.go +++ b/node/helpers_test.go @@ -11,16 +11,16 @@ import ( "time" testutils "github.com/celestiaorg/utils/test" + "github.com/evstack/ev-node/block" + coreexecutor "github.com/evstack/ev-node/core/execution" + coresequencer "github.com/evstack/ev-node/core/sequencer" + "github.com/evstack/ev-node/test/testda" "github.com/ipfs/go-datastore" dssync "github.com/ipfs/go-datastore/sync" "github.com/libp2p/go-libp2p/core/peer" "github.com/rs/zerolog" "github.com/stretchr/testify/require" - coreda "github.com/evstack/ev-node/core/da" - coreexecutor "github.com/evstack/ev-node/core/execution" - coresequencer "github.com/evstack/ev-node/core/sequencer" - evconfig "github.com/evstack/ev-node/pkg/config" "github.com/evstack/ev-node/pkg/p2p" "github.com/evstack/ev-node/pkg/p2p/key" @@ -29,29 +29,49 @@ import ( ) const ( - // MockDAAddress is the address used by the mock gRPC service + // TestDAAddress is the address used by the dummy gRPC service // NOTE: this should be unique per test package to avoid // "bind: listen address already in use" because multiple packages // are tested in parallel - MockDAAddress = "grpc://localhost:7990" + TestDAAddress = "grpc://localhost:7990" - // MockDANamespace is a sample namespace used by the mock DA client - MockDANamespace = "mock-namespace" + // TestDANamespace is a sample namespace used by the dummy DA client + TestDANamespace = "mock-namespace" // MockExecutorAddress is a sample address used by the mock executor MockExecutorAddress = "127.0.0.1:40041" ) -// createTestComponents creates test components for node initialization -func createTestComponents(t *testing.T, config evconfig.Config) (coreexecutor.Executor, coresequencer.Sequencer, coreda.DA, *p2p.Client, datastore.Batching, *key.NodeKey, func()) { - executor := coreexecutor.NewDummyExecutor() - sequencer := coresequencer.NewDummySequencer() - dummyDA := coreda.NewDummyDA(100_000, config.DA.BlockTime.Duration) - dummyDA.StartHeightTicker() +// sharedDummyDA is a shared DummyDA instance for multi-node tests. +var ( + sharedDummyDA *testda.DummyDA + sharedDummyDAOnce sync.Once +) + +func getSharedDummyDA(maxBlobSize uint64) *testda.DummyDA { + sharedDummyDAOnce.Do(func() { + sharedDummyDA = testda.New(testda.WithMaxBlobSize(maxBlobSize)) + }) + return sharedDummyDA +} + +func resetSharedDummyDA() { + if sharedDummyDA != nil { + sharedDummyDA.Reset() + } +} - stopDAHeightTicker := func() { - dummyDA.StopHeightTicker() +func newDummyDAClient(maxBlobSize uint64) *testda.DummyDA { + if maxBlobSize == 0 { + maxBlobSize = testda.DefaultMaxBlobSize } + return getSharedDummyDA(maxBlobSize) +} + +func createTestComponents(t *testing.T, config evconfig.Config) (coreexecutor.Executor, coresequencer.Sequencer, block.DAClient, *p2p.Client, datastore.Batching, *key.NodeKey, func()) { + executor := coreexecutor.NewDummyExecutor() + sequencer := coresequencer.NewDummySequencer() + daClient := newDummyDAClient(0) // Create genesis and keys for P2P client _, genesisValidatorKey, _ := types.GetGenesisWithPrivkey("test-chain") @@ -65,7 +85,8 @@ func createTestComponents(t *testing.T, config evconfig.Config) (coreexecutor.Ex require.NotNil(t, p2pClient) ds := dssync.MutexWrap(datastore.NewMapDatastore()) - return executor, sequencer, dummyDA, p2pClient, ds, p2pKey, stopDAHeightTicker + stop := daClient.StartHeightTicker(config.DA.BlockTime.Duration) + return executor, sequencer, daClient, p2pClient, ds, p2pKey, stop } func getTestConfig(t *testing.T, n int) evconfig.Config { @@ -81,8 +102,8 @@ func getTestConfig(t *testing.T, n int) evconfig.Config { }, DA: evconfig.DAConfig{ BlockTime: evconfig.DurationWrapper{Duration: 200 * time.Millisecond}, - Address: MockDAAddress, - Namespace: MockDANamespace, + Address: TestDAAddress, + Namespace: TestDANamespace, MaxSubmitAttempts: 30, }, P2P: evconfig.P2PConfig{ @@ -101,7 +122,7 @@ func newTestNode( config evconfig.Config, executor coreexecutor.Executor, sequencer coresequencer.Sequencer, - dac coreda.DA, + daClient block.DAClient, p2pClient *p2p.Client, ds datastore.Batching, stopDAHeightTicker func(), @@ -115,7 +136,7 @@ func newTestNode( config, executor, sequencer, - dac, + daClient, remoteSigner, p2pClient, genesis, @@ -136,8 +157,9 @@ func newTestNode( } func createNodeWithCleanup(t *testing.T, config evconfig.Config) (*FullNode, func()) { - executor, sequencer, dac, p2pClient, ds, _, stopDAHeightTicker := createTestComponents(t, config) - return newTestNode(t, config, executor, sequencer, dac, p2pClient, ds, stopDAHeightTicker) + resetSharedDummyDA() + executor, sequencer, daClient, p2pClient, ds, _, stopDAHeightTicker := createTestComponents(t, config) + return newTestNode(t, config, executor, sequencer, daClient, p2pClient, ds, stopDAHeightTicker) } func createNodeWithCustomComponents( @@ -145,18 +167,19 @@ func createNodeWithCustomComponents( config evconfig.Config, executor coreexecutor.Executor, sequencer coresequencer.Sequencer, - dac coreda.DA, + daClient block.DAClient, p2pClient *p2p.Client, ds datastore.Batching, stopDAHeightTicker func(), ) (*FullNode, func()) { - return newTestNode(t, config, executor, sequencer, dac, p2pClient, ds, stopDAHeightTicker) + return newTestNode(t, config, executor, sequencer, daClient, p2pClient, ds, stopDAHeightTicker) } // Creates the given number of nodes the given nodes using the given wait group to synchronize them func createNodesWithCleanup(t *testing.T, num int, config evconfig.Config) ([]*FullNode, []func()) { t.Helper() require := require.New(t) + resetSharedDummyDA() nodes := make([]*FullNode, num) cleanups := make([]func(), num) @@ -168,7 +191,7 @@ func createNodesWithCleanup(t *testing.T, num int, config evconfig.Config) ([]*F aggListenAddress := config.P2P.ListenAddress aggPeers := config.P2P.Peers - executor, sequencer, dac, p2pClient, ds, aggP2PKey, stopDAHeightTicker := createTestComponents(t, config) + executor, sequencer, daClient, p2pClient, ds, aggP2PKey, stopDAHeightTicker := createTestComponents(t, config) aggPeerID, err := peer.IDFromPrivateKey(aggP2PKey.PrivKey) require.NoError(err) @@ -180,7 +203,7 @@ func createNodesWithCleanup(t *testing.T, num int, config evconfig.Config) ([]*F config, executor, sequencer, - dac, + daClient, remoteSigner, p2pClient, genesis, @@ -209,12 +232,12 @@ func createNodesWithCleanup(t *testing.T, num int, config evconfig.Config) ([]*F } config.P2P.ListenAddress = fmt.Sprintf("/ip4/127.0.0.1/tcp/%d", 40001+i) config.RPC.Address = fmt.Sprintf("127.0.0.1:%d", 8001+i) - executor, sequencer, _, p2pClient, _, nodeP2PKey, stopDAHeightTicker := createTestComponents(t, config) + executor, sequencer, daClient, p2pClient, _, nodeP2PKey, stopDAHeightTicker := createTestComponents(t, config) node, err := NewNode( config, executor, sequencer, - dac, + daClient, nil, p2pClient, genesis, diff --git a/node/node.go b/node/node.go index 4d780035aa..ca4bf15124 100644 --- a/node/node.go +++ b/node/node.go @@ -5,7 +5,6 @@ import ( "github.com/rs/zerolog" "github.com/evstack/ev-node/block" - coreda "github.com/evstack/ev-node/core/da" coreexecutor "github.com/evstack/ev-node/core/execution" coresequencer "github.com/evstack/ev-node/core/sequencer" "github.com/evstack/ev-node/pkg/config" @@ -33,7 +32,7 @@ func NewNode( conf config.Config, exec coreexecutor.Executor, sequencer coresequencer.Sequencer, - da coreda.DA, + daClient block.DAClient, signer signer.Signer, p2pClient *p2p.Client, genesis genesis.Genesis, @@ -58,7 +57,7 @@ func NewNode( database, exec, sequencer, - da, + daClient, metricsProvider, logger, nodeOptions, diff --git a/node/single_sequencer_integration_test.go b/node/single_sequencer_integration_test.go index 98dca09c71..754f8017ce 100644 --- a/node/single_sequencer_integration_test.go +++ b/node/single_sequencer_integration_test.go @@ -14,9 +14,9 @@ import ( "github.com/stretchr/testify/require" "github.com/stretchr/testify/suite" - coreda "github.com/evstack/ev-node/core/da" coreexecutor "github.com/evstack/ev-node/core/execution" evconfig "github.com/evstack/ev-node/pkg/config" + "github.com/evstack/ev-node/test/testda" ) // FullNodeTestSuite is a test suite for full node integration tests @@ -321,9 +321,9 @@ func TestBatchQueueThrottlingWithDAFailure(t *testing.T) { dummyExecutor, ok := executor.(*coreexecutor.DummyExecutor) require.True(ok, "Expected DummyExecutor implementation") - // Cast dummyDA to our enhanced version so we can make it fail - dummyDAImpl, ok := dummyDA.(*coreda.DummyDA) - require.True(ok, "Expected DummyDA implementation") + // Cast dummyDA to our test double so we can simulate failures + dummyDAImpl, ok := dummyDA.(*testda.DummyDA) + require.True(ok, "Expected testda.DummyDA implementation") // Create node with components node, cleanup := createNodeWithCustomComponents(t, config, executor, sequencer, dummyDAImpl, p2pClient, ds, func() {}) @@ -423,6 +423,8 @@ func waitForBlockN(t *testing.T, n uint64, node *FullNode, blockInterval time.Du func TestReadinessEndpointWhenBlockProductionStops(t *testing.T) { require := require.New(t) + httpClient := &http.Client{Timeout: 1 * time.Second} + config := getTestConfig(t, 1) config.Node.Aggregator = true config.Node.BlockTime = evconfig.DurationWrapper{Duration: 500 * time.Millisecond} @@ -440,10 +442,14 @@ func TestReadinessEndpointWhenBlockProductionStops(t *testing.T) { waitForBlockN(t, 1, node, config.Node.BlockTime.Duration) - resp, err := http.Get("http://" + config.RPC.Address + "/health/ready") - require.NoError(err) - require.Equal(http.StatusOK, resp.StatusCode, "Readiness should be READY while producing blocks") - resp.Body.Close() + require.Eventually(func() bool { + resp, err := httpClient.Get("http://" + config.RPC.Address + "/health/ready") + if err != nil { + return false + } + defer resp.Body.Close() + return resp.StatusCode == http.StatusOK + }, 10*time.Second, 100*time.Millisecond, "Readiness should be READY while producing blocks") time.Sleep(time.Duration(config.Node.MaxPendingHeadersAndData+2) * config.Node.BlockTime.Duration) @@ -452,7 +458,7 @@ func TestReadinessEndpointWhenBlockProductionStops(t *testing.T) { require.LessOrEqual(height, config.Node.MaxPendingHeadersAndData) require.Eventually(func() bool { - resp, err := http.Get("http://" + config.RPC.Address + "/health/ready") + resp, err := httpClient.Get("http://" + config.RPC.Address + "/health/ready") if err != nil { return false } diff --git a/pkg/cmd/run_node.go b/pkg/cmd/run_node.go index ed4584e480..40e8b1fc13 100644 --- a/pkg/cmd/run_node.go +++ b/pkg/cmd/run_node.go @@ -16,11 +16,12 @@ import ( "github.com/rs/zerolog" "github.com/spf13/cobra" - coreda "github.com/evstack/ev-node/core/da" + "github.com/evstack/ev-node/block" coreexecutor "github.com/evstack/ev-node/core/execution" coresequencer "github.com/evstack/ev-node/core/sequencer" "github.com/evstack/ev-node/node" rollconf "github.com/evstack/ev-node/pkg/config" + blobrpc "github.com/evstack/ev-node/pkg/da/jsonrpc" genesispkg "github.com/evstack/ev-node/pkg/genesis" "github.com/evstack/ev-node/pkg/p2p" "github.com/evstack/ev-node/pkg/signer" @@ -80,7 +81,6 @@ func StartNode( cmd *cobra.Command, executor coreexecutor.Executor, sequencer coresequencer.Sequencer, - da coreda.DA, p2pClient *p2p.Client, datastore datastore.Batching, nodeConfig rollconf.Config, @@ -90,6 +90,13 @@ func StartNode( ctx, cancel := context.WithCancel(cmd.Context()) defer cancel() + blobClient, err := blobrpc.NewClient(ctx, nodeConfig.DA.Address, nodeConfig.DA.AuthToken, "") + if err != nil { + return fmt.Errorf("failed to create blob client: %w", err) + } + defer blobClient.Close() + daClient := block.NewDAClient(blobClient, nodeConfig, logger) + // create a new remote signer var signer signer.Signer if nodeConfig.Signer.SignerType == "file" && (nodeConfig.Node.Aggregator && !nodeConfig.Node.BasedSequencer) { @@ -135,7 +142,7 @@ func StartNode( nodeConfig, executor, sequencer, - da, + daClient, signer, p2pClient, genesis, diff --git a/pkg/cmd/run_node_test.go b/pkg/cmd/run_node_test.go index 16430ee450..6ed94033af 100644 --- a/pkg/cmd/run_node_test.go +++ b/pkg/cmd/run_node_test.go @@ -8,12 +8,6 @@ import ( "testing" "time" - "github.com/ipfs/go-datastore" - "github.com/rs/zerolog" - "github.com/spf13/cobra" - "github.com/stretchr/testify/assert" - - coreda "github.com/evstack/ev-node/core/da" coreexecutor "github.com/evstack/ev-node/core/execution" coresequencer "github.com/evstack/ev-node/core/sequencer" "github.com/evstack/ev-node/node" @@ -22,18 +16,15 @@ import ( "github.com/evstack/ev-node/pkg/p2p" "github.com/evstack/ev-node/pkg/signer" filesigner "github.com/evstack/ev-node/pkg/signer/file" + "github.com/ipfs/go-datastore" + "github.com/rs/zerolog" + "github.com/spf13/cobra" + "github.com/stretchr/testify/assert" ) -const MockDANamespace = "test" - -func createTestComponents(_ context.Context, t *testing.T) (coreexecutor.Executor, coresequencer.Sequencer, coreda.DA, signer.Signer, *p2p.Client, datastore.Batching, func()) { +func createTestComponents(_ context.Context, t *testing.T) (coreexecutor.Executor, coresequencer.Sequencer, signer.Signer, *p2p.Client, datastore.Batching, func()) { executor := coreexecutor.NewDummyExecutor() sequencer := coresequencer.NewDummySequencer() - dummyDA := coreda.NewDummyDA(100_000, 10*time.Second) - dummyDA.StartHeightTicker() - stopDAHeightTicker := func() { - dummyDA.StopHeightTicker() - } tmpDir := t.TempDir() keyProvider, err := filesigner.CreateFileSystemSigner(filepath.Join(tmpDir, "config"), []byte{}) if err != nil { @@ -43,7 +34,7 @@ func createTestComponents(_ context.Context, t *testing.T) (coreexecutor.Executo p2pClient := &p2p.Client{} ds := datastore.NewMapDatastore() - return executor, sequencer, dummyDA, keyProvider, p2pClient, ds, stopDAHeightTicker + return executor, sequencer, keyProvider, p2pClient, ds, func() {} } func TestParseFlags(t *testing.T) { @@ -78,13 +69,13 @@ func TestParseFlags(t *testing.T) { args := append([]string{"start"}, flags...) - executor, sequencer, dac, keyProvider, p2pClient, ds, stopDAHeightTicker := createTestComponents(context.Background(), t) + executor, sequencer, keyProvider, p2pClient, ds, stopDAHeightTicker := createTestComponents(context.Background(), t) defer stopDAHeightTicker() nodeConfig := rollconf.DefaultConfig() nodeConfig.RootDir = t.TempDir() - newRunNodeCmd := newRunNodeCmd(t.Context(), executor, sequencer, dac, keyProvider, p2pClient, ds, nodeConfig) + newRunNodeCmd := newRunNodeCmd(t.Context(), executor, sequencer, keyProvider, p2pClient, ds, nodeConfig) _ = newRunNodeCmd.Flags().Set(rollconf.FlagRootDir, "custom/root/dir") if err := newRunNodeCmd.ParseFlags(args); err != nil { t.Errorf("Error: %v", err) @@ -153,13 +144,13 @@ func TestAggregatorFlagInvariants(t *testing.T) { for i, flags := range flagVariants { args := append([]string{"start"}, flags...) - executor, sequencer, dac, keyProvider, p2pClient, ds, stopDAHeightTicker := createTestComponents(context.Background(), t) + executor, sequencer, keyProvider, p2pClient, ds, stopDAHeightTicker := createTestComponents(context.Background(), t) defer stopDAHeightTicker() nodeConfig := rollconf.DefaultConfig() nodeConfig.RootDir = t.TempDir() - newRunNodeCmd := newRunNodeCmd(t.Context(), executor, sequencer, dac, keyProvider, p2pClient, ds, nodeConfig) + newRunNodeCmd := newRunNodeCmd(t.Context(), executor, sequencer, keyProvider, p2pClient, ds, nodeConfig) _ = newRunNodeCmd.Flags().Set(rollconf.FlagRootDir, "custom/root/dir") if err := newRunNodeCmd.ParseFlags(args); err != nil { @@ -189,13 +180,13 @@ func TestDefaultAggregatorValue(t *testing.T) { } for _, tc := range testCases { t.Run(tc.name, func(t *testing.T) { - executor, sequencer, dac, keyProvider, p2pClient, ds, stopDAHeightTicker := createTestComponents(context.Background(), t) + executor, sequencer, keyProvider, p2pClient, ds, stopDAHeightTicker := createTestComponents(context.Background(), t) defer stopDAHeightTicker() nodeConfig := rollconf.DefaultConfig() nodeConfig.RootDir = t.TempDir() - newRunNodeCmd := newRunNodeCmd(t.Context(), executor, sequencer, dac, keyProvider, p2pClient, ds, nodeConfig) + newRunNodeCmd := newRunNodeCmd(t.Context(), executor, sequencer, keyProvider, p2pClient, ds, nodeConfig) _ = newRunNodeCmd.Flags().Set(rollconf.FlagRootDir, "custom/root/dir") // Create a new command without specifying any flags @@ -271,10 +262,10 @@ func TestCentralizedAddresses(t *testing.T) { "--rollkit.da.address=http://central-da:26657", } - executor, sequencer, dac, keyProvider, p2pClient, ds, stopDAHeightTicker := createTestComponents(context.Background(), t) + executor, sequencer, keyProvider, p2pClient, ds, stopDAHeightTicker := createTestComponents(context.Background(), t) defer stopDAHeightTicker() - cmd := newRunNodeCmd(t.Context(), executor, sequencer, dac, keyProvider, p2pClient, ds, nodeConfig) + cmd := newRunNodeCmd(t.Context(), executor, sequencer, keyProvider, p2pClient, ds, nodeConfig) _ = cmd.Flags().Set(rollconf.FlagRootDir, "custom/root/dir") if err := cmd.ParseFlags(args); err != nil { t.Fatalf("ParseFlags error: %v", err) @@ -537,7 +528,7 @@ func TestStartNodeSignerPathResolution(t *testing.T) { func TestStartNodeErrors(t *testing.T) { baseCtx := context.Background() - executor, sequencer, dac, _, p2pClient, ds, stopDAHeightTicker := createTestComponents(baseCtx, t) + executor, sequencer, _, p2pClient, ds, stopDAHeightTicker := createTestComponents(baseCtx, t) defer stopDAHeightTicker() tmpDir := t.TempDir() @@ -643,7 +634,7 @@ func TestStartNodeErrors(t *testing.T) { dummySigner, _ := filesigner.CreateFileSystemSigner(dummySignerPath, []byte("password")) - cmd := newRunNodeCmd(baseCtx, executor, sequencer, dac, dummySigner, p2pClient, ds, nodeConfig) + cmd := newRunNodeCmd(baseCtx, executor, sequencer, dummySigner, p2pClient, ds, nodeConfig) cmd.SetContext(baseCtx) @@ -654,7 +645,7 @@ func TestStartNodeErrors(t *testing.T) { runFunc := func() { currentTestLogger := zerolog.Nop() - err := StartNode(currentTestLogger, cmd, executor, sequencer, dac, p2pClient, ds, nodeConfig, testGenesis, node.NodeOptions{}) + err := StartNode(currentTestLogger, cmd, executor, sequencer, p2pClient, ds, nodeConfig, testGenesis, node.NodeOptions{}) if tc.expectedError != "" { assert.ErrorContains(t, err, tc.expectedError) } else { @@ -671,7 +662,7 @@ func TestStartNodeErrors(t *testing.T) { } else { assert.NotPanics(t, runFunc) checkLogger := zerolog.Nop() - err := StartNode(checkLogger, cmd, executor, sequencer, dac, p2pClient, ds, nodeConfig, testGenesis, node.NodeOptions{}) + err := StartNode(checkLogger, cmd, executor, sequencer, p2pClient, ds, nodeConfig, testGenesis, node.NodeOptions{}) if tc.expectedError != "" { assert.ErrorContains(t, err, tc.expectedError) } @@ -685,7 +676,6 @@ func newRunNodeCmd( ctx context.Context, executor coreexecutor.Executor, sequencer coresequencer.Sequencer, - dac coreda.DA, remoteSigner signer.Signer, p2pClient *p2p.Client, datastore datastore.Batching, @@ -697,9 +687,6 @@ func newRunNodeCmd( if sequencer == nil { panic("sequencer cannot be nil") } - if dac == nil { - panic("da client cannot be nil") - } // Create a test genesis testGenesis := genesis.NewGenesis("test", 1, time.Now(), []byte{}) @@ -709,7 +696,7 @@ func newRunNodeCmd( Aliases: []string{"node", "run"}, Short: "Run the rollkit node", RunE: func(cmd *cobra.Command, args []string) error { - return StartNode(zerolog.Nop(), cmd, executor, sequencer, dac, p2pClient, datastore, nodeConfig, testGenesis, node.NodeOptions{}) + return StartNode(zerolog.Nop(), cmd, executor, sequencer, p2pClient, datastore, nodeConfig, testGenesis, node.NodeOptions{}) }, } diff --git a/pkg/config/config.go b/pkg/config/config.go index e392c31e40..81d10b2eb0 100644 --- a/pkg/config/config.go +++ b/pkg/config/config.go @@ -11,7 +11,7 @@ import ( "time" "github.com/celestiaorg/go-square/v3/share" - "github.com/evstack/ev-node/core/da" + datypes "github.com/evstack/ev-node/pkg/da/types" "github.com/mitchellh/mapstructure" "github.com/spf13/cobra" "github.com/spf13/pflag" @@ -74,8 +74,6 @@ const ( FlagDAMempoolTTL = FlagPrefixEvnode + "da.mempool_ttl" // FlagDAMaxSubmitAttempts is a flag for specifying the maximum DA submit attempts FlagDAMaxSubmitAttempts = FlagPrefixEvnode + "da.max_submit_attempts" - // FlagDARetrieveBatchSize configures how many IDs are fetched per DA Get request - FlagDARetrieveBatchSize = FlagPrefixEvnode + "da.retrieve_batch_size" // FlagDARequestTimeout controls the per-request timeout when talking to the DA layer FlagDARequestTimeout = FlagPrefixEvnode + "da.request_timeout" @@ -171,7 +169,6 @@ type DAConfig struct { BlockTime DurationWrapper `mapstructure:"block_time" yaml:"block_time" comment:"Average block time of the DA chain (duration). Determines frequency of DA layer syncing, maximum backoff time for retries, and is multiplied by MempoolTTL to calculate transaction expiration. Examples: \"15s\", \"30s\", \"1m\", \"2m30s\", \"10m\"."` MempoolTTL uint64 `mapstructure:"mempool_ttl" yaml:"mempool_ttl" comment:"Number of DA blocks after which a transaction is considered expired and dropped from the mempool. Controls retry backoff timing."` MaxSubmitAttempts int `mapstructure:"max_submit_attempts" yaml:"max_submit_attempts" comment:"Maximum number of attempts to submit data to the DA layer before giving up. Higher values provide more resilience but can delay error reporting."` - RetrieveBatchSize int `mapstructure:"retrieve_batch_size" yaml:"retrieve_batch_size" comment:"Number of IDs to request per DA Get call when retrieving blobs. Smaller batches lower per-request latency; larger batches reduce the number of RPC round trips."` RequestTimeout DurationWrapper `mapstructure:"request_timeout" yaml:"request_timeout" comment:"Per-request timeout applied to DA interactions. Larger values tolerate slower DA nodes at the cost of waiting longer before failing."` } @@ -289,7 +286,7 @@ func validateNamespace(namespace string) error { return fmt.Errorf("namespace cannot be empty") } - ns := da.NamespaceFromString(namespace) + ns := datypes.NamespaceFromString(namespace) if _, err := share.NewNamespaceFromBytes(ns.Bytes()); err != nil { return fmt.Errorf("could not validate namespace (%s): %w", namespace, err) } @@ -352,7 +349,6 @@ func AddFlags(cmd *cobra.Command) { cmd.Flags().StringSlice(FlagDASigningAddresses, def.DA.SigningAddresses, "Comma-separated list of addresses for DA submissions (used in round-robin)") cmd.Flags().Uint64(FlagDAMempoolTTL, def.DA.MempoolTTL, "number of DA blocks until transaction is dropped from the mempool") cmd.Flags().Int(FlagDAMaxSubmitAttempts, def.DA.MaxSubmitAttempts, "maximum number of attempts to submit data to the DA layer before giving up") - cmd.Flags().Int(FlagDARetrieveBatchSize, def.DA.RetrieveBatchSize, "number of IDs to request per DA Get call when retrieving blobs") cmd.Flags().Duration(FlagDARequestTimeout, def.DA.RequestTimeout.Duration, "per-request timeout when interacting with the DA layer") // P2P configuration flags diff --git a/pkg/config/config_test.go b/pkg/config/config_test.go index 41caabc22a..f7cfa653d9 100644 --- a/pkg/config/config_test.go +++ b/pkg/config/config_test.go @@ -26,7 +26,6 @@ func TestDefaultConfig(t *testing.T) { assert.Equal(t, "", def.DA.AuthToken) assert.Equal(t, "", def.DA.SubmitOptions) assert.NotEmpty(t, def.DA.Namespace) - assert.Equal(t, 100, def.DA.RetrieveBatchSize) assert.Equal(t, 60*time.Second, def.DA.RequestTimeout.Duration) assert.Equal(t, 1*time.Second, def.Node.BlockTime.Duration) assert.Equal(t, 6*time.Second, def.DA.BlockTime.Duration) @@ -76,7 +75,6 @@ func TestAddFlags(t *testing.T) { assertFlagValue(t, flags, FlagDASigningAddresses, DefaultConfig().DA.SigningAddresses) assertFlagValue(t, flags, FlagDAMempoolTTL, DefaultConfig().DA.MempoolTTL) assertFlagValue(t, flags, FlagDAMaxSubmitAttempts, DefaultConfig().DA.MaxSubmitAttempts) - assertFlagValue(t, flags, FlagDARetrieveBatchSize, DefaultConfig().DA.RetrieveBatchSize) assertFlagValue(t, flags, FlagDARequestTimeout, DefaultConfig().DA.RequestTimeout.Duration) // P2P flags @@ -109,7 +107,7 @@ func TestAddFlags(t *testing.T) { assertFlagValue(t, flags, FlagRPCEnableDAVisualization, DefaultConfig().RPC.EnableDAVisualization) // Count the number of flags we're explicitly checking - expectedFlagCount := 45 // Update this number if you add more flag checks above + expectedFlagCount := 44 // Update this number if you add more flag checks above // Get the actual number of flags (both regular and persistent) actualFlagCount := 0 diff --git a/pkg/config/defaults.go b/pkg/config/defaults.go index e3ddad31e0..78aef49e85 100644 --- a/pkg/config/defaults.go +++ b/pkg/config/defaults.go @@ -73,7 +73,6 @@ func DefaultConfig() Config { Address: "http://localhost:7980", BlockTime: DurationWrapper{6 * time.Second}, MaxSubmitAttempts: 30, - RetrieveBatchSize: 100, RequestTimeout: DurationWrapper{60 * time.Second}, Namespace: randString(10), DataNamespace: "", diff --git a/pkg/da/jsonrpc/README.md b/pkg/da/jsonrpc/README.md index 7440a6d483..80e2968864 100644 --- a/pkg/da/jsonrpc/README.md +++ b/pkg/da/jsonrpc/README.md @@ -7,7 +7,7 @@ This package is a **trimmed copy** of code from `celestia-node` to stay JSON-com - `blob.go` comes from `celestia-node/blob/blob.go` @ tag `v0.28.4` (release v0.28.4), with unused pieces removed (blob v1, proof helpers, share length calc, appconsts dependency, etc.). - `submit_options.go` mirrors the exported JSON fields of `celestia-node/state/tx_config.go` @ the same tag, leaving out functional options, defaults, and Cosmos keyring helpers. -## Why copy instead of import? +## Why copy instead of import - Avoids pulling Cosmos SDK / celestia-app dependencies into ev-node for the small surface we need (blob JSON and commitment for v0). - Keeps binary size and module graph smaller while remaining wire-compatible with celestia-node's blob service. diff --git a/pkg/da/jsonrpc/blob.go b/pkg/da/jsonrpc/blob.go index 67b94f9a7e..7a58c5c716 100644 --- a/pkg/da/jsonrpc/blob.go +++ b/pkg/da/jsonrpc/blob.go @@ -1,4 +1,4 @@ -package blob +package jsonrpc // NOTE: This file is a trimmed copy of celestia-node's blob/blob.go // at release v0.28.4 (commit tag v0.28.4). We keep only the JSON- @@ -24,9 +24,6 @@ type Commitment []byte // This mirrors celestia-node's blob.Proof shape. type Proof []*nmt.Proof -// DefaultMaxBlobSize is the default maximum blob size used by celestia-app (32 MiB). -const DefaultMaxBlobSize = 32 * 1_048_576 // bytes - // subtreeRootThreshold is copied from celestia-app/v6 appconsts.SubtreeRootThreshold. // It controls the branching factor when generating commitments. const subtreeRootThreshold = 64 @@ -45,12 +42,13 @@ type Blob struct { // NewBlobV0 builds a version 0 blob (the only version we currently need). func NewBlobV0(namespace libshare.Namespace, data []byte) (*Blob, error) { - return NewBlob(libshare.ShareVersionZero, namespace, data, nil) + return NewBlob(libshare.ShareVersionZero, namespace, data, nil, nil) } // NewBlob constructs a new blob from the provided namespace, data, signer, and share version. +// If commitment is provided, it is used directly; otherwise it is computed. // This is a lightly adapted copy of celestia-node/blob.NewBlob. -func NewBlob(shareVersion uint8, namespace libshare.Namespace, data, signer []byte) (*Blob, error) { +func NewBlob(shareVersion uint8, namespace libshare.Namespace, data, signer, commitment []byte) (*Blob, error) { if err := namespace.ValidateForBlob(); err != nil { return nil, fmt.Errorf("invalid namespace: %w", err) } @@ -60,9 +58,15 @@ func NewBlob(shareVersion uint8, namespace libshare.Namespace, data, signer []by return nil, fmt.Errorf("build blob: %w", err) } - com, err := inclusion.CreateCommitment(libBlob, merkle.HashFromByteSlices, subtreeRootThreshold) - if err != nil { - return nil, fmt.Errorf("create commitment: %w", err) + var com Commitment + if len(commitment) > 0 { + com = commitment + } else { + c, err := inclusion.CreateCommitment(libBlob, merkle.HashFromByteSlices, subtreeRootThreshold) + if err != nil { + return nil, fmt.Errorf("create commitment: %w", err) + } + com = c } return &Blob{ @@ -125,7 +129,7 @@ func (b *Blob) UnmarshalJSON(data []byte) error { return err } - blob, err := NewBlob(jb.ShareVersion, ns, jb.Data, jb.Signer) + blob, err := NewBlob(jb.ShareVersion, ns, jb.Data, jb.Signer, jb.Commitment) if err != nil { return err } diff --git a/pkg/da/jsonrpc/blob_test.go b/pkg/da/jsonrpc/blob_test.go index 2949fe4dd0..51d623c04c 100644 --- a/pkg/da/jsonrpc/blob_test.go +++ b/pkg/da/jsonrpc/blob_test.go @@ -1,4 +1,4 @@ -package blob +package jsonrpc import ( "encoding/json" diff --git a/pkg/da/jsonrpc/client.go b/pkg/da/jsonrpc/client.go index e6a52db97e..5e8fd25f47 100644 --- a/pkg/da/jsonrpc/client.go +++ b/pkg/da/jsonrpc/client.go @@ -1,4 +1,4 @@ -package blob +package jsonrpc import ( "context" diff --git a/pkg/da/jsonrpc/client_test.go b/pkg/da/jsonrpc/client_test.go index 5fb6d307c4..ca0f11a96f 100644 --- a/pkg/da/jsonrpc/client_test.go +++ b/pkg/da/jsonrpc/client_test.go @@ -1,4 +1,4 @@ -package blob_test +package jsonrpc_test import ( "context" @@ -10,8 +10,7 @@ import ( "github.com/stretchr/testify/require" libshare "github.com/celestiaorg/go-square/v3/share" - - blob "github.com/evstack/ev-node/pkg/da/jsonrpc" + blobrpc "github.com/evstack/ev-node/pkg/da/jsonrpc" "github.com/evstack/ev-node/pkg/da/jsonrpc/mocks" ) @@ -24,25 +23,25 @@ func newTestServer(t *testing.T, module any) *httptest.Server { func TestClient_CallsAreForwarded(t *testing.T) { ns := libshare.MustNewV0Namespace([]byte("namespace")) - blb, err := blob.NewBlobV0(ns, []byte("data")) + blb, err := blobrpc.NewBlobV0(ns, []byte("data")) require.NoError(t, err) module := mocks.NewMockBlobModule(t) module.On("Submit", mock.Anything, mock.Anything, mock.Anything).Return(uint64(7), nil) module.On("Get", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(blb, nil) - module.On("GetAll", mock.Anything, mock.Anything, mock.Anything).Return([]*blob.Blob{blb}, nil) - module.On("GetProof", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(&blob.Proof{}, nil) + module.On("GetAll", mock.Anything, mock.Anything, mock.Anything).Return([]*blobrpc.Blob{blb}, nil) + module.On("GetProof", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(&blobrpc.Proof{}, nil) module.On("Included", mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(true, nil) - module.On("GetCommitmentProof", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(&blob.CommitmentProof{}, nil) + module.On("GetCommitmentProof", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(&blobrpc.CommitmentProof{}, nil) srv := newTestServer(t, module) t.Cleanup(srv.Close) - client, err := blob.NewClient(context.Background(), srv.URL, "", "") + client, err := blobrpc.NewClient(context.Background(), srv.URL, "", "") require.NoError(t, err) t.Cleanup(client.Close) - height, err := client.Blob.Submit(context.Background(), []*blob.Blob{blb}, nil) + height, err := client.Blob.Submit(context.Background(), []*blobrpc.Blob{blb}, nil) require.NoError(t, err) require.Equal(t, uint64(7), height) diff --git a/pkg/da/jsonrpc/mocks/blob_module_mock.go b/pkg/da/jsonrpc/mocks/blob_module_mock.go index a3d0b9c644..f46dea6cc7 100644 --- a/pkg/da/jsonrpc/mocks/blob_module_mock.go +++ b/pkg/da/jsonrpc/mocks/blob_module_mock.go @@ -40,26 +40,26 @@ func (_m *MockBlobModule) EXPECT() *MockBlobModule_Expecter { } // Get provides a mock function for the type MockBlobModule -func (_mock *MockBlobModule) Get(context1 context.Context, v uint64, namespace share.Namespace, commitment blob.Commitment) (*blob.Blob, error) { +func (_mock *MockBlobModule) Get(context1 context.Context, v uint64, namespace share.Namespace, commitment jsonrpc.Commitment) (*jsonrpc.Blob, error) { ret := _mock.Called(context1, v, namespace, commitment) if len(ret) == 0 { panic("no return value specified for Get") } - var r0 *blob.Blob + var r0 *jsonrpc.Blob var r1 error - if returnFunc, ok := ret.Get(0).(func(context.Context, uint64, share.Namespace, blob.Commitment) (*blob.Blob, error)); ok { + if returnFunc, ok := ret.Get(0).(func(context.Context, uint64, share.Namespace, jsonrpc.Commitment) (*jsonrpc.Blob, error)); ok { return returnFunc(context1, v, namespace, commitment) } - if returnFunc, ok := ret.Get(0).(func(context.Context, uint64, share.Namespace, blob.Commitment) *blob.Blob); ok { + if returnFunc, ok := ret.Get(0).(func(context.Context, uint64, share.Namespace, jsonrpc.Commitment) *jsonrpc.Blob); ok { r0 = returnFunc(context1, v, namespace, commitment) } else { if ret.Get(0) != nil { - r0 = ret.Get(0).(*blob.Blob) + r0 = ret.Get(0).(*jsonrpc.Blob) } } - if returnFunc, ok := ret.Get(1).(func(context.Context, uint64, share.Namespace, blob.Commitment) error); ok { + if returnFunc, ok := ret.Get(1).(func(context.Context, uint64, share.Namespace, jsonrpc.Commitment) error); ok { r1 = returnFunc(context1, v, namespace, commitment) } else { r1 = ret.Error(1) @@ -76,12 +76,12 @@ type MockBlobModule_Get_Call struct { // - context1 context.Context // - v uint64 // - namespace share.Namespace -// - commitment blob.Commitment +// - commitment jsonrpc.Commitment func (_e *MockBlobModule_Expecter) Get(context1 interface{}, v interface{}, namespace interface{}, commitment interface{}) *MockBlobModule_Get_Call { return &MockBlobModule_Get_Call{Call: _e.mock.On("Get", context1, v, namespace, commitment)} } -func (_c *MockBlobModule_Get_Call) Run(run func(context1 context.Context, v uint64, namespace share.Namespace, commitment blob.Commitment)) *MockBlobModule_Get_Call { +func (_c *MockBlobModule_Get_Call) Run(run func(context1 context.Context, v uint64, namespace share.Namespace, commitment jsonrpc.Commitment)) *MockBlobModule_Get_Call { _c.Call.Run(func(args mock.Arguments) { var arg0 context.Context if args[0] != nil { @@ -95,9 +95,9 @@ func (_c *MockBlobModule_Get_Call) Run(run func(context1 context.Context, v uint if args[2] != nil { arg2 = args[2].(share.Namespace) } - var arg3 blob.Commitment + var arg3 jsonrpc.Commitment if args[3] != nil { - arg3 = args[3].(blob.Commitment) + arg3 = args[3].(jsonrpc.Commitment) } run( arg0, @@ -109,34 +109,34 @@ func (_c *MockBlobModule_Get_Call) Run(run func(context1 context.Context, v uint return _c } -func (_c *MockBlobModule_Get_Call) Return(blob1 *blob.Blob, err error) *MockBlobModule_Get_Call { - _c.Call.Return(blob1, err) +func (_c *MockBlobModule_Get_Call) Return(blob *jsonrpc.Blob, err error) *MockBlobModule_Get_Call { + _c.Call.Return(blob, err) return _c } -func (_c *MockBlobModule_Get_Call) RunAndReturn(run func(context1 context.Context, v uint64, namespace share.Namespace, commitment blob.Commitment) (*blob.Blob, error)) *MockBlobModule_Get_Call { +func (_c *MockBlobModule_Get_Call) RunAndReturn(run func(context1 context.Context, v uint64, namespace share.Namespace, commitment jsonrpc.Commitment) (*jsonrpc.Blob, error)) *MockBlobModule_Get_Call { _c.Call.Return(run) return _c } // GetAll provides a mock function for the type MockBlobModule -func (_mock *MockBlobModule) GetAll(context1 context.Context, v uint64, namespaces []share.Namespace) ([]*blob.Blob, error) { +func (_mock *MockBlobModule) GetAll(context1 context.Context, v uint64, namespaces []share.Namespace) ([]*jsonrpc.Blob, error) { ret := _mock.Called(context1, v, namespaces) if len(ret) == 0 { panic("no return value specified for GetAll") } - var r0 []*blob.Blob + var r0 []*jsonrpc.Blob var r1 error - if returnFunc, ok := ret.Get(0).(func(context.Context, uint64, []share.Namespace) ([]*blob.Blob, error)); ok { + if returnFunc, ok := ret.Get(0).(func(context.Context, uint64, []share.Namespace) ([]*jsonrpc.Blob, error)); ok { return returnFunc(context1, v, namespaces) } - if returnFunc, ok := ret.Get(0).(func(context.Context, uint64, []share.Namespace) []*blob.Blob); ok { + if returnFunc, ok := ret.Get(0).(func(context.Context, uint64, []share.Namespace) []*jsonrpc.Blob); ok { r0 = returnFunc(context1, v, namespaces) } else { if ret.Get(0) != nil { - r0 = ret.Get(0).([]*blob.Blob) + r0 = ret.Get(0).([]*jsonrpc.Blob) } } if returnFunc, ok := ret.Get(1).(func(context.Context, uint64, []share.Namespace) error); ok { @@ -183,34 +183,34 @@ func (_c *MockBlobModule_GetAll_Call) Run(run func(context1 context.Context, v u return _c } -func (_c *MockBlobModule_GetAll_Call) Return(blobs []*blob.Blob, err error) *MockBlobModule_GetAll_Call { +func (_c *MockBlobModule_GetAll_Call) Return(blobs []*jsonrpc.Blob, err error) *MockBlobModule_GetAll_Call { _c.Call.Return(blobs, err) return _c } -func (_c *MockBlobModule_GetAll_Call) RunAndReturn(run func(context1 context.Context, v uint64, namespaces []share.Namespace) ([]*blob.Blob, error)) *MockBlobModule_GetAll_Call { +func (_c *MockBlobModule_GetAll_Call) RunAndReturn(run func(context1 context.Context, v uint64, namespaces []share.Namespace) ([]*jsonrpc.Blob, error)) *MockBlobModule_GetAll_Call { _c.Call.Return(run) return _c } // GetCommitmentProof provides a mock function for the type MockBlobModule -func (_mock *MockBlobModule) GetCommitmentProof(context1 context.Context, v uint64, namespace share.Namespace, bytes []byte) (*blob.CommitmentProof, error) { +func (_mock *MockBlobModule) GetCommitmentProof(context1 context.Context, v uint64, namespace share.Namespace, bytes []byte) (*jsonrpc.CommitmentProof, error) { ret := _mock.Called(context1, v, namespace, bytes) if len(ret) == 0 { panic("no return value specified for GetCommitmentProof") } - var r0 *blob.CommitmentProof + var r0 *jsonrpc.CommitmentProof var r1 error - if returnFunc, ok := ret.Get(0).(func(context.Context, uint64, share.Namespace, []byte) (*blob.CommitmentProof, error)); ok { + if returnFunc, ok := ret.Get(0).(func(context.Context, uint64, share.Namespace, []byte) (*jsonrpc.CommitmentProof, error)); ok { return returnFunc(context1, v, namespace, bytes) } - if returnFunc, ok := ret.Get(0).(func(context.Context, uint64, share.Namespace, []byte) *blob.CommitmentProof); ok { + if returnFunc, ok := ret.Get(0).(func(context.Context, uint64, share.Namespace, []byte) *jsonrpc.CommitmentProof); ok { r0 = returnFunc(context1, v, namespace, bytes) } else { if ret.Get(0) != nil { - r0 = ret.Get(0).(*blob.CommitmentProof) + r0 = ret.Get(0).(*jsonrpc.CommitmentProof) } } if returnFunc, ok := ret.Get(1).(func(context.Context, uint64, share.Namespace, []byte) error); ok { @@ -263,37 +263,37 @@ func (_c *MockBlobModule_GetCommitmentProof_Call) Run(run func(context1 context. return _c } -func (_c *MockBlobModule_GetCommitmentProof_Call) Return(commitmentProof *blob.CommitmentProof, err error) *MockBlobModule_GetCommitmentProof_Call { +func (_c *MockBlobModule_GetCommitmentProof_Call) Return(commitmentProof *jsonrpc.CommitmentProof, err error) *MockBlobModule_GetCommitmentProof_Call { _c.Call.Return(commitmentProof, err) return _c } -func (_c *MockBlobModule_GetCommitmentProof_Call) RunAndReturn(run func(context1 context.Context, v uint64, namespace share.Namespace, bytes []byte) (*blob.CommitmentProof, error)) *MockBlobModule_GetCommitmentProof_Call { +func (_c *MockBlobModule_GetCommitmentProof_Call) RunAndReturn(run func(context1 context.Context, v uint64, namespace share.Namespace, bytes []byte) (*jsonrpc.CommitmentProof, error)) *MockBlobModule_GetCommitmentProof_Call { _c.Call.Return(run) return _c } // GetProof provides a mock function for the type MockBlobModule -func (_mock *MockBlobModule) GetProof(context1 context.Context, v uint64, namespace share.Namespace, commitment blob.Commitment) (*blob.Proof, error) { +func (_mock *MockBlobModule) GetProof(context1 context.Context, v uint64, namespace share.Namespace, commitment jsonrpc.Commitment) (*jsonrpc.Proof, error) { ret := _mock.Called(context1, v, namespace, commitment) if len(ret) == 0 { panic("no return value specified for GetProof") } - var r0 *blob.Proof + var r0 *jsonrpc.Proof var r1 error - if returnFunc, ok := ret.Get(0).(func(context.Context, uint64, share.Namespace, blob.Commitment) (*blob.Proof, error)); ok { + if returnFunc, ok := ret.Get(0).(func(context.Context, uint64, share.Namespace, jsonrpc.Commitment) (*jsonrpc.Proof, error)); ok { return returnFunc(context1, v, namespace, commitment) } - if returnFunc, ok := ret.Get(0).(func(context.Context, uint64, share.Namespace, blob.Commitment) *blob.Proof); ok { + if returnFunc, ok := ret.Get(0).(func(context.Context, uint64, share.Namespace, jsonrpc.Commitment) *jsonrpc.Proof); ok { r0 = returnFunc(context1, v, namespace, commitment) } else { if ret.Get(0) != nil { - r0 = ret.Get(0).(*blob.Proof) + r0 = ret.Get(0).(*jsonrpc.Proof) } } - if returnFunc, ok := ret.Get(1).(func(context.Context, uint64, share.Namespace, blob.Commitment) error); ok { + if returnFunc, ok := ret.Get(1).(func(context.Context, uint64, share.Namespace, jsonrpc.Commitment) error); ok { r1 = returnFunc(context1, v, namespace, commitment) } else { r1 = ret.Error(1) @@ -310,12 +310,12 @@ type MockBlobModule_GetProof_Call struct { // - context1 context.Context // - v uint64 // - namespace share.Namespace -// - commitment blob.Commitment +// - commitment jsonrpc.Commitment func (_e *MockBlobModule_Expecter) GetProof(context1 interface{}, v interface{}, namespace interface{}, commitment interface{}) *MockBlobModule_GetProof_Call { return &MockBlobModule_GetProof_Call{Call: _e.mock.On("GetProof", context1, v, namespace, commitment)} } -func (_c *MockBlobModule_GetProof_Call) Run(run func(context1 context.Context, v uint64, namespace share.Namespace, commitment blob.Commitment)) *MockBlobModule_GetProof_Call { +func (_c *MockBlobModule_GetProof_Call) Run(run func(context1 context.Context, v uint64, namespace share.Namespace, commitment jsonrpc.Commitment)) *MockBlobModule_GetProof_Call { _c.Call.Run(func(args mock.Arguments) { var arg0 context.Context if args[0] != nil { @@ -329,9 +329,9 @@ func (_c *MockBlobModule_GetProof_Call) Run(run func(context1 context.Context, v if args[2] != nil { arg2 = args[2].(share.Namespace) } - var arg3 blob.Commitment + var arg3 jsonrpc.Commitment if args[3] != nil { - arg3 = args[3].(blob.Commitment) + arg3 = args[3].(jsonrpc.Commitment) } run( arg0, @@ -343,18 +343,18 @@ func (_c *MockBlobModule_GetProof_Call) Run(run func(context1 context.Context, v return _c } -func (_c *MockBlobModule_GetProof_Call) Return(proof *blob.Proof, err error) *MockBlobModule_GetProof_Call { +func (_c *MockBlobModule_GetProof_Call) Return(proof *jsonrpc.Proof, err error) *MockBlobModule_GetProof_Call { _c.Call.Return(proof, err) return _c } -func (_c *MockBlobModule_GetProof_Call) RunAndReturn(run func(context1 context.Context, v uint64, namespace share.Namespace, commitment blob.Commitment) (*blob.Proof, error)) *MockBlobModule_GetProof_Call { +func (_c *MockBlobModule_GetProof_Call) RunAndReturn(run func(context1 context.Context, v uint64, namespace share.Namespace, commitment jsonrpc.Commitment) (*jsonrpc.Proof, error)) *MockBlobModule_GetProof_Call { _c.Call.Return(run) return _c } // Included provides a mock function for the type MockBlobModule -func (_mock *MockBlobModule) Included(context1 context.Context, v uint64, namespace share.Namespace, proof *blob.Proof, commitment blob.Commitment) (bool, error) { +func (_mock *MockBlobModule) Included(context1 context.Context, v uint64, namespace share.Namespace, proof *jsonrpc.Proof, commitment jsonrpc.Commitment) (bool, error) { ret := _mock.Called(context1, v, namespace, proof, commitment) if len(ret) == 0 { @@ -363,15 +363,15 @@ func (_mock *MockBlobModule) Included(context1 context.Context, v uint64, namesp var r0 bool var r1 error - if returnFunc, ok := ret.Get(0).(func(context.Context, uint64, share.Namespace, *blob.Proof, blob.Commitment) (bool, error)); ok { + if returnFunc, ok := ret.Get(0).(func(context.Context, uint64, share.Namespace, *jsonrpc.Proof, jsonrpc.Commitment) (bool, error)); ok { return returnFunc(context1, v, namespace, proof, commitment) } - if returnFunc, ok := ret.Get(0).(func(context.Context, uint64, share.Namespace, *blob.Proof, blob.Commitment) bool); ok { + if returnFunc, ok := ret.Get(0).(func(context.Context, uint64, share.Namespace, *jsonrpc.Proof, jsonrpc.Commitment) bool); ok { r0 = returnFunc(context1, v, namespace, proof, commitment) } else { r0 = ret.Get(0).(bool) } - if returnFunc, ok := ret.Get(1).(func(context.Context, uint64, share.Namespace, *blob.Proof, blob.Commitment) error); ok { + if returnFunc, ok := ret.Get(1).(func(context.Context, uint64, share.Namespace, *jsonrpc.Proof, jsonrpc.Commitment) error); ok { r1 = returnFunc(context1, v, namespace, proof, commitment) } else { r1 = ret.Error(1) @@ -388,13 +388,13 @@ type MockBlobModule_Included_Call struct { // - context1 context.Context // - v uint64 // - namespace share.Namespace -// - proof *blob.Proof -// - commitment blob.Commitment +// - proof *jsonrpc.Proof +// - commitment jsonrpc.Commitment func (_e *MockBlobModule_Expecter) Included(context1 interface{}, v interface{}, namespace interface{}, proof interface{}, commitment interface{}) *MockBlobModule_Included_Call { return &MockBlobModule_Included_Call{Call: _e.mock.On("Included", context1, v, namespace, proof, commitment)} } -func (_c *MockBlobModule_Included_Call) Run(run func(context1 context.Context, v uint64, namespace share.Namespace, proof *blob.Proof, commitment blob.Commitment)) *MockBlobModule_Included_Call { +func (_c *MockBlobModule_Included_Call) Run(run func(context1 context.Context, v uint64, namespace share.Namespace, proof *jsonrpc.Proof, commitment jsonrpc.Commitment)) *MockBlobModule_Included_Call { _c.Call.Run(func(args mock.Arguments) { var arg0 context.Context if args[0] != nil { @@ -408,13 +408,13 @@ func (_c *MockBlobModule_Included_Call) Run(run func(context1 context.Context, v if args[2] != nil { arg2 = args[2].(share.Namespace) } - var arg3 *blob.Proof + var arg3 *jsonrpc.Proof if args[3] != nil { - arg3 = args[3].(*blob.Proof) + arg3 = args[3].(*jsonrpc.Proof) } - var arg4 blob.Commitment + var arg4 jsonrpc.Commitment if args[4] != nil { - arg4 = args[4].(blob.Commitment) + arg4 = args[4].(jsonrpc.Commitment) } run( arg0, @@ -432,13 +432,13 @@ func (_c *MockBlobModule_Included_Call) Return(b bool, err error) *MockBlobModul return _c } -func (_c *MockBlobModule_Included_Call) RunAndReturn(run func(context1 context.Context, v uint64, namespace share.Namespace, proof *blob.Proof, commitment blob.Commitment) (bool, error)) *MockBlobModule_Included_Call { +func (_c *MockBlobModule_Included_Call) RunAndReturn(run func(context1 context.Context, v uint64, namespace share.Namespace, proof *jsonrpc.Proof, commitment jsonrpc.Commitment) (bool, error)) *MockBlobModule_Included_Call { _c.Call.Return(run) return _c } // Submit provides a mock function for the type MockBlobModule -func (_mock *MockBlobModule) Submit(context1 context.Context, blobs []*blob.Blob, submitOptions *blob.SubmitOptions) (uint64, error) { +func (_mock *MockBlobModule) Submit(context1 context.Context, blobs []*jsonrpc.Blob, submitOptions *jsonrpc.SubmitOptions) (uint64, error) { ret := _mock.Called(context1, blobs, submitOptions) if len(ret) == 0 { @@ -447,15 +447,15 @@ func (_mock *MockBlobModule) Submit(context1 context.Context, blobs []*blob.Blob var r0 uint64 var r1 error - if returnFunc, ok := ret.Get(0).(func(context.Context, []*blob.Blob, *blob.SubmitOptions) (uint64, error)); ok { + if returnFunc, ok := ret.Get(0).(func(context.Context, []*jsonrpc.Blob, *jsonrpc.SubmitOptions) (uint64, error)); ok { return returnFunc(context1, blobs, submitOptions) } - if returnFunc, ok := ret.Get(0).(func(context.Context, []*blob.Blob, *blob.SubmitOptions) uint64); ok { + if returnFunc, ok := ret.Get(0).(func(context.Context, []*jsonrpc.Blob, *jsonrpc.SubmitOptions) uint64); ok { r0 = returnFunc(context1, blobs, submitOptions) } else { r0 = ret.Get(0).(uint64) } - if returnFunc, ok := ret.Get(1).(func(context.Context, []*blob.Blob, *blob.SubmitOptions) error); ok { + if returnFunc, ok := ret.Get(1).(func(context.Context, []*jsonrpc.Blob, *jsonrpc.SubmitOptions) error); ok { r1 = returnFunc(context1, blobs, submitOptions) } else { r1 = ret.Error(1) @@ -470,25 +470,25 @@ type MockBlobModule_Submit_Call struct { // Submit is a helper method to define mock.On call // - context1 context.Context -// - blobs []*blob.Blob -// - submitOptions *blob.SubmitOptions +// - blobs []*jsonrpc.Blob +// - submitOptions *jsonrpc.SubmitOptions func (_e *MockBlobModule_Expecter) Submit(context1 interface{}, blobs interface{}, submitOptions interface{}) *MockBlobModule_Submit_Call { return &MockBlobModule_Submit_Call{Call: _e.mock.On("Submit", context1, blobs, submitOptions)} } -func (_c *MockBlobModule_Submit_Call) Run(run func(context1 context.Context, blobs []*blob.Blob, submitOptions *blob.SubmitOptions)) *MockBlobModule_Submit_Call { +func (_c *MockBlobModule_Submit_Call) Run(run func(context1 context.Context, blobs []*jsonrpc.Blob, submitOptions *jsonrpc.SubmitOptions)) *MockBlobModule_Submit_Call { _c.Call.Run(func(args mock.Arguments) { var arg0 context.Context if args[0] != nil { arg0 = args[0].(context.Context) } - var arg1 []*blob.Blob + var arg1 []*jsonrpc.Blob if args[1] != nil { - arg1 = args[1].([]*blob.Blob) + arg1 = args[1].([]*jsonrpc.Blob) } - var arg2 *blob.SubmitOptions + var arg2 *jsonrpc.SubmitOptions if args[2] != nil { - arg2 = args[2].(*blob.SubmitOptions) + arg2 = args[2].(*jsonrpc.SubmitOptions) } run( arg0, @@ -504,29 +504,29 @@ func (_c *MockBlobModule_Submit_Call) Return(v uint64, err error) *MockBlobModul return _c } -func (_c *MockBlobModule_Submit_Call) RunAndReturn(run func(context1 context.Context, blobs []*blob.Blob, submitOptions *blob.SubmitOptions) (uint64, error)) *MockBlobModule_Submit_Call { +func (_c *MockBlobModule_Submit_Call) RunAndReturn(run func(context1 context.Context, blobs []*jsonrpc.Blob, submitOptions *jsonrpc.SubmitOptions) (uint64, error)) *MockBlobModule_Submit_Call { _c.Call.Return(run) return _c } // Subscribe provides a mock function for the type MockBlobModule -func (_mock *MockBlobModule) Subscribe(context1 context.Context, namespace share.Namespace) (<-chan *blob.SubscriptionResponse, error) { +func (_mock *MockBlobModule) Subscribe(context1 context.Context, namespace share.Namespace) (<-chan *jsonrpc.SubscriptionResponse, error) { ret := _mock.Called(context1, namespace) if len(ret) == 0 { panic("no return value specified for Subscribe") } - var r0 <-chan *blob.SubscriptionResponse + var r0 <-chan *jsonrpc.SubscriptionResponse var r1 error - if returnFunc, ok := ret.Get(0).(func(context.Context, share.Namespace) (<-chan *blob.SubscriptionResponse, error)); ok { + if returnFunc, ok := ret.Get(0).(func(context.Context, share.Namespace) (<-chan *jsonrpc.SubscriptionResponse, error)); ok { return returnFunc(context1, namespace) } - if returnFunc, ok := ret.Get(0).(func(context.Context, share.Namespace) <-chan *blob.SubscriptionResponse); ok { + if returnFunc, ok := ret.Get(0).(func(context.Context, share.Namespace) <-chan *jsonrpc.SubscriptionResponse); ok { r0 = returnFunc(context1, namespace) } else { if ret.Get(0) != nil { - r0 = ret.Get(0).(<-chan *blob.SubscriptionResponse) + r0 = ret.Get(0).(<-chan *jsonrpc.SubscriptionResponse) } } if returnFunc, ok := ret.Get(1).(func(context.Context, share.Namespace) error); ok { @@ -567,12 +567,12 @@ func (_c *MockBlobModule_Subscribe_Call) Run(run func(context1 context.Context, return _c } -func (_c *MockBlobModule_Subscribe_Call) Return(subscriptionResponseCh <-chan *blob.SubscriptionResponse, err error) *MockBlobModule_Subscribe_Call { +func (_c *MockBlobModule_Subscribe_Call) Return(subscriptionResponseCh <-chan *jsonrpc.SubscriptionResponse, err error) *MockBlobModule_Subscribe_Call { _c.Call.Return(subscriptionResponseCh, err) return _c } -func (_c *MockBlobModule_Subscribe_Call) RunAndReturn(run func(context1 context.Context, namespace share.Namespace) (<-chan *blob.SubscriptionResponse, error)) *MockBlobModule_Subscribe_Call { +func (_c *MockBlobModule_Subscribe_Call) RunAndReturn(run func(context1 context.Context, namespace share.Namespace) (<-chan *jsonrpc.SubscriptionResponse, error)) *MockBlobModule_Subscribe_Call { _c.Call.Return(run) return _c } diff --git a/pkg/da/jsonrpc/module.go b/pkg/da/jsonrpc/module.go index 794d4c8b35..991845a40d 100644 --- a/pkg/da/jsonrpc/module.go +++ b/pkg/da/jsonrpc/module.go @@ -1,4 +1,4 @@ -package blob +package jsonrpc import ( "context" diff --git a/pkg/da/jsonrpc/submit_options.go b/pkg/da/jsonrpc/submit_options.go index 1c1f66cb4c..1100fd8813 100644 --- a/pkg/da/jsonrpc/submit_options.go +++ b/pkg/da/jsonrpc/submit_options.go @@ -1,4 +1,4 @@ -package blob +package jsonrpc // NOTE: This mirrors the exported JSON shape of celestia-node/state/tx_config.go // at release v0.28.4, pared down to avoid importing Cosmos-SDK and diff --git a/pkg/da/jsonrpc/types.go b/pkg/da/jsonrpc/types.go index 577cfc571e..b7377e950e 100644 --- a/pkg/da/jsonrpc/types.go +++ b/pkg/da/jsonrpc/types.go @@ -1,4 +1,4 @@ -package blob +package jsonrpc // CommitmentProof matches celestia-node's blob.CommitmentProof JSON shape. // We keep only the fields we need on the client side. diff --git a/core/da/errors.go b/pkg/da/types/errors.go similarity index 96% rename from core/da/errors.go rename to pkg/da/types/errors.go index beac62be54..d4eecfb076 100644 --- a/core/da/errors.go +++ b/pkg/da/types/errors.go @@ -1,8 +1,6 @@ package da -import ( - "errors" -) +import "errors" var ( ErrBlobNotFound = errors.New("blob: not found") diff --git a/pkg/da/types/namespace.go b/pkg/da/types/namespace.go new file mode 100644 index 0000000000..8d95b5703c --- /dev/null +++ b/pkg/da/types/namespace.go @@ -0,0 +1,98 @@ +package da + +import ( + "crypto/sha256" + "encoding/hex" + "fmt" + "strings" +) + +const ( + NamespaceVersionIndex = 0 + NamespaceVersionSize = 1 + NamespaceIDSize = 28 + NamespaceSize = NamespaceVersionSize + NamespaceIDSize + NamespaceVersionZero = uint8(0) + NamespaceVersionMax = uint8(255) + NamespaceVersionZeroPrefixSize = 18 + NamespaceVersionZeroDataSize = 10 +) + +// Namespace mirrors Celestia namespace layout (version + 28-byte ID). +type Namespace struct { + Version uint8 + ID [NamespaceIDSize]byte +} + +// Bytes returns the namespace as a byte slice. +func (n Namespace) Bytes() []byte { + result := make([]byte, NamespaceSize) + result[NamespaceVersionIndex] = n.Version + copy(result[NamespaceVersionSize:], n.ID[:]) + return result +} + +// IsValidForVersion0 validates version-0 namespace rules (first 18 bytes zero). +func (n Namespace) IsValidForVersion0() bool { + if n.Version != NamespaceVersionZero { + return false + } + + for i := range NamespaceVersionZeroPrefixSize { + if n.ID[i] != 0 { + return false + } + } + return true +} + +// NewNamespaceV0 builds a version-0 namespace from up to 10 bytes of data. +func NewNamespaceV0(data []byte) (*Namespace, error) { + if len(data) > NamespaceVersionZeroDataSize { + return nil, fmt.Errorf("data too long for version 0 namespace: got %d bytes, max %d", len(data), NamespaceVersionZeroDataSize) + } + + ns := &Namespace{Version: NamespaceVersionZero} + copy(ns.ID[NamespaceVersionZeroPrefixSize:], data) + return ns, nil +} + +// NamespaceFromBytes parses a namespace from its byte representation. +func NamespaceFromBytes(b []byte) (*Namespace, error) { + if len(b) != NamespaceSize { + return nil, fmt.Errorf("invalid namespace size: expected %d, got %d", NamespaceSize, len(b)) + } + + ns := &Namespace{Version: b[NamespaceVersionIndex]} + copy(ns.ID[:], b[NamespaceVersionSize:]) + + if ns.Version == NamespaceVersionZero && !ns.IsValidForVersion0() { + return nil, fmt.Errorf("invalid version 0 namespace: first %d bytes of ID must be zero", NamespaceVersionZeroPrefixSize) + } + + return ns, nil +} + +// NamespaceFromString deterministically builds a version-0 namespace from a string. +func NamespaceFromString(s string) *Namespace { + hash := sha256.Sum256([]byte(s)) + ns, _ := NewNamespaceV0(hash[:NamespaceVersionZeroDataSize]) + return ns +} + +// HexString returns the 0x-prefixed hex encoding of the namespace bytes. +func (n Namespace) HexString() string { + return "0x" + hex.EncodeToString(n.Bytes()) +} + +// ParseHexNamespace parses a hex string (with or without 0x) into a namespace. +func ParseHexNamespace(hexStr string) (*Namespace, error) { + hexStr = strings.TrimPrefix(hexStr, "0x") + + b, err := hex.DecodeString(hexStr) + if err != nil { + return nil, fmt.Errorf("invalid hex string: %w", err) + } + + return NamespaceFromBytes(b) +} diff --git a/core/da/namespace_test.go b/pkg/da/types/namespace_test.go similarity index 68% rename from core/da/namespace_test.go rename to pkg/da/types/namespace_test.go index b582e8b61b..1c068c8f9d 100644 --- a/core/da/namespace_test.go +++ b/pkg/da/types/namespace_test.go @@ -13,30 +13,10 @@ func TestNamespaceV0Creation(t *testing.T) { expectError bool description string }{ - { - name: "valid 10 byte data", - data: []byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}, - expectError: false, - description: "Should create valid namespace with 10 bytes of data", - }, - { - name: "valid 5 byte data", - data: []byte{1, 2, 3, 4, 5}, - expectError: false, - description: "Should create valid namespace with 5 bytes of data (padded with zeros)", - }, - { - name: "empty data", - data: []byte{}, - expectError: false, - description: "Should create valid namespace with empty data (all zeros)", - }, - { - name: "data too long", - data: []byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11}, - expectError: true, - description: "Should fail with data longer than 10 bytes", - }, + {name: "valid 10 byte data", data: []byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}, description: "Should create valid namespace with 10 bytes of data"}, + {name: "valid 5 byte data", data: []byte{1, 2, 3, 4, 5}, description: "Should create valid namespace with 5 bytes of data (padded with zeros)"}, + {name: "empty data", data: []byte{}, description: "Should create valid namespace with empty data (all zeros)"}, + {name: "data too long", data: []byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11}, expectError: true, description: "Should fail with data longer than 10 bytes"}, } for _, tt := range tests { @@ -50,43 +30,39 @@ func TestNamespaceV0Creation(t *testing.T) { if ns != nil { t.Errorf("expected nil namespace but got %v", ns) } - } else { - if err != nil { - t.Fatalf("%s: unexpected error: %v", tt.description, err) - } - if ns == nil { - t.Fatal("expected non-nil namespace but got nil") - } + return + } - // Verify version is 0 - if ns.Version != NamespaceVersionZero { - t.Errorf("Version should be 0, got %d", ns.Version) - } + if err != nil { + t.Fatalf("%s: unexpected error: %v", tt.description, err) + } + if ns == nil { + t.Fatal("expected non-nil namespace but got nil") + } - // Verify first 18 bytes of ID are zeros - for i := range NamespaceVersionZeroPrefixSize { - if ns.ID[i] != byte(0) { - t.Errorf("First 18 bytes should be zero, but byte %d is %d", i, ns.ID[i]) - } - } + if ns.Version != NamespaceVersionZero { + t.Errorf("Version should be 0, got %d", ns.Version) + } - // Verify data is in the last 10 bytes - expectedData := make([]byte, NamespaceVersionZeroDataSize) - copy(expectedData, tt.data) - actualData := ns.ID[NamespaceVersionZeroPrefixSize:] - if !bytes.Equal(expectedData, actualData) { - t.Errorf("Data should match in last 10 bytes, expected %v, got %v", expectedData, actualData) + for i := range NamespaceVersionZeroPrefixSize { + if ns.ID[i] != 0 { + t.Errorf("First 18 bytes should be zero, but byte %d is %d", i, ns.ID[i]) } + } - // Verify total size - if len(ns.Bytes()) != NamespaceSize { - t.Errorf("Total namespace size should be 29 bytes, got %d", len(ns.Bytes())) - } + expectedData := make([]byte, NamespaceVersionZeroDataSize) + copy(expectedData, tt.data) + actualData := ns.ID[NamespaceVersionZeroPrefixSize:] + if !bytes.Equal(expectedData, actualData) { + t.Errorf("Data should match in last 10 bytes, expected %v, got %v", expectedData, actualData) + } - // Verify it's valid for version 0 - if !ns.IsValidForVersion0() { - t.Error("Should be valid for version 0") - } + if len(ns.Bytes()) != NamespaceSize { + t.Errorf("Total namespace size should be %d bytes, got %d", NamespaceSize, len(ns.Bytes())) + } + + if !ns.IsValidForVersion0() { + t.Error("Should be valid for version 0") } }) } @@ -102,7 +78,6 @@ func TestNamespaceFromBytes(t *testing.T) { { name: "valid version 0 namespace", input: append([]byte{0}, append(make([]byte, 18), []byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}...)...), - expectError: false, description: "Should parse valid version 0 namespace", }, { @@ -121,7 +96,7 @@ func TestNamespaceFromBytes(t *testing.T) { name: "invalid version 0 - non-zero prefix", input: append([]byte{0}, append([]byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1}, []byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}...)...), expectError: true, - description: "Should fail when version 0 namespace has non-zero bytes in first 18 bytes of ID", + description: "Should fail when version 0 namespace has non-zero prefix", }, } @@ -136,16 +111,17 @@ func TestNamespaceFromBytes(t *testing.T) { if ns != nil { t.Errorf("expected nil namespace but got %v", ns) } - } else { - if err != nil { - t.Fatalf("%s: unexpected error: %v", tt.description, err) - } - if ns == nil { - t.Fatal("expected non-nil namespace but got nil") - } - if !bytes.Equal(tt.input, ns.Bytes()) { - t.Errorf("Should round-trip correctly, expected %v, got %v", tt.input, ns.Bytes()) - } + return + } + + if err != nil { + t.Fatalf("%s: unexpected error: %v", tt.description, err) + } + if ns == nil { + t.Fatal("expected non-nil namespace but got nil") + } + if !bytes.Equal(tt.input, ns.Bytes()) { + t.Errorf("Should round-trip correctly, expected %v, got %v", tt.input, ns.Bytes()) } }) } @@ -171,7 +147,6 @@ func TestNamespaceFromString(t *testing.T) { t.Errorf("expected namespace size %d, got %d", NamespaceSize, len(ns.Bytes())) } - // The hash should be deterministic ns2 := NamespaceFromString("rollkit-headers") if !bytes.Equal(ns.Bytes(), ns2.Bytes()) { t.Error("Same string should produce same namespace") @@ -192,7 +167,6 @@ func TestNamespaceFromString(t *testing.T) { t.Errorf("expected namespace size %d, got %d", NamespaceSize, len(ns.Bytes())) } - // Different strings should produce different namespaces ns2 := NamespaceFromString("rollkit-headers") if bytes.Equal(ns.Bytes(), ns2.Bytes()) { t.Error("Different strings should produce different namespaces") @@ -233,7 +207,6 @@ func TestHexStringConversion(t *testing.T) { t.Fatalf("unexpected error: %v", err) } - // Test HexString hexStr := ns.HexString() if len(hexStr) <= 2 { t.Error("Hex string should not be empty") @@ -242,7 +215,6 @@ func TestHexStringConversion(t *testing.T) { t.Errorf("Should have 0x prefix, got %s", hexStr[:2]) } - // Test ParseHexNamespace parsed, err := ParseHexNamespace(hexStr) if err != nil { t.Fatalf("unexpected error parsing hex: %v", err) @@ -251,7 +223,6 @@ func TestHexStringConversion(t *testing.T) { t.Error("Should round-trip through hex") } - // Test without 0x prefix parsed2, err := ParseHexNamespace(hexStr[2:]) if err != nil { t.Fatalf("unexpected error parsing hex without prefix: %v", err) @@ -260,22 +231,16 @@ func TestHexStringConversion(t *testing.T) { t.Error("Should work without 0x prefix") } - // Test invalid hex - _, err = ParseHexNamespace("invalid-hex") - if err == nil { + if _, err = ParseHexNamespace("invalid-hex"); err == nil { t.Error("Should fail with invalid hex") } - // Test wrong size hex - _, err = ParseHexNamespace("0x0011") - if err == nil { + if _, err = ParseHexNamespace("0x0011"); err == nil { t.Error("Should fail with wrong size") } } func TestCelestiaSpecCompliance(t *testing.T) { - // Test that our implementation follows the Celestia namespace specification - t.Run("namespace structure", func(t *testing.T) { ns, err := NewNamespaceV0([]byte{0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A}) if err != nil { @@ -284,17 +249,14 @@ func TestCelestiaSpecCompliance(t *testing.T) { nsBytes := ns.Bytes() - // Check total size is 29 bytes (1 version + 28 ID) if len(nsBytes) != 29 { t.Errorf("Total namespace size should be 29 bytes, got %d", len(nsBytes)) } - // Check version byte is at position 0 - if nsBytes[0] != byte(0) { + if nsBytes[0] != 0 { t.Errorf("Version byte should be 0, got %d", nsBytes[0]) } - // Check ID is 28 bytes starting at position 1 if len(nsBytes[1:]) != 28 { t.Errorf("ID should be 28 bytes, got %d", len(nsBytes[1:])) } @@ -308,14 +270,12 @@ func TestCelestiaSpecCompliance(t *testing.T) { nsBytes := ns.Bytes() - // For version 0, first 18 bytes of ID must be zero for i := 1; i <= 18; i++ { - if nsBytes[i] != byte(0) { + if nsBytes[i] != 0 { t.Errorf("Bytes 1-18 should be zero for version 0, but byte %d is %d", i, nsBytes[i]) } } - // Last 10 bytes should contain our data expectedData := []byte{0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF} actualData := nsBytes[19:29] if !bytes.Equal(expectedData, actualData) { @@ -324,20 +284,15 @@ func TestCelestiaSpecCompliance(t *testing.T) { }) t.Run("example from spec", func(t *testing.T) { - // Create a namespace similar to the example in the spec ns, err := NewNamespaceV0([]byte{0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01}) if err != nil { t.Fatalf("unexpected error: %v", err) } hexStr := ns.HexString() - t.Logf("Example namespace: %s", hexStr) - - // Verify it matches the expected format if len(hexStr) != 60 { t.Errorf("Hex string should be 60 chars (0x + 58 hex chars), got %d", len(hexStr)) } - // The prefix should be: 0x (2 chars) + version byte 00 (2 chars) + 18 zero bytes (36 chars) = 40 chars total expectedPrefix := "0x00000000000000000000000000000000000000" if hexStr[:40] != expectedPrefix { t.Errorf("Should have correct zero prefix, expected %s, got %s", expectedPrefix, hexStr[:40]) @@ -346,7 +301,6 @@ func TestCelestiaSpecCompliance(t *testing.T) { } func TestRealWorldNamespaces(t *testing.T) { - // Test with actual namespace strings used in rollkit namespaces := []string{ "rollkit-headers", "rollkit-data", @@ -359,10 +313,8 @@ func TestRealWorldNamespaces(t *testing.T) { for _, nsStr := range namespaces { t.Run(nsStr, func(t *testing.T) { - // Convert string to namespace nsBytes := NamespaceFromString(nsStr) - // Verify it's valid ns, err := NamespaceFromBytes(nsBytes.Bytes()) if err != nil { t.Fatalf("unexpected error: %v", err) @@ -371,20 +323,16 @@ func TestRealWorldNamespaces(t *testing.T) { t.Error("namespace should be valid for version 0") } - // Verify uniqueness hexStr := hex.EncodeToString(nsBytes.Bytes()) if seen[hexStr] { t.Errorf("Namespace should be unique, but %s was already seen", hexStr) } seen[hexStr] = true - // Verify deterministic nsBytes2 := NamespaceFromString(nsStr) if !bytes.Equal(nsBytes.Bytes(), nsBytes2.Bytes()) { t.Error("Should be deterministic") } - - t.Logf("Namespace for '%s': %s", nsStr, hex.EncodeToString(nsBytes.Bytes())) }) } } diff --git a/block/internal/da/types.go b/pkg/da/types/types.go similarity index 81% rename from block/internal/da/types.go rename to pkg/da/types/types.go index 20fab61df5..b2f2e7bc30 100644 --- a/block/internal/da/types.go +++ b/pkg/da/types/types.go @@ -1,8 +1,12 @@ package da -import "time" +import ( + "encoding/binary" + "fmt" + "time" +) -// StatusCode mirrors DA status codes used in Celestia blob client. +// StatusCode mirrors the blob RPC status codes shared with block/internal/da. type StatusCode uint64 // Data Availability return codes. @@ -68,3 +72,13 @@ type BaseResult struct { // Timestamp is the timestamp of the posted data on Data Availability Layer. Timestamp time.Time } + +// SplitID splits an ID into a height and a commitment. +// if len(id) <= 8, it returns 0 and nil. +func SplitID(id []byte) (uint64, []byte, error) { + if len(id) <= 8 { + return 0, nil, fmt.Errorf("invalid ID length: %d", len(id)) + } + commitment := id[8:] + return binary.LittleEndian.Uint64(id[:8]), commitment, nil +} diff --git a/pkg/rpc/server/da_visualization.go b/pkg/rpc/server/da_visualization.go index 96410d7ace..008f88f402 100644 --- a/pkg/rpc/server/da_visualization.go +++ b/pkg/rpc/server/da_visualization.go @@ -11,7 +11,8 @@ import ( "sync" "time" - coreda "github.com/evstack/ev-node/core/da" + blobrpc "github.com/evstack/ev-node/pkg/da/jsonrpc" + datypes "github.com/evstack/ev-node/pkg/da/types" "github.com/rs/zerolog" ) @@ -34,15 +35,20 @@ type DASubmissionInfo struct { // DAVisualizationServer provides DA layer visualization endpoints type DAVisualizationServer struct { - da coreda.DA + da DAVizClient logger zerolog.Logger submissions []DASubmissionInfo mutex sync.RWMutex isAggregator bool } +// DAVizClient is the minimal DA surface needed by the visualization server. +type DAVizClient interface { + Get(ctx context.Context, ids []datypes.ID, namespace []byte) ([]datypes.Blob, error) +} + // NewDAVisualizationServer creates a new DA visualization server -func NewDAVisualizationServer(da coreda.DA, logger zerolog.Logger, isAggregator bool) *DAVisualizationServer { +func NewDAVisualizationServer(da DAVizClient, logger zerolog.Logger, isAggregator bool) *DAVisualizationServer { return &DAVisualizationServer{ da: da, logger: logger, @@ -53,7 +59,7 @@ func NewDAVisualizationServer(da coreda.DA, logger zerolog.Logger, isAggregator // RecordSubmission records a DA submission for visualization // Only keeps the last 100 submissions in memory for the dashboard display -func (s *DAVisualizationServer) RecordSubmission(result *coreda.ResultSubmit, gasPrice float64, numBlobs uint64, namespace []byte) { +func (s *DAVisualizationServer) RecordSubmission(result *datypes.ResultSubmit, gasPrice float64, numBlobs uint64, namespace []byte) { s.mutex.Lock() defer s.mutex.Unlock() @@ -85,27 +91,27 @@ func (s *DAVisualizationServer) RecordSubmission(result *coreda.ResultSubmit, ga } // getStatusCodeString converts status code to human-readable string -func (s *DAVisualizationServer) getStatusCodeString(code coreda.StatusCode) string { +func (s *DAVisualizationServer) getStatusCodeString(code datypes.StatusCode) string { switch code { - case coreda.StatusSuccess: + case datypes.StatusSuccess: return "Success" - case coreda.StatusNotFound: + case datypes.StatusNotFound: return "Not Found" - case coreda.StatusNotIncludedInBlock: + case datypes.StatusNotIncludedInBlock: return "Not Included In Block" - case coreda.StatusAlreadyInMempool: + case datypes.StatusAlreadyInMempool: return "Already In Mempool" - case coreda.StatusTooBig: + case datypes.StatusTooBig: return "Too Big" - case coreda.StatusContextDeadline: + case datypes.StatusContextDeadline: return "Context Deadline" - case coreda.StatusError: + case datypes.StatusError: return "Error" - case coreda.StatusIncorrectAccountSequence: + case datypes.StatusIncorrectAccountSequence: return "Incorrect Account Sequence" - case coreda.StatusContextCanceled: + case datypes.StatusContextCanceled: return "Context Canceled" - case coreda.StatusHeightFromFuture: + case datypes.StatusHeightFromFuture: return "Height From Future" default: return "Unknown" @@ -179,11 +185,11 @@ func (s *DAVisualizationServer) handleDABlobDetails(w http.ResponseWriter, r *ht // 1. Check query parameter first nsParam := r.URL.Query().Get("namespace") if nsParam != "" { - if ns, err := coreda.ParseHexNamespace(nsParam); err == nil { + if ns, err := datypes.ParseHexNamespace(nsParam); err == nil { namespace = ns.Bytes() found = true } else { - ns := coreda.NamespaceFromString(nsParam) + ns := datypes.NamespaceFromString(nsParam) namespace = ns.Bytes() found = true } @@ -216,7 +222,7 @@ func (s *DAVisualizationServer) handleDABlobDetails(w http.ResponseWriter, r *ht return } - blobs, err := s.da.Get(ctx, []coreda.ID{id}, namespace) + blobs, err := s.da.Get(ctx, []datypes.ID{id}, namespace) if err != nil { s.logger.Error().Err(err).Str("blob_id", blobID).Msg("Failed to retrieve blob from DA") http.Error(w, fmt.Sprintf("Failed to retrieve blob: %v", err), http.StatusInternalServerError) @@ -229,10 +235,7 @@ func (s *DAVisualizationServer) handleDABlobDetails(w http.ResponseWriter, r *ht } // Parse the blob ID to extract height and commitment - height, commitment, err := coreda.SplitID(id) - if err != nil { - s.logger.Error().Err(err).Str("blob_id", blobID).Msg("Failed to split blob ID") - } + height, commitment := blobrpc.SplitID(id) blob := blobs[0] response := map[string]any{ diff --git a/pkg/rpc/server/da_visualization_non_aggregator_test.go b/pkg/rpc/server/da_visualization_non_aggregator_test.go index 2c67489562..2094684893 100644 --- a/pkg/rpc/server/da_visualization_non_aggregator_test.go +++ b/pkg/rpc/server/da_visualization_non_aggregator_test.go @@ -7,14 +7,13 @@ import ( "strings" "testing" - "github.com/evstack/ev-node/test/mocks" "github.com/rs/zerolog" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) func TestNonAggregatorDAVisualizationServer(t *testing.T) { - da := &mocks.MockDA{} + da := &stubDAVizClient{} logger := zerolog.New(nil) // Create a non-aggregator server @@ -27,7 +26,7 @@ func TestNonAggregatorDAVisualizationServer(t *testing.T) { } func TestNonAggregatorHandleDASubmissions(t *testing.T) { - da := &mocks.MockDA{} + da := &stubDAVizClient{} logger := zerolog.New(nil) // Create a non-aggregator server @@ -57,7 +56,7 @@ func TestNonAggregatorHandleDASubmissions(t *testing.T) { } func TestNonAggregatorHandleDAStats(t *testing.T) { - da := &mocks.MockDA{} + da := &stubDAVizClient{} logger := zerolog.New(nil) // Create a non-aggregator server @@ -83,7 +82,7 @@ func TestNonAggregatorHandleDAStats(t *testing.T) { } func TestNonAggregatorHandleDAHealth(t *testing.T) { - da := &mocks.MockDA{} + da := &stubDAVizClient{} logger := zerolog.New(nil) // Create a non-aggregator server @@ -110,7 +109,7 @@ func TestNonAggregatorHandleDAHealth(t *testing.T) { } func TestNonAggregatorHandleDAVisualizationHTML(t *testing.T) { - da := &mocks.MockDA{} + da := &stubDAVizClient{} logger := zerolog.New(nil) // Create a non-aggregator server @@ -139,7 +138,7 @@ func TestNonAggregatorHandleDAVisualizationHTML(t *testing.T) { } func TestAggregatorWithNoSubmissionsHTML(t *testing.T) { - da := &mocks.MockDA{} + da := &stubDAVizClient{} logger := zerolog.New(nil) // Create an aggregator server but don't add any submissions diff --git a/pkg/rpc/server/da_visualization_test.go b/pkg/rpc/server/da_visualization_test.go index 2994dc9552..69d69776f2 100644 --- a/pkg/rpc/server/da_visualization_test.go +++ b/pkg/rpc/server/da_visualization_test.go @@ -1,6 +1,7 @@ package server import ( + "context" "encoding/hex" "encoding/json" "net/http" @@ -9,16 +10,21 @@ import ( "testing" "time" - coreda "github.com/evstack/ev-node/core/da" "github.com/evstack/ev-node/pkg/config" - "github.com/evstack/ev-node/test/mocks" + coreda "github.com/evstack/ev-node/pkg/da/types" "github.com/rs/zerolog" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) +type stubDAVizClient struct{} + +func (s *stubDAVizClient) Get(_ context.Context, _ []coreda.ID, _ []byte) ([]coreda.Blob, error) { + return nil, nil +} + func TestNewDAVisualizationServer(t *testing.T) { - da := &mocks.MockDA{} + da := &stubDAVizClient{} logger := zerolog.New(nil) server := NewDAVisualizationServer(da, logger, true) @@ -29,7 +35,7 @@ func TestNewDAVisualizationServer(t *testing.T) { } func TestRecordSubmission(t *testing.T) { - da := &mocks.MockDA{} + da := &stubDAVizClient{} logger := zerolog.New(nil) server := NewDAVisualizationServer(da, logger, true) @@ -62,7 +68,7 @@ func TestRecordSubmission(t *testing.T) { } func TestRecordSubmissionMemoryLimit(t *testing.T) { - da := &mocks.MockDA{} + da := &stubDAVizClient{} logger := zerolog.New(nil) server := NewDAVisualizationServer(da, logger, true) @@ -88,7 +94,7 @@ func TestRecordSubmissionMemoryLimit(t *testing.T) { } func TestGetStatusCodeString(t *testing.T) { - da := &mocks.MockDA{} + da := &stubDAVizClient{} logger := zerolog.New(nil) server := NewDAVisualizationServer(da, logger, true) @@ -111,7 +117,7 @@ func TestGetStatusCodeString(t *testing.T) { } func TestHandleDASubmissions(t *testing.T) { - da := &mocks.MockDA{} + da := &stubDAVizClient{} logger := zerolog.New(nil) server := NewDAVisualizationServer(da, logger, true) @@ -152,7 +158,7 @@ func TestHandleDASubmissions(t *testing.T) { } func TestHandleDABlobDetailsMissingID(t *testing.T) { - da := &mocks.MockDA{} + da := &stubDAVizClient{} logger := zerolog.New(nil) server := NewDAVisualizationServer(da, logger, true) @@ -167,7 +173,7 @@ func TestHandleDABlobDetailsMissingID(t *testing.T) { } func TestHandleDABlobDetailsInvalidID(t *testing.T) { - da := &mocks.MockDA{} + da := &stubDAVizClient{} logger := zerolog.New(nil) server := NewDAVisualizationServer(da, logger, true) @@ -182,7 +188,7 @@ func TestHandleDABlobDetailsInvalidID(t *testing.T) { } func TestHandleDAVisualizationHTML(t *testing.T) { - da := &mocks.MockDA{} + da := &stubDAVizClient{} logger := zerolog.New(nil) server := NewDAVisualizationServer(da, logger, true) @@ -216,7 +222,7 @@ func TestHandleDAVisualizationHTML(t *testing.T) { } func TestGlobalDAVisualizationServer(t *testing.T) { - da := &mocks.MockDA{} + da := &stubDAVizClient{} logger := zerolog.New(nil) server := NewDAVisualizationServer(da, logger, true) @@ -233,7 +239,7 @@ func TestGlobalDAVisualizationServer(t *testing.T) { } func TestRegisterCustomHTTPEndpointsDAVisualization(t *testing.T) { - da := &mocks.MockDA{} + da := &stubDAVizClient{} logger := zerolog.New(nil) server := NewDAVisualizationServer(da, logger, true) diff --git a/pkg/rpc/server/server.go b/pkg/rpc/server/server.go index e0abed2de0..817ca1f377 100644 --- a/pkg/rpc/server/server.go +++ b/pkg/rpc/server/server.go @@ -13,7 +13,7 @@ import ( "connectrpc.com/connect" "connectrpc.com/grpcreflect" goheader "github.com/celestiaorg/go-header" - coreda "github.com/evstack/ev-node/core/da" + datypes "github.com/evstack/ev-node/pkg/da/types" ds "github.com/ipfs/go-datastore" "github.com/rs/zerolog" "golang.org/x/net/http2" @@ -286,8 +286,8 @@ func (cs *ConfigServer) GetNamespace( req *connect.Request[emptypb.Empty], ) (*connect.Response[pb.GetNamespaceResponse], error) { - hns := coreda.NamespaceFromString(cs.config.DA.GetNamespace()) - dns := coreda.NamespaceFromString(cs.config.DA.GetDataNamespace()) + hns := datypes.NamespaceFromString(cs.config.DA.GetNamespace()) + dns := datypes.NamespaceFromString(cs.config.DA.GetDataNamespace()) return connect.NewResponse(&pb.GetNamespaceResponse{ HeaderNamespace: hns.HexString(), diff --git a/pkg/sync/README.md b/pkg/sync/README.md index c228e9a9d4..b8ed245c07 100644 --- a/pkg/sync/README.md +++ b/pkg/sync/README.md @@ -154,7 +154,7 @@ The sync package is consumed by both the block executor (aggregator mode) and th - `github.com/ipfs/go-datastore/sync` - Thread-safe datastore wrapper - `github.com/celestiaorg/go-header` - Header synchronization library - `github.com/libp2p/go-libp2p` - P2P networking stack -- `github.com/evstack/ev-node/core/da` - Data Availability client interface +- `github.com/evstack/ev-node/pkg/da/types` - Data Availability interface and types ## Data Flow diff --git a/scripts/build.mk b/scripts/build.mk index 67bfe95dd9..5ed508f70e 100644 --- a/scripts/build.mk +++ b/scripts/build.mk @@ -32,7 +32,7 @@ build-all: @echo "--> Building evm" @cd apps/evm && go build -ldflags "$(LDFLAGS)" -o $(CURDIR)/build/evm . @echo "--> Building local-da" - @cd da && go build -ldflags "$(LDFLAGS)" -o $(CURDIR)/build/local-da ./cmd/local-da + @cd tools/local-da && go build -ldflags "$(LDFLAGS)" -o $(CURDIR)/build/local-da . @echo "--> All ev-node binaries built!" ## build-testapp-bench: @@ -53,7 +53,7 @@ build-evm: build-da: @echo "--> Building local-da" @mkdir -p $(CURDIR)/build - @cd da && go build -ldflags "$(LDFLAGS)" -o $(CURDIR)/build/local-da ./cmd/local-da + @cd tools/local-da && go build -ldflags "$(LDFLAGS)" -o $(CURDIR)/build/local-da . @echo " Check the binary with: $(CURDIR)/build/local-da" .PHONY: build-da diff --git a/sequencers/based/README.md b/sequencers/based/README.md index 9b425b5a96..6b15105474 100644 --- a/sequencers/based/README.md +++ b/sequencers/based/README.md @@ -59,7 +59,7 @@ When at an epoch end, the retriever fetches transactions from **all DA blocks in 3. Fetches forced inclusion blobs from `epochEnd` 4. Returns all transactions as a single `ForcedInclusionEvent` -### Why Epoch-Based? +### Why Epoch-Based - **Efficiency**: Reduces the number of DA queries - **Batching**: Allows processing multiple DA blocks worth of transactions together diff --git a/sequencers/based/sequencer.go b/sequencers/based/sequencer.go index 7960cd52df..4e7c6cfcac 100644 --- a/sequencers/based/sequencer.go +++ b/sequencers/based/sequencer.go @@ -11,8 +11,8 @@ import ( "github.com/rs/zerolog" "github.com/evstack/ev-node/block" - coreda "github.com/evstack/ev-node/core/da" coresequencer "github.com/evstack/ev-node/core/sequencer" + datypes "github.com/evstack/ev-node/pkg/da/types" "github.com/evstack/ev-node/pkg/genesis" seqcommon "github.com/evstack/ev-node/sequencers/common" ) @@ -160,7 +160,7 @@ func (s *BasedSequencer) fetchNextDAEpoch(ctx context.Context, maxBytes uint64) // Check if forced inclusion is not configured if errors.Is(err, block.ErrForceInclusionNotConfigured) { return time.Time{}, 0, block.ErrForceInclusionNotConfigured - } else if errors.Is(err, coreda.ErrHeightFromFuture) { + } else if errors.Is(err, datypes.ErrHeightFromFuture) { // If we get a height from future error, stay at current position // We'll retry the same height on the next call until DA produces that block s.logger.Debug(). diff --git a/sequencers/based/sequencer_test.go b/sequencers/based/sequencer_test.go index eb29e6bffb..7d74cb52a8 100644 --- a/sequencers/based/sequencer_test.go +++ b/sequencers/based/sequencer_test.go @@ -13,8 +13,8 @@ import ( "github.com/stretchr/testify/require" "github.com/evstack/ev-node/block" - coreda "github.com/evstack/ev-node/core/da" coresequencer "github.com/evstack/ev-node/core/sequencer" + datypes "github.com/evstack/ev-node/pkg/da/types" "github.com/evstack/ev-node/pkg/genesis" ) @@ -408,7 +408,7 @@ func TestBasedSequencer_GetNextBatch_ErrorHandling(t *testing.T) { func TestBasedSequencer_GetNextBatch_HeightFromFuture(t *testing.T) { mockRetriever := new(MockForcedInclusionRetriever) - mockRetriever.On("RetrieveForcedIncludedTxs", mock.Anything, uint64(100)).Return(nil, coreda.ErrHeightFromFuture) + mockRetriever.On("RetrieveForcedIncludedTxs", mock.Anything, uint64(100)).Return(nil, datypes.ErrHeightFromFuture) gen := genesis.Genesis{ ChainID: "test-chain", diff --git a/sequencers/common/size_validation.go b/sequencers/common/size_validation.go deleted file mode 100644 index a88206e280..0000000000 --- a/sequencers/common/size_validation.go +++ /dev/null @@ -1,9 +0,0 @@ -package common - -// TODO(@julienrbrt): technically we may need to check for block gas as well - -const ( - // AbsoluteMaxBlobSize is the absolute maximum size for a single blob (DA layer limit). - // Blobs exceeding this size are invalid and should be rejected permanently. - AbsoluteMaxBlobSize = 2 * 1024 * 1024 // 2MB -) diff --git a/sequencers/single/sequencer.go b/sequencers/single/sequencer.go index 8b114e1e06..417582ca0e 100644 --- a/sequencers/single/sequencer.go +++ b/sequencers/single/sequencer.go @@ -14,8 +14,8 @@ import ( "github.com/rs/zerolog" "github.com/evstack/ev-node/block" - coreda "github.com/evstack/ev-node/core/da" coresequencer "github.com/evstack/ev-node/core/sequencer" + datypes "github.com/evstack/ev-node/pkg/da/types" "github.com/evstack/ev-node/pkg/genesis" seqcommon "github.com/evstack/ev-node/sequencers/common" ) @@ -36,8 +36,8 @@ type Sequencer struct { logger zerolog.Logger proposer bool - Id []byte - da coreda.DA + Id []byte + daVerifier block.DAVerifier batchTime time.Duration queue *BatchQueue // single queue for immediate availability @@ -56,7 +56,7 @@ func NewSequencer( ctx context.Context, logger zerolog.Logger, db ds.Batching, - da coreda.DA, + daVerifier block.DAVerifier, id []byte, batchTime time.Duration, proposer bool, @@ -66,7 +66,7 @@ func NewSequencer( ) (*Sequencer, error) { s := &Sequencer{ logger: logger, - da: da, + daVerifier: daVerifier, batchTime: batchTime, Id: id, queue: NewBatchQueue(db, "batches", maxQueueSize), @@ -266,12 +266,12 @@ func (c *Sequencer) VerifyBatch(ctx context.Context, req coresequencer.VerifyBat } if !c.proposer { - proofs, err := c.da.GetProofs(ctx, req.BatchData, c.Id) + proofs, err := c.daVerifier.GetProofs(ctx, req.BatchData, c.Id) if err != nil { return nil, fmt.Errorf("failed to get proofs: %w", err) } - valid, err := c.da.Validate(ctx, req.BatchData, proofs, c.Id) + valid, err := c.daVerifier.Validate(ctx, req.BatchData, proofs, c.Id) if err != nil { return nil, fmt.Errorf("failed to validate proof: %w", err) } @@ -313,7 +313,7 @@ func (c *Sequencer) fetchNextDAEpoch(ctx context.Context, maxBytes uint64) (uint forcedTxsEvent, err := c.fiRetriever.RetrieveForcedIncludedTxs(ctx, currentDAHeight) if err != nil { - if errors.Is(err, coreda.ErrHeightFromFuture) { + if errors.Is(err, datypes.ErrHeightFromFuture) { c.logger.Debug(). Uint64("da_height", currentDAHeight). Msg("DA height from future, waiting for DA to produce block") diff --git a/sequencers/single/sequencer_test.go b/sequencers/single/sequencer_test.go index 6124642acb..a40ee29f81 100644 --- a/sequencers/single/sequencer_test.go +++ b/sequencers/single/sequencer_test.go @@ -14,10 +14,10 @@ import ( "github.com/stretchr/testify/require" "github.com/evstack/ev-node/block" - coreda "github.com/evstack/ev-node/core/da" coresequencer "github.com/evstack/ev-node/core/sequencer" "github.com/evstack/ev-node/pkg/genesis" damocks "github.com/evstack/ev-node/test/mocks" + "github.com/evstack/ev-node/test/testda" ) // MockForcedInclusionRetriever is a mock implementation of DARetriever for testing @@ -33,6 +33,10 @@ func (m *MockForcedInclusionRetriever) RetrieveForcedIncludedTxs(ctx context.Con return args.Get(0).(*block.ForcedInclusionEvent), args.Error(1) } +func newDummyDA(maxBlobSize uint64) *testda.DummyDA { + return testda.New(testda.WithMaxBlobSize(maxBlobSize)) +} + // newTestSequencer creates a sequencer for tests that don't need full initialization func newTestSequencer(t *testing.T, db ds.Batching, fiRetriever ForcedInclusionRetriever, proposer bool) *Sequencer { ctx := context.Background() @@ -60,7 +64,7 @@ func newTestSequencer(t *testing.T, db ds.Batching, fiRetriever ForcedInclusionR } func TestSequencer_SubmitBatchTxs(t *testing.T) { - dummyDA := coreda.NewDummyDA(100_000_000, 10*time.Second) + dummyDA := newDummyDA(100_000_000) db := ds.NewMapDatastore() ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second) defer cancel() @@ -114,7 +118,7 @@ func TestSequencer_SubmitBatchTxs(t *testing.T) { } func TestSequencer_SubmitBatchTxs_EmptyBatch(t *testing.T) { - dummyDA := coreda.NewDummyDA(100_000_000, 10*time.Second) + dummyDA := newDummyDA(100_000_000) db := ds.NewMapDatastore() ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second) defer cancel() @@ -274,14 +278,14 @@ func TestSequencer_VerifyBatch(t *testing.T) { proofs := [][]byte{[]byte("proof1"), []byte("proof2")} t.Run("Proposer Mode", func(t *testing.T) { - mockDA := damocks.NewMockDA(t) + mockDAVerifier := damocks.NewMockVerifier(t) mockRetriever := new(MockForcedInclusionRetriever) mockRetriever.On("RetrieveForcedIncludedTxs", mock.Anything, mock.Anything). Return(nil, block.ErrForceInclusionNotConfigured).Maybe() db := ds.NewMapDatastore() seq := newTestSequencer(t, db, mockRetriever, true) - seq.da = mockDA + seq.daVerifier = mockDAVerifier defer db.Close() res, err := seq.VerifyBatch(context.Background(), coresequencer.VerifyBatchRequest{Id: seq.Id, BatchData: batchData}) @@ -289,106 +293,106 @@ func TestSequencer_VerifyBatch(t *testing.T) { assert.NotNil(res) assert.True(res.Status, "Expected status to be true in proposer mode") - mockDA.AssertNotCalled(t, "GetProofs", context.Background(), mock.Anything) - mockDA.AssertNotCalled(t, "Validate", mock.Anything, mock.Anything, mock.Anything) + mockDAVerifier.AssertNotCalled(t, "GetProofs", context.Background(), mock.Anything) + mockDAVerifier.AssertNotCalled(t, "Validate", mock.Anything, mock.Anything, mock.Anything) }) t.Run("Non-Proposer Mode", func(t *testing.T) { t.Run("Valid Proofs", func(t *testing.T) { - mockDA := damocks.NewMockDA(t) + mockDAVerifier := damocks.NewMockVerifier(t) mockRetriever := new(MockForcedInclusionRetriever) mockRetriever.On("RetrieveForcedIncludedTxs", mock.Anything, mock.Anything). Return(nil, block.ErrForceInclusionNotConfigured).Maybe() db := ds.NewMapDatastore() seq := newTestSequencer(t, db, mockRetriever, false) - seq.da = mockDA + seq.daVerifier = mockDAVerifier defer db.Close() - mockDA.On("GetProofs", context.Background(), batchData, Id).Return(proofs, nil).Once() - mockDA.On("Validate", mock.Anything, batchData, proofs, Id).Return([]bool{true, true}, nil).Once() + mockDAVerifier.On("GetProofs", context.Background(), batchData, Id).Return(proofs, nil).Once() + mockDAVerifier.On("Validate", mock.Anything, batchData, proofs, Id).Return([]bool{true, true}, nil).Once() res, err := seq.VerifyBatch(context.Background(), coresequencer.VerifyBatchRequest{Id: seq.Id, BatchData: batchData}) assert.NoError(err) assert.NotNil(res) assert.True(res.Status, "Expected status to be true for valid proofs") - mockDA.AssertExpectations(t) + mockDAVerifier.AssertExpectations(t) }) t.Run("Invalid Proof", func(t *testing.T) { - mockDA := damocks.NewMockDA(t) + mockDAVerifier := damocks.NewMockVerifier(t) mockRetriever := new(MockForcedInclusionRetriever) mockRetriever.On("RetrieveForcedIncludedTxs", mock.Anything, mock.Anything). Return(nil, block.ErrForceInclusionNotConfigured).Maybe() db := ds.NewMapDatastore() seq := newTestSequencer(t, db, mockRetriever, false) - seq.da = mockDA + seq.daVerifier = mockDAVerifier defer db.Close() - mockDA.On("GetProofs", context.Background(), batchData, Id).Return(proofs, nil).Once() - mockDA.On("Validate", mock.Anything, batchData, proofs, Id).Return([]bool{true, false}, nil).Once() + mockDAVerifier.On("GetProofs", context.Background(), batchData, Id).Return(proofs, nil).Once() + mockDAVerifier.On("Validate", mock.Anything, batchData, proofs, Id).Return([]bool{true, false}, nil).Once() res, err := seq.VerifyBatch(context.Background(), coresequencer.VerifyBatchRequest{Id: seq.Id, BatchData: batchData}) assert.NoError(err) assert.NotNil(res) assert.False(res.Status, "Expected status to be false for invalid proof") - mockDA.AssertExpectations(t) + mockDAVerifier.AssertExpectations(t) }) t.Run("GetProofs Error", func(t *testing.T) { - mockDA := damocks.NewMockDA(t) + mockDAVerifier := damocks.NewMockVerifier(t) mockRetriever := new(MockForcedInclusionRetriever) mockRetriever.On("RetrieveForcedIncludedTxs", mock.Anything, mock.Anything). Return(nil, block.ErrForceInclusionNotConfigured).Maybe() db := ds.NewMapDatastore() seq := newTestSequencer(t, db, mockRetriever, false) - seq.da = mockDA + seq.daVerifier = mockDAVerifier defer db.Close() expectedErr := errors.New("get proofs failed") - mockDA.On("GetProofs", context.Background(), batchData, Id).Return(nil, expectedErr).Once() + mockDAVerifier.On("GetProofs", context.Background(), batchData, Id).Return(nil, expectedErr).Once() res, err := seq.VerifyBatch(context.Background(), coresequencer.VerifyBatchRequest{Id: seq.Id, BatchData: batchData}) assert.Error(err) assert.Nil(res) assert.Contains(err.Error(), expectedErr.Error()) - mockDA.AssertExpectations(t) - mockDA.AssertNotCalled(t, "Validate", mock.Anything, mock.Anything, mock.Anything) + mockDAVerifier.AssertExpectations(t) + mockDAVerifier.AssertNotCalled(t, "Validate", mock.Anything, mock.Anything, mock.Anything) }) t.Run("Validate Error", func(t *testing.T) { - mockDA := damocks.NewMockDA(t) + mockDAVerifier := damocks.NewMockVerifier(t) mockRetriever := new(MockForcedInclusionRetriever) mockRetriever.On("RetrieveForcedIncludedTxs", mock.Anything, mock.Anything). Return(nil, block.ErrForceInclusionNotConfigured).Maybe() db := ds.NewMapDatastore() seq := newTestSequencer(t, db, mockRetriever, false) - seq.da = mockDA + seq.daVerifier = mockDAVerifier defer db.Close() expectedErr := errors.New("validate failed") - mockDA.On("GetProofs", context.Background(), batchData, Id).Return(proofs, nil).Once() - mockDA.On("Validate", mock.Anything, batchData, proofs, Id).Return(nil, expectedErr).Once() + mockDAVerifier.On("GetProofs", context.Background(), batchData, Id).Return(proofs, nil).Once() + mockDAVerifier.On("Validate", mock.Anything, batchData, proofs, Id).Return(nil, expectedErr).Once() res, err := seq.VerifyBatch(context.Background(), coresequencer.VerifyBatchRequest{Id: seq.Id, BatchData: batchData}) assert.Error(err) assert.Nil(res) assert.Contains(err.Error(), expectedErr.Error()) - mockDA.AssertExpectations(t) + mockDAVerifier.AssertExpectations(t) }) t.Run("Invalid ID", func(t *testing.T) { - mockDA := damocks.NewMockDA(t) + mockDAVerifier := damocks.NewMockVerifier(t) mockRetriever := new(MockForcedInclusionRetriever) mockRetriever.On("RetrieveForcedIncludedTxs", mock.Anything, mock.Anything). Return(nil, block.ErrForceInclusionNotConfigured).Maybe() db := ds.NewMapDatastore() seq := newTestSequencer(t, db, mockRetriever, false) - seq.da = mockDA + seq.daVerifier = mockDAVerifier defer db.Close() invalidId := []byte("invalid") @@ -397,8 +401,8 @@ func TestSequencer_VerifyBatch(t *testing.T) { assert.Nil(res) assert.ErrorIs(err, ErrInvalidId) - mockDA.AssertNotCalled(t, "GetProofs", context.Background(), mock.Anything) - mockDA.AssertNotCalled(t, "Validate", mock.Anything, mock.Anything, mock.Anything) + mockDAVerifier.AssertNotCalled(t, "GetProofs", context.Background(), mock.Anything) + mockDAVerifier.AssertNotCalled(t, "Validate", mock.Anything, mock.Anything, mock.Anything) }) }) } @@ -406,7 +410,7 @@ func TestSequencer_VerifyBatch(t *testing.T) { func TestSequencer_GetNextBatch_BeforeDASubmission(t *testing.T) { t.Skip() // Initialize a new sequencer with mock DA - mockDA := &damocks.MockDA{} + mockDA := damocks.NewMockVerifier(t) db := ds.NewMapDatastore() ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) defer cancel() @@ -425,10 +429,6 @@ func TestSequencer_GetNextBatch_BeforeDASubmission(t *testing.T) { } }() - // Set up mock expectations - mockDA.On("Submit", mock.Anything, mock.Anything, mock.Anything, mock.Anything). - Return(nil, errors.New("mock DA always rejects submissions")) - // Submit a batch Id := []byte("test1") tx := []byte("transaction1") @@ -714,7 +714,7 @@ func TestSequencer_QueueLimit_Integration(t *testing.T) { db := ds.NewMapDatastore() defer db.Close() - mockDA := &damocks.MockDA{} + mockDA := damocks.NewMockVerifier(t) mockRetriever := new(MockForcedInclusionRetriever) mockRetriever.On("RetrieveForcedIncludedTxs", mock.Anything, mock.Anything). Return(nil, block.ErrForceInclusionNotConfigured).Maybe() @@ -839,9 +839,9 @@ func TestSequencer_DAFailureAndQueueThrottling_Integration(t *testing.T) { defer db.Close() // Create a dummy DA that we can make fail - dummyDA := coreda.NewDummyDA(100_000, 100*time.Millisecond) - dummyDA.StartHeightTicker() - defer dummyDA.StopHeightTicker() + dummyDA := newDummyDA(100_000) + stopTicker := dummyDA.StartHeightTicker(100 * time.Millisecond) + defer stopTicker() // Create sequencer with small queue size to trigger throttling quickly queueSize := 3 // Small for testing diff --git a/test/e2e/da_posting_integration_test.go b/test/e2e/da_posting_integration_test.go index bdaaae8b5e..f4b072ba77 100644 --- a/test/e2e/da_posting_integration_test.go +++ b/test/e2e/da_posting_integration_test.go @@ -29,10 +29,9 @@ import ( "github.com/cosmos/cosmos-sdk/x/bank" "github.com/cosmos/cosmos-sdk/x/gov" "github.com/cosmos/ibc-go/v8/modules/apps/transfer" - coreda "github.com/evstack/ev-node/core/da" - "github.com/evstack/ev-node/da/jsonrpc" - seqcommon "github.com/evstack/ev-node/sequencers/common" - "github.com/rs/zerolog" + libshare "github.com/celestiaorg/go-square/v3/share" + "github.com/evstack/ev-node/pkg/da/jsonrpc" + datypes "github.com/evstack/ev-node/pkg/da/types" "github.com/stretchr/testify/require" ) @@ -202,7 +201,7 @@ func TestEvNode_PostsToDA(t *testing.T) { daAddress := fmt.Sprintf("http://%s", bridgeNetInfo.Internal.RPCAddress()) headerNamespaceStr := "ev-header" dataNamespaceStr := "ev-data" - dataNamespace := coreda.NamespaceFromString(dataNamespaceStr) + dataNamespace := datypes.NamespaceFromString(dataNamespaceStr) require.NoError(t, evNode.Start(ctx, "--evnode.da.address", daAddress, @@ -241,7 +240,7 @@ func TestEvNode_PostsToDA(t *testing.T) { // 6) Assert data landed on DA via celestia-node blob RPC (namespace ev-data) daRPCAddr := fmt.Sprintf("http://%s", bridgeNetInfo.Internal.RPCAddress()) - daClient, err := jsonrpc.NewClient(ctx, zerolog.Nop(), daRPCAddr, authToken, seqcommon.AbsoluteMaxBlobSize) + daClient, err := jsonrpc.NewClient(ctx, daRPCAddr, authToken, "") require.NoError(t, err, "new da client") defer daClient.Close() @@ -275,17 +274,20 @@ func TestEvNode_PostsToDA(t *testing.T) { return false, nil }) + ns, err := libshare.NewNamespaceFromBytes(dataNamespace.Bytes()) + require.NoError(t, err, "create libshare namespace") + wait.ForCondition(ctx, time.Minute, 5*time.Second, func() (bool, error) { if pfbHeight == 0 { return false, nil } for h := pfbHeight; h <= pfbHeight+10; h++ { - ids, err := daClient.DA.GetIDs(ctx, uint64(h), dataNamespace.Bytes()) + blobs, err := daClient.Blob.GetAll(ctx, uint64(h), []libshare.Namespace{ns}) if err != nil { - t.Logf("GetIDs data height=%d err=%v", h, err) + t.Logf("GetAll data height=%d err=%v", h, err) continue } - if ids != nil && len(ids.IDs) > 0 { + if len(blobs) > 0 { return true, nil } } diff --git a/test/e2e/evm_full_node_e2e_test.go b/test/e2e/evm_full_node_e2e_test.go index b25c2302a9..b534eaacfd 100644 --- a/test/e2e/evm_full_node_e2e_test.go +++ b/test/e2e/evm_full_node_e2e_test.go @@ -424,27 +424,23 @@ func TestEvmSequencerWithFullNodeE2E(t *testing.T) { t.Logf("Full node block height before DA inclusion wait: %d", fnBlockHeightBeforeWait) - // Wait a few seconds to allow DA inclusion to process - waitTime := 4 * time.Second - t.Logf("Waiting %v for DA inclusion to process...", waitTime) - time.Sleep(waitTime) - - // Get the DA included height from full node after the wait - fnDAIncludedHeightBytes, err := fullNodeRPCClient.GetMetadata(fnCtx, store.DAIncludedHeightKey) - require.NoError(t, err, "Should get DA included height from full node") - - // Decode the DA included height - require.Equal(t, 8, len(fnDAIncludedHeightBytes), "DA included height should be 8 bytes") - fnDAIncludedHeight := binary.LittleEndian.Uint64(fnDAIncludedHeightBytes) + // Wait until DA inclusion metadata exists and has caught up. + var fnDAIncludedHeight uint64 + require.Eventually(t, func() bool { + fnDAIncludedHeightBytes, err := fullNodeRPCClient.GetMetadata(fnCtx, store.DAIncludedHeightKey) + if err != nil { + return false + } + if len(fnDAIncludedHeightBytes) != 8 { + return false + } + fnDAIncludedHeight = binary.LittleEndian.Uint64(fnDAIncludedHeightBytes) + return fnDAIncludedHeight >= fnBlockHeightBeforeWait + }, DefaultTestTimeout, 500*time.Millisecond, + "Full node DA included height should be >= block height before wait") t.Logf("After waiting, full node DA included height: %d", fnDAIncludedHeight) - // Verify that the DA included height is >= the full node's block height before wait - // This ensures that the blocks that existed before the wait have been DA included - require.GreaterOrEqual(t, fnDAIncludedHeight, fnBlockHeightBeforeWait, - "Full node DA included height (%d) should be >= block height before wait (%d)", - fnDAIncludedHeight, fnBlockHeightBeforeWait) - t.Logf("✅ DA inclusion verification passed:") t.Logf(" - Full node block height before wait: %d", fnBlockHeightBeforeWait) t.Logf(" - Full node DA included height after wait: %d", fnDAIncludedHeight) diff --git a/test/e2e/go.mod b/test/e2e/go.mod index 2baff6dd01..1b922eaa69 100644 --- a/test/e2e/go.mod +++ b/test/e2e/go.mod @@ -12,19 +12,15 @@ require ( github.com/cosmos/ibc-go/v8 v8.7.0 github.com/ethereum/go-ethereum v1.16.7 github.com/evstack/ev-node v1.0.0-beta.10 - github.com/evstack/ev-node/core v1.0.0-beta.5 - github.com/evstack/ev-node/da v0.0.0-00010101000000-000000000000 github.com/evstack/ev-node/execution/evm v0.0.0-20250602130019-2a732cf903a5 github.com/evstack/ev-node/execution/evm/test v0.0.0-00010101000000-000000000000 github.com/libp2p/go-libp2p v0.45.0 - github.com/rs/zerolog v1.34.0 github.com/stretchr/testify v1.11.1 ) replace ( github.com/evstack/ev-node => ../../ github.com/evstack/ev-node/core => ../../core - github.com/evstack/ev-node/da => ../../da github.com/evstack/ev-node/execution/evm => ../../execution/evm github.com/evstack/ev-node/execution/evm/test => ../../execution/evm/test ) @@ -102,6 +98,7 @@ require ( github.com/emicklei/dot v1.6.2 // indirect github.com/ethereum/c-kzg-4844/v2 v2.1.5 // indirect github.com/ethereum/go-verkle v0.2.2 // indirect + github.com/evstack/ev-node/core v1.0.0-beta.5 // indirect github.com/fatih/color v1.16.0 // indirect github.com/felixge/httpsnoop v1.0.4 // indirect github.com/ferranbt/fastssz v0.1.4 // indirect @@ -201,6 +198,7 @@ require ( github.com/rivo/uniseg v0.2.0 // indirect github.com/rogpeppe/go-internal v1.14.1 // indirect github.com/rs/cors v1.11.1 // indirect + github.com/rs/zerolog v1.34.0 // indirect github.com/sagikazarmark/locafero v0.11.0 // indirect github.com/sasha-s/go-deadlock v0.3.5 // indirect github.com/shirou/gopsutil v3.21.4-0.20210419000835-c7a38de76ee5+incompatible // indirect diff --git a/test/mocks/da.go b/test/mocks/da.go index bb3ad63391..c6df9b1c41 100644 --- a/test/mocks/da.go +++ b/test/mocks/da.go @@ -7,17 +7,17 @@ package mocks import ( "context" - "github.com/evstack/ev-node/core/da" + "github.com/evstack/ev-node/pkg/da/types" mock "github.com/stretchr/testify/mock" ) -// NewMockDA creates a new instance of MockDA. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. +// NewMockClient creates a new instance of MockClient. 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 NewMockDA(t interface { +func NewMockClient(t interface { mock.TestingT Cleanup(func()) -}) *MockDA { - mock := &MockDA{} +}) *MockClient { + mock := &MockClient{} mock.Mock.Test(t) t.Cleanup(func() { mock.AssertExpectations(t) }) @@ -25,69 +25,69 @@ func NewMockDA(t interface { return mock } -// MockDA is an autogenerated mock type for the DA type -type MockDA struct { +// MockClient is an autogenerated mock type for the Client type +type MockClient struct { mock.Mock } -type MockDA_Expecter struct { +type MockClient_Expecter struct { mock *mock.Mock } -func (_m *MockDA) EXPECT() *MockDA_Expecter { - return &MockDA_Expecter{mock: &_m.Mock} +func (_m *MockClient) EXPECT() *MockClient_Expecter { + return &MockClient_Expecter{mock: &_m.Mock} } -// Commit provides a mock function for the type MockDA -func (_mock *MockDA) Commit(ctx context.Context, blobs []da.Blob, namespace []byte) ([]da.Commitment, error) { - ret := _mock.Called(ctx, blobs, namespace) +// Get provides a mock function for the type MockClient +func (_mock *MockClient) Get(ctx context.Context, ids []da.ID, namespace []byte) ([]da.Blob, error) { + ret := _mock.Called(ctx, ids, namespace) if len(ret) == 0 { - panic("no return value specified for Commit") + panic("no return value specified for Get") } - var r0 []da.Commitment + var r0 []da.Blob var r1 error - if returnFunc, ok := ret.Get(0).(func(context.Context, []da.Blob, []byte) ([]da.Commitment, error)); ok { - return returnFunc(ctx, blobs, namespace) + if returnFunc, ok := ret.Get(0).(func(context.Context, []da.ID, []byte) ([]da.Blob, error)); ok { + return returnFunc(ctx, ids, namespace) } - if returnFunc, ok := ret.Get(0).(func(context.Context, []da.Blob, []byte) []da.Commitment); ok { - r0 = returnFunc(ctx, blobs, namespace) + if returnFunc, ok := ret.Get(0).(func(context.Context, []da.ID, []byte) []da.Blob); ok { + r0 = returnFunc(ctx, ids, namespace) } else { if ret.Get(0) != nil { - r0 = ret.Get(0).([]da.Commitment) + r0 = ret.Get(0).([]da.Blob) } } - if returnFunc, ok := ret.Get(1).(func(context.Context, []da.Blob, []byte) error); ok { - r1 = returnFunc(ctx, blobs, namespace) + if returnFunc, ok := ret.Get(1).(func(context.Context, []da.ID, []byte) error); ok { + r1 = returnFunc(ctx, ids, namespace) } else { r1 = ret.Error(1) } return r0, r1 } -// MockDA_Commit_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Commit' -type MockDA_Commit_Call struct { +// MockClient_Get_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Get' +type MockClient_Get_Call struct { *mock.Call } -// Commit is a helper method to define mock.On call +// Get is a helper method to define mock.On call // - ctx context.Context -// - blobs []da.Blob +// - ids []da.ID // - namespace []byte -func (_e *MockDA_Expecter) Commit(ctx interface{}, blobs interface{}, namespace interface{}) *MockDA_Commit_Call { - return &MockDA_Commit_Call{Call: _e.mock.On("Commit", ctx, blobs, namespace)} +func (_e *MockClient_Expecter) Get(ctx interface{}, ids interface{}, namespace interface{}) *MockClient_Get_Call { + return &MockClient_Get_Call{Call: _e.mock.On("Get", ctx, ids, namespace)} } -func (_c *MockDA_Commit_Call) Run(run func(ctx context.Context, blobs []da.Blob, namespace []byte)) *MockDA_Commit_Call { +func (_c *MockClient_Get_Call) Run(run func(ctx context.Context, ids []da.ID, namespace []byte)) *MockClient_Get_Call { _c.Call.Run(func(args mock.Arguments) { var arg0 context.Context if args[0] != nil { arg0 = args[0].(context.Context) } - var arg1 []da.Blob + var arg1 []da.ID if args[1] != nil { - arg1 = args[1].([]da.Blob) + arg1 = args[1].([]da.ID) } var arg2 []byte if args[2] != nil { @@ -102,480 +102,332 @@ func (_c *MockDA_Commit_Call) Run(run func(ctx context.Context, blobs []da.Blob, return _c } -func (_c *MockDA_Commit_Call) Return(vs []da.Commitment, err error) *MockDA_Commit_Call { +func (_c *MockClient_Get_Call) Return(vs []da.Blob, err error) *MockClient_Get_Call { _c.Call.Return(vs, err) return _c } -func (_c *MockDA_Commit_Call) RunAndReturn(run func(ctx context.Context, blobs []da.Blob, namespace []byte) ([]da.Commitment, error)) *MockDA_Commit_Call { +func (_c *MockClient_Get_Call) RunAndReturn(run func(ctx context.Context, ids []da.ID, namespace []byte) ([]da.Blob, error)) *MockClient_Get_Call { _c.Call.Return(run) return _c } -// Get provides a mock function for the type MockDA -func (_mock *MockDA) Get(ctx context.Context, ids []da.ID, namespace []byte) ([]da.Blob, error) { - ret := _mock.Called(ctx, ids, namespace) +// GetDataNamespace provides a mock function for the type MockClient +func (_mock *MockClient) GetDataNamespace() []byte { + ret := _mock.Called() if len(ret) == 0 { - panic("no return value specified for Get") + panic("no return value specified for GetDataNamespace") } - var r0 []da.Blob - var r1 error - if returnFunc, ok := ret.Get(0).(func(context.Context, []da.ID, []byte) ([]da.Blob, error)); ok { - return returnFunc(ctx, ids, namespace) - } - if returnFunc, ok := ret.Get(0).(func(context.Context, []da.ID, []byte) []da.Blob); ok { - r0 = returnFunc(ctx, ids, namespace) + var r0 []byte + if returnFunc, ok := ret.Get(0).(func() []byte); ok { + r0 = returnFunc() } else { if ret.Get(0) != nil { - r0 = ret.Get(0).([]da.Blob) + r0 = ret.Get(0).([]byte) } } - if returnFunc, ok := ret.Get(1).(func(context.Context, []da.ID, []byte) error); ok { - r1 = returnFunc(ctx, ids, namespace) - } else { - r1 = ret.Error(1) - } - return r0, r1 + return r0 } -// MockDA_Get_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Get' -type MockDA_Get_Call struct { +// MockClient_GetDataNamespace_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GetDataNamespace' +type MockClient_GetDataNamespace_Call struct { *mock.Call } -// Get is a helper method to define mock.On call -// - ctx context.Context -// - ids []da.ID -// - namespace []byte -func (_e *MockDA_Expecter) Get(ctx interface{}, ids interface{}, namespace interface{}) *MockDA_Get_Call { - return &MockDA_Get_Call{Call: _e.mock.On("Get", ctx, ids, namespace)} +// GetDataNamespace is a helper method to define mock.On call +func (_e *MockClient_Expecter) GetDataNamespace() *MockClient_GetDataNamespace_Call { + return &MockClient_GetDataNamespace_Call{Call: _e.mock.On("GetDataNamespace")} } -func (_c *MockDA_Get_Call) Run(run func(ctx context.Context, ids []da.ID, namespace []byte)) *MockDA_Get_Call { +func (_c *MockClient_GetDataNamespace_Call) Run(run func()) *MockClient_GetDataNamespace_Call { _c.Call.Run(func(args mock.Arguments) { - var arg0 context.Context - if args[0] != nil { - arg0 = args[0].(context.Context) - } - var arg1 []da.ID - if args[1] != nil { - arg1 = args[1].([]da.ID) - } - var arg2 []byte - if args[2] != nil { - arg2 = args[2].([]byte) - } - run( - arg0, - arg1, - arg2, - ) + run() }) return _c } -func (_c *MockDA_Get_Call) Return(vs []da.Blob, err error) *MockDA_Get_Call { - _c.Call.Return(vs, err) +func (_c *MockClient_GetDataNamespace_Call) Return(bytes []byte) *MockClient_GetDataNamespace_Call { + _c.Call.Return(bytes) return _c } -func (_c *MockDA_Get_Call) RunAndReturn(run func(ctx context.Context, ids []da.ID, namespace []byte) ([]da.Blob, error)) *MockDA_Get_Call { +func (_c *MockClient_GetDataNamespace_Call) RunAndReturn(run func() []byte) *MockClient_GetDataNamespace_Call { _c.Call.Return(run) return _c } -// GetIDs provides a mock function for the type MockDA -func (_mock *MockDA) GetIDs(ctx context.Context, height uint64, namespace []byte) (*da.GetIDsResult, error) { - ret := _mock.Called(ctx, height, namespace) +// GetForcedInclusionNamespace provides a mock function for the type MockClient +func (_mock *MockClient) GetForcedInclusionNamespace() []byte { + ret := _mock.Called() if len(ret) == 0 { - panic("no return value specified for GetIDs") + panic("no return value specified for GetForcedInclusionNamespace") } - var r0 *da.GetIDsResult - var r1 error - if returnFunc, ok := ret.Get(0).(func(context.Context, uint64, []byte) (*da.GetIDsResult, error)); ok { - return returnFunc(ctx, height, namespace) - } - if returnFunc, ok := ret.Get(0).(func(context.Context, uint64, []byte) *da.GetIDsResult); ok { - r0 = returnFunc(ctx, height, namespace) + var r0 []byte + if returnFunc, ok := ret.Get(0).(func() []byte); ok { + r0 = returnFunc() } else { if ret.Get(0) != nil { - r0 = ret.Get(0).(*da.GetIDsResult) + r0 = ret.Get(0).([]byte) } } - if returnFunc, ok := ret.Get(1).(func(context.Context, uint64, []byte) error); ok { - r1 = returnFunc(ctx, height, namespace) - } else { - r1 = ret.Error(1) - } - return r0, r1 + return r0 } -// MockDA_GetIDs_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GetIDs' -type MockDA_GetIDs_Call struct { +// MockClient_GetForcedInclusionNamespace_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GetForcedInclusionNamespace' +type MockClient_GetForcedInclusionNamespace_Call struct { *mock.Call } -// GetIDs is a helper method to define mock.On call -// - ctx context.Context -// - height uint64 -// - namespace []byte -func (_e *MockDA_Expecter) GetIDs(ctx interface{}, height interface{}, namespace interface{}) *MockDA_GetIDs_Call { - return &MockDA_GetIDs_Call{Call: _e.mock.On("GetIDs", ctx, height, namespace)} +// GetForcedInclusionNamespace is a helper method to define mock.On call +func (_e *MockClient_Expecter) GetForcedInclusionNamespace() *MockClient_GetForcedInclusionNamespace_Call { + return &MockClient_GetForcedInclusionNamespace_Call{Call: _e.mock.On("GetForcedInclusionNamespace")} } -func (_c *MockDA_GetIDs_Call) Run(run func(ctx context.Context, height uint64, namespace []byte)) *MockDA_GetIDs_Call { +func (_c *MockClient_GetForcedInclusionNamespace_Call) Run(run func()) *MockClient_GetForcedInclusionNamespace_Call { _c.Call.Run(func(args mock.Arguments) { - var arg0 context.Context - if args[0] != nil { - arg0 = args[0].(context.Context) - } - var arg1 uint64 - if args[1] != nil { - arg1 = args[1].(uint64) - } - var arg2 []byte - if args[2] != nil { - arg2 = args[2].([]byte) - } - run( - arg0, - arg1, - arg2, - ) + run() }) return _c } -func (_c *MockDA_GetIDs_Call) Return(getIDsResult *da.GetIDsResult, err error) *MockDA_GetIDs_Call { - _c.Call.Return(getIDsResult, err) +func (_c *MockClient_GetForcedInclusionNamespace_Call) Return(bytes []byte) *MockClient_GetForcedInclusionNamespace_Call { + _c.Call.Return(bytes) return _c } -func (_c *MockDA_GetIDs_Call) RunAndReturn(run func(ctx context.Context, height uint64, namespace []byte) (*da.GetIDsResult, error)) *MockDA_GetIDs_Call { +func (_c *MockClient_GetForcedInclusionNamespace_Call) RunAndReturn(run func() []byte) *MockClient_GetForcedInclusionNamespace_Call { _c.Call.Return(run) return _c } -// GetProofs provides a mock function for the type MockDA -func (_mock *MockDA) GetProofs(ctx context.Context, ids []da.ID, namespace []byte) ([]da.Proof, error) { - ret := _mock.Called(ctx, ids, namespace) +// GetHeaderNamespace provides a mock function for the type MockClient +func (_mock *MockClient) GetHeaderNamespace() []byte { + ret := _mock.Called() if len(ret) == 0 { - panic("no return value specified for GetProofs") + panic("no return value specified for GetHeaderNamespace") } - var r0 []da.Proof - var r1 error - if returnFunc, ok := ret.Get(0).(func(context.Context, []da.ID, []byte) ([]da.Proof, error)); ok { - return returnFunc(ctx, ids, namespace) - } - if returnFunc, ok := ret.Get(0).(func(context.Context, []da.ID, []byte) []da.Proof); ok { - r0 = returnFunc(ctx, ids, namespace) + var r0 []byte + if returnFunc, ok := ret.Get(0).(func() []byte); ok { + r0 = returnFunc() } else { if ret.Get(0) != nil { - r0 = ret.Get(0).([]da.Proof) + r0 = ret.Get(0).([]byte) } } - if returnFunc, ok := ret.Get(1).(func(context.Context, []da.ID, []byte) error); ok { - r1 = returnFunc(ctx, ids, namespace) - } else { - r1 = ret.Error(1) - } - return r0, r1 + return r0 } -// MockDA_GetProofs_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GetProofs' -type MockDA_GetProofs_Call struct { +// MockClient_GetHeaderNamespace_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GetHeaderNamespace' +type MockClient_GetHeaderNamespace_Call struct { *mock.Call } -// GetProofs is a helper method to define mock.On call -// - ctx context.Context -// - ids []da.ID -// - namespace []byte -func (_e *MockDA_Expecter) GetProofs(ctx interface{}, ids interface{}, namespace interface{}) *MockDA_GetProofs_Call { - return &MockDA_GetProofs_Call{Call: _e.mock.On("GetProofs", ctx, ids, namespace)} +// GetHeaderNamespace is a helper method to define mock.On call +func (_e *MockClient_Expecter) GetHeaderNamespace() *MockClient_GetHeaderNamespace_Call { + return &MockClient_GetHeaderNamespace_Call{Call: _e.mock.On("GetHeaderNamespace")} } -func (_c *MockDA_GetProofs_Call) Run(run func(ctx context.Context, ids []da.ID, namespace []byte)) *MockDA_GetProofs_Call { +func (_c *MockClient_GetHeaderNamespace_Call) Run(run func()) *MockClient_GetHeaderNamespace_Call { _c.Call.Run(func(args mock.Arguments) { - var arg0 context.Context - if args[0] != nil { - arg0 = args[0].(context.Context) - } - var arg1 []da.ID - if args[1] != nil { - arg1 = args[1].([]da.ID) - } - var arg2 []byte - if args[2] != nil { - arg2 = args[2].([]byte) - } - run( - arg0, - arg1, - arg2, - ) + run() }) return _c } -func (_c *MockDA_GetProofs_Call) Return(vs []da.Proof, err error) *MockDA_GetProofs_Call { - _c.Call.Return(vs, err) +func (_c *MockClient_GetHeaderNamespace_Call) Return(bytes []byte) *MockClient_GetHeaderNamespace_Call { + _c.Call.Return(bytes) return _c } -func (_c *MockDA_GetProofs_Call) RunAndReturn(run func(ctx context.Context, ids []da.ID, namespace []byte) ([]da.Proof, error)) *MockDA_GetProofs_Call { +func (_c *MockClient_GetHeaderNamespace_Call) RunAndReturn(run func() []byte) *MockClient_GetHeaderNamespace_Call { _c.Call.Return(run) return _c } -// Submit provides a mock function for the type MockDA -func (_mock *MockDA) Submit(ctx context.Context, blobs []da.Blob, gasPrice float64, namespace []byte) ([]da.ID, error) { - ret := _mock.Called(ctx, blobs, gasPrice, namespace) +// HasForcedInclusionNamespace provides a mock function for the type MockClient +func (_mock *MockClient) HasForcedInclusionNamespace() bool { + ret := _mock.Called() if len(ret) == 0 { - panic("no return value specified for Submit") + panic("no return value specified for HasForcedInclusionNamespace") } - var r0 []da.ID - var r1 error - if returnFunc, ok := ret.Get(0).(func(context.Context, []da.Blob, float64, []byte) ([]da.ID, error)); ok { - return returnFunc(ctx, blobs, gasPrice, namespace) - } - if returnFunc, ok := ret.Get(0).(func(context.Context, []da.Blob, float64, []byte) []da.ID); ok { - r0 = returnFunc(ctx, blobs, gasPrice, namespace) - } else { - if ret.Get(0) != nil { - r0 = ret.Get(0).([]da.ID) - } - } - if returnFunc, ok := ret.Get(1).(func(context.Context, []da.Blob, float64, []byte) error); ok { - r1 = returnFunc(ctx, blobs, gasPrice, namespace) + var r0 bool + if returnFunc, ok := ret.Get(0).(func() bool); ok { + r0 = returnFunc() } else { - r1 = ret.Error(1) + r0 = ret.Get(0).(bool) } - return r0, r1 + return r0 } -// MockDA_Submit_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Submit' -type MockDA_Submit_Call struct { +// MockClient_HasForcedInclusionNamespace_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'HasForcedInclusionNamespace' +type MockClient_HasForcedInclusionNamespace_Call struct { *mock.Call } -// Submit is a helper method to define mock.On call -// - ctx context.Context -// - blobs []da.Blob -// - gasPrice float64 -// - namespace []byte -func (_e *MockDA_Expecter) Submit(ctx interface{}, blobs interface{}, gasPrice interface{}, namespace interface{}) *MockDA_Submit_Call { - return &MockDA_Submit_Call{Call: _e.mock.On("Submit", ctx, blobs, gasPrice, namespace)} +// HasForcedInclusionNamespace is a helper method to define mock.On call +func (_e *MockClient_Expecter) HasForcedInclusionNamespace() *MockClient_HasForcedInclusionNamespace_Call { + return &MockClient_HasForcedInclusionNamespace_Call{Call: _e.mock.On("HasForcedInclusionNamespace")} } -func (_c *MockDA_Submit_Call) Run(run func(ctx context.Context, blobs []da.Blob, gasPrice float64, namespace []byte)) *MockDA_Submit_Call { +func (_c *MockClient_HasForcedInclusionNamespace_Call) Run(run func()) *MockClient_HasForcedInclusionNamespace_Call { _c.Call.Run(func(args mock.Arguments) { - var arg0 context.Context - if args[0] != nil { - arg0 = args[0].(context.Context) - } - var arg1 []da.Blob - if args[1] != nil { - arg1 = args[1].([]da.Blob) - } - var arg2 float64 - if args[2] != nil { - arg2 = args[2].(float64) - } - var arg3 []byte - if args[3] != nil { - arg3 = args[3].([]byte) - } - run( - arg0, - arg1, - arg2, - arg3, - ) + run() }) return _c } -func (_c *MockDA_Submit_Call) Return(vs []da.ID, err error) *MockDA_Submit_Call { - _c.Call.Return(vs, err) +func (_c *MockClient_HasForcedInclusionNamespace_Call) Return(b bool) *MockClient_HasForcedInclusionNamespace_Call { + _c.Call.Return(b) return _c } -func (_c *MockDA_Submit_Call) RunAndReturn(run func(ctx context.Context, blobs []da.Blob, gasPrice float64, namespace []byte) ([]da.ID, error)) *MockDA_Submit_Call { +func (_c *MockClient_HasForcedInclusionNamespace_Call) RunAndReturn(run func() bool) *MockClient_HasForcedInclusionNamespace_Call { _c.Call.Return(run) return _c } -// SubmitWithOptions provides a mock function for the type MockDA -func (_mock *MockDA) SubmitWithOptions(ctx context.Context, blobs []da.Blob, gasPrice float64, namespace []byte, options []byte) ([]da.ID, error) { - ret := _mock.Called(ctx, blobs, gasPrice, namespace, options) +// Retrieve provides a mock function for the type MockClient +func (_mock *MockClient) Retrieve(ctx context.Context, height uint64, namespace []byte) da.ResultRetrieve { + ret := _mock.Called(ctx, height, namespace) if len(ret) == 0 { - panic("no return value specified for SubmitWithOptions") + panic("no return value specified for Retrieve") } - var r0 []da.ID - var r1 error - if returnFunc, ok := ret.Get(0).(func(context.Context, []da.Blob, float64, []byte, []byte) ([]da.ID, error)); ok { - return returnFunc(ctx, blobs, gasPrice, namespace, options) - } - if returnFunc, ok := ret.Get(0).(func(context.Context, []da.Blob, float64, []byte, []byte) []da.ID); ok { - r0 = returnFunc(ctx, blobs, gasPrice, namespace, options) - } else { - if ret.Get(0) != nil { - r0 = ret.Get(0).([]da.ID) - } - } - if returnFunc, ok := ret.Get(1).(func(context.Context, []da.Blob, float64, []byte, []byte) error); ok { - r1 = returnFunc(ctx, blobs, gasPrice, namespace, options) + var r0 da.ResultRetrieve + if returnFunc, ok := ret.Get(0).(func(context.Context, uint64, []byte) da.ResultRetrieve); ok { + r0 = returnFunc(ctx, height, namespace) } else { - r1 = ret.Error(1) + r0 = ret.Get(0).(da.ResultRetrieve) } - return r0, r1 + return r0 } -// MockDA_SubmitWithOptions_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'SubmitWithOptions' -type MockDA_SubmitWithOptions_Call struct { +// MockClient_Retrieve_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Retrieve' +type MockClient_Retrieve_Call struct { *mock.Call } -// SubmitWithOptions is a helper method to define mock.On call +// Retrieve is a helper method to define mock.On call // - ctx context.Context -// - blobs []da.Blob -// - gasPrice float64 +// - height uint64 // - namespace []byte -// - options []byte -func (_e *MockDA_Expecter) SubmitWithOptions(ctx interface{}, blobs interface{}, gasPrice interface{}, namespace interface{}, options interface{}) *MockDA_SubmitWithOptions_Call { - return &MockDA_SubmitWithOptions_Call{Call: _e.mock.On("SubmitWithOptions", ctx, blobs, gasPrice, namespace, options)} +func (_e *MockClient_Expecter) Retrieve(ctx interface{}, height interface{}, namespace interface{}) *MockClient_Retrieve_Call { + return &MockClient_Retrieve_Call{Call: _e.mock.On("Retrieve", ctx, height, namespace)} } -func (_c *MockDA_SubmitWithOptions_Call) Run(run func(ctx context.Context, blobs []da.Blob, gasPrice float64, namespace []byte, options []byte)) *MockDA_SubmitWithOptions_Call { +func (_c *MockClient_Retrieve_Call) Run(run func(ctx context.Context, height uint64, namespace []byte)) *MockClient_Retrieve_Call { _c.Call.Run(func(args mock.Arguments) { var arg0 context.Context if args[0] != nil { arg0 = args[0].(context.Context) } - var arg1 []da.Blob + var arg1 uint64 if args[1] != nil { - arg1 = args[1].([]da.Blob) + arg1 = args[1].(uint64) } - var arg2 float64 + var arg2 []byte if args[2] != nil { - arg2 = args[2].(float64) - } - var arg3 []byte - if args[3] != nil { - arg3 = args[3].([]byte) - } - var arg4 []byte - if args[4] != nil { - arg4 = args[4].([]byte) + arg2 = args[2].([]byte) } run( arg0, arg1, arg2, - arg3, - arg4, ) }) return _c } -func (_c *MockDA_SubmitWithOptions_Call) Return(vs []da.ID, err error) *MockDA_SubmitWithOptions_Call { - _c.Call.Return(vs, err) +func (_c *MockClient_Retrieve_Call) Return(resultRetrieve da.ResultRetrieve) *MockClient_Retrieve_Call { + _c.Call.Return(resultRetrieve) return _c } -func (_c *MockDA_SubmitWithOptions_Call) RunAndReturn(run func(ctx context.Context, blobs []da.Blob, gasPrice float64, namespace []byte, options []byte) ([]da.ID, error)) *MockDA_SubmitWithOptions_Call { +func (_c *MockClient_Retrieve_Call) RunAndReturn(run func(ctx context.Context, height uint64, namespace []byte) da.ResultRetrieve) *MockClient_Retrieve_Call { _c.Call.Return(run) return _c } -// Validate provides a mock function for the type MockDA -func (_mock *MockDA) Validate(ctx context.Context, ids []da.ID, proofs []da.Proof, namespace []byte) ([]bool, error) { - ret := _mock.Called(ctx, ids, proofs, namespace) +// Submit provides a mock function for the type MockClient +func (_mock *MockClient) Submit(ctx context.Context, data [][]byte, gasPrice float64, namespace []byte, options []byte) da.ResultSubmit { + ret := _mock.Called(ctx, data, gasPrice, namespace, options) if len(ret) == 0 { - panic("no return value specified for Validate") + panic("no return value specified for Submit") } - var r0 []bool - var r1 error - if returnFunc, ok := ret.Get(0).(func(context.Context, []da.ID, []da.Proof, []byte) ([]bool, error)); ok { - return returnFunc(ctx, ids, proofs, namespace) - } - if returnFunc, ok := ret.Get(0).(func(context.Context, []da.ID, []da.Proof, []byte) []bool); ok { - r0 = returnFunc(ctx, ids, proofs, namespace) - } else { - if ret.Get(0) != nil { - r0 = ret.Get(0).([]bool) - } - } - if returnFunc, ok := ret.Get(1).(func(context.Context, []da.ID, []da.Proof, []byte) error); ok { - r1 = returnFunc(ctx, ids, proofs, namespace) + var r0 da.ResultSubmit + if returnFunc, ok := ret.Get(0).(func(context.Context, [][]byte, float64, []byte, []byte) da.ResultSubmit); ok { + r0 = returnFunc(ctx, data, gasPrice, namespace, options) } else { - r1 = ret.Error(1) + r0 = ret.Get(0).(da.ResultSubmit) } - return r0, r1 + return r0 } -// MockDA_Validate_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Validate' -type MockDA_Validate_Call struct { +// MockClient_Submit_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Submit' +type MockClient_Submit_Call struct { *mock.Call } -// Validate is a helper method to define mock.On call +// Submit is a helper method to define mock.On call // - ctx context.Context -// - ids []da.ID -// - proofs []da.Proof +// - data [][]byte +// - gasPrice float64 // - namespace []byte -func (_e *MockDA_Expecter) Validate(ctx interface{}, ids interface{}, proofs interface{}, namespace interface{}) *MockDA_Validate_Call { - return &MockDA_Validate_Call{Call: _e.mock.On("Validate", ctx, ids, proofs, namespace)} +// - options []byte +func (_e *MockClient_Expecter) Submit(ctx interface{}, data interface{}, gasPrice interface{}, namespace interface{}, options interface{}) *MockClient_Submit_Call { + return &MockClient_Submit_Call{Call: _e.mock.On("Submit", ctx, data, gasPrice, namespace, options)} } -func (_c *MockDA_Validate_Call) Run(run func(ctx context.Context, ids []da.ID, proofs []da.Proof, namespace []byte)) *MockDA_Validate_Call { +func (_c *MockClient_Submit_Call) Run(run func(ctx context.Context, data [][]byte, gasPrice float64, namespace []byte, options []byte)) *MockClient_Submit_Call { _c.Call.Run(func(args mock.Arguments) { var arg0 context.Context if args[0] != nil { arg0 = args[0].(context.Context) } - var arg1 []da.ID + var arg1 [][]byte if args[1] != nil { - arg1 = args[1].([]da.ID) + arg1 = args[1].([][]byte) } - var arg2 []da.Proof + var arg2 float64 if args[2] != nil { - arg2 = args[2].([]da.Proof) + arg2 = args[2].(float64) } var arg3 []byte if args[3] != nil { arg3 = args[3].([]byte) } + var arg4 []byte + if args[4] != nil { + arg4 = args[4].([]byte) + } run( arg0, arg1, arg2, arg3, + arg4, ) }) return _c } -func (_c *MockDA_Validate_Call) Return(bools []bool, err error) *MockDA_Validate_Call { - _c.Call.Return(bools, err) +func (_c *MockClient_Submit_Call) Return(resultSubmit da.ResultSubmit) *MockClient_Submit_Call { + _c.Call.Return(resultSubmit) return _c } -func (_c *MockDA_Validate_Call) RunAndReturn(run func(ctx context.Context, ids []da.ID, proofs []da.Proof, namespace []byte) ([]bool, error)) *MockDA_Validate_Call { +func (_c *MockClient_Submit_Call) RunAndReturn(run func(ctx context.Context, data [][]byte, gasPrice float64, namespace []byte, options []byte) da.ResultSubmit) *MockClient_Submit_Call { _c.Call.Return(run) return _c } diff --git a/test/mocks/da_verifier.go b/test/mocks/da_verifier.go new file mode 100644 index 0000000000..fe64098bec --- /dev/null +++ b/test/mocks/da_verifier.go @@ -0,0 +1,193 @@ +// Code generated by mockery; DO NOT EDIT. +// github.com/vektra/mockery +// template: testify + +package mocks + +import ( + "context" + + "github.com/evstack/ev-node/pkg/da/types" + mock "github.com/stretchr/testify/mock" +) + +// NewMockVerifier creates a new instance of MockVerifier. 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 NewMockVerifier(t interface { + mock.TestingT + Cleanup(func()) +}) *MockVerifier { + mock := &MockVerifier{} + mock.Mock.Test(t) + + t.Cleanup(func() { mock.AssertExpectations(t) }) + + return mock +} + +// MockVerifier is an autogenerated mock type for the Verifier type +type MockVerifier struct { + mock.Mock +} + +type MockVerifier_Expecter struct { + mock *mock.Mock +} + +func (_m *MockVerifier) EXPECT() *MockVerifier_Expecter { + return &MockVerifier_Expecter{mock: &_m.Mock} +} + +// GetProofs provides a mock function for the type MockVerifier +func (_mock *MockVerifier) GetProofs(ctx context.Context, ids []da.ID, namespace []byte) ([]da.Proof, error) { + ret := _mock.Called(ctx, ids, namespace) + + if len(ret) == 0 { + panic("no return value specified for GetProofs") + } + + var r0 []da.Proof + var r1 error + if returnFunc, ok := ret.Get(0).(func(context.Context, []da.ID, []byte) ([]da.Proof, error)); ok { + return returnFunc(ctx, ids, namespace) + } + if returnFunc, ok := ret.Get(0).(func(context.Context, []da.ID, []byte) []da.Proof); ok { + r0 = returnFunc(ctx, ids, namespace) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).([]da.Proof) + } + } + if returnFunc, ok := ret.Get(1).(func(context.Context, []da.ID, []byte) error); ok { + r1 = returnFunc(ctx, ids, namespace) + } else { + r1 = ret.Error(1) + } + return r0, r1 +} + +// MockVerifier_GetProofs_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GetProofs' +type MockVerifier_GetProofs_Call struct { + *mock.Call +} + +// GetProofs is a helper method to define mock.On call +// - ctx context.Context +// - ids []da.ID +// - namespace []byte +func (_e *MockVerifier_Expecter) GetProofs(ctx interface{}, ids interface{}, namespace interface{}) *MockVerifier_GetProofs_Call { + return &MockVerifier_GetProofs_Call{Call: _e.mock.On("GetProofs", ctx, ids, namespace)} +} + +func (_c *MockVerifier_GetProofs_Call) Run(run func(ctx context.Context, ids []da.ID, namespace []byte)) *MockVerifier_GetProofs_Call { + _c.Call.Run(func(args mock.Arguments) { + var arg0 context.Context + if args[0] != nil { + arg0 = args[0].(context.Context) + } + var arg1 []da.ID + if args[1] != nil { + arg1 = args[1].([]da.ID) + } + var arg2 []byte + if args[2] != nil { + arg2 = args[2].([]byte) + } + run( + arg0, + arg1, + arg2, + ) + }) + return _c +} + +func (_c *MockVerifier_GetProofs_Call) Return(vs []da.Proof, err error) *MockVerifier_GetProofs_Call { + _c.Call.Return(vs, err) + return _c +} + +func (_c *MockVerifier_GetProofs_Call) RunAndReturn(run func(ctx context.Context, ids []da.ID, namespace []byte) ([]da.Proof, error)) *MockVerifier_GetProofs_Call { + _c.Call.Return(run) + return _c +} + +// Validate provides a mock function for the type MockVerifier +func (_mock *MockVerifier) Validate(ctx context.Context, ids []da.ID, proofs []da.Proof, namespace []byte) ([]bool, error) { + ret := _mock.Called(ctx, ids, proofs, namespace) + + if len(ret) == 0 { + panic("no return value specified for Validate") + } + + var r0 []bool + var r1 error + if returnFunc, ok := ret.Get(0).(func(context.Context, []da.ID, []da.Proof, []byte) ([]bool, error)); ok { + return returnFunc(ctx, ids, proofs, namespace) + } + if returnFunc, ok := ret.Get(0).(func(context.Context, []da.ID, []da.Proof, []byte) []bool); ok { + r0 = returnFunc(ctx, ids, proofs, namespace) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).([]bool) + } + } + if returnFunc, ok := ret.Get(1).(func(context.Context, []da.ID, []da.Proof, []byte) error); ok { + r1 = returnFunc(ctx, ids, proofs, namespace) + } else { + r1 = ret.Error(1) + } + return r0, r1 +} + +// MockVerifier_Validate_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Validate' +type MockVerifier_Validate_Call struct { + *mock.Call +} + +// Validate is a helper method to define mock.On call +// - ctx context.Context +// - ids []da.ID +// - proofs []da.Proof +// - namespace []byte +func (_e *MockVerifier_Expecter) Validate(ctx interface{}, ids interface{}, proofs interface{}, namespace interface{}) *MockVerifier_Validate_Call { + return &MockVerifier_Validate_Call{Call: _e.mock.On("Validate", ctx, ids, proofs, namespace)} +} + +func (_c *MockVerifier_Validate_Call) Run(run func(ctx context.Context, ids []da.ID, proofs []da.Proof, namespace []byte)) *MockVerifier_Validate_Call { + _c.Call.Run(func(args mock.Arguments) { + var arg0 context.Context + if args[0] != nil { + arg0 = args[0].(context.Context) + } + var arg1 []da.ID + if args[1] != nil { + arg1 = args[1].([]da.ID) + } + var arg2 []da.Proof + if args[2] != nil { + arg2 = args[2].([]da.Proof) + } + var arg3 []byte + if args[3] != nil { + arg3 = args[3].([]byte) + } + run( + arg0, + arg1, + arg2, + arg3, + ) + }) + return _c +} + +func (_c *MockVerifier_Validate_Call) Return(bools []bool, err error) *MockVerifier_Validate_Call { + _c.Call.Return(bools, err) + return _c +} + +func (_c *MockVerifier_Validate_Call) RunAndReturn(run func(ctx context.Context, ids []da.ID, proofs []da.Proof, namespace []byte) ([]bool, error)) *MockVerifier_Validate_Call { + _c.Call.Return(run) + return _c +} diff --git a/test/testda/dummy.go b/test/testda/dummy.go new file mode 100644 index 0000000000..ecfa5cc85f --- /dev/null +++ b/test/testda/dummy.go @@ -0,0 +1,236 @@ +// Package testda provides test implementations of the DA client interface. +package testda + +import ( + "context" + "sync" + "sync/atomic" + "time" + + datypes "github.com/evstack/ev-node/pkg/da/types" +) + +const ( + // DefaultMaxBlobSize is the default maximum blob size (2MB). + DefaultMaxBlobSize = 2 * 1024 * 1024 +) + +// DummyDA is a test implementation of the DA client interface. +// It supports blob storage, height simulation, and failure injection. +type DummyDA struct { + mu sync.Mutex + height atomic.Uint64 + maxBlobSz uint64 + blobs map[uint64]map[string][][]byte // height -> namespace -> blobs + failSubmit atomic.Bool + + tickerMu sync.Mutex + tickerStop chan struct{} +} + +// Option configures a DummyDA instance. +type Option func(*DummyDA) + +// WithMaxBlobSize sets the maximum blob size. +func WithMaxBlobSize(size uint64) Option { + return func(d *DummyDA) { + d.maxBlobSz = size + } +} + +// WithStartHeight sets the initial DA height. +func WithStartHeight(height uint64) Option { + return func(d *DummyDA) { + d.height.Store(height) + } +} + +// New creates a new DummyDA with the given options. +func New(opts ...Option) *DummyDA { + d := &DummyDA{ + maxBlobSz: DefaultMaxBlobSize, + blobs: make(map[uint64]map[string][][]byte), + } + for _, opt := range opts { + opt(d) + } + return d +} + +// Submit stores blobs and returns success or simulated failure. +func (d *DummyDA) Submit(_ context.Context, data [][]byte, _ float64, namespace []byte, _ []byte) datypes.ResultSubmit { + if d.failSubmit.Load() { + return datypes.ResultSubmit{ + BaseResult: datypes.BaseResult{ + Code: datypes.StatusError, + Message: "simulated DA failure", + }, + } + } + + var blobSz uint64 + for _, b := range data { + if uint64(len(b)) > d.maxBlobSz { + return datypes.ResultSubmit{ + BaseResult: datypes.BaseResult{ + Code: datypes.StatusTooBig, + Message: datypes.ErrBlobSizeOverLimit.Error(), + }, + } + } + blobSz += uint64(len(b)) + } + + d.mu.Lock() + height := d.height.Add(1) + if d.blobs[height] == nil { + d.blobs[height] = make(map[string][][]byte) + } + nsKey := string(namespace) + d.blobs[height][nsKey] = append(d.blobs[height][nsKey], data...) + d.mu.Unlock() + + return datypes.ResultSubmit{ + BaseResult: datypes.BaseResult{ + Code: datypes.StatusSuccess, + Height: height, + BlobSize: blobSz, + SubmittedCount: uint64(len(data)), + Timestamp: time.Now(), + }, + } +} + +// Retrieve returns blobs stored at the given height and namespace. +func (d *DummyDA) Retrieve(_ context.Context, height uint64, namespace []byte) datypes.ResultRetrieve { + d.mu.Lock() + byHeight := d.blobs[height] + var blobs [][]byte + if byHeight != nil { + blobs = byHeight[string(namespace)] + } + d.mu.Unlock() + + if len(blobs) == 0 { + return datypes.ResultRetrieve{ + BaseResult: datypes.BaseResult{ + Code: datypes.StatusNotFound, + Height: height, + Message: datypes.ErrBlobNotFound.Error(), + Timestamp: time.Now(), + }, + } + } + + ids := make([][]byte, len(blobs)) + return datypes.ResultRetrieve{ + BaseResult: datypes.BaseResult{ + Code: datypes.StatusSuccess, + Height: height, + IDs: ids, + Timestamp: time.Now(), + }, + Data: blobs, + } +} + +// GetHeaderNamespace returns the header namespace. +func (d *DummyDA) GetHeaderNamespace() []byte { return []byte("hdr") } + +// GetDataNamespace returns the data namespace. +func (d *DummyDA) GetDataNamespace() []byte { return []byte("data") } + +// GetForcedInclusionNamespace returns the forced inclusion namespace. +func (d *DummyDA) GetForcedInclusionNamespace() []byte { return nil } + +// HasForcedInclusionNamespace reports whether forced inclusion is configured. +func (d *DummyDA) HasForcedInclusionNamespace() bool { return false } + +// Get retrieves blobs by ID (stub implementation). +func (d *DummyDA) Get(_ context.Context, _ []datypes.ID, _ []byte) ([]datypes.Blob, error) { + return nil, nil +} + +// GetProofs returns inclusion proofs (stub implementation). +func (d *DummyDA) GetProofs(_ context.Context, _ []datypes.ID, _ []byte) ([]datypes.Proof, error) { + return nil, nil +} + +// Validate validates inclusion proofs. +func (d *DummyDA) Validate(_ context.Context, ids []datypes.ID, _ []datypes.Proof, _ []byte) ([]bool, error) { + res := make([]bool, len(ids)) + for i := range res { + res[i] = true + } + return res, nil +} + +// SetSubmitFailure controls whether Submit should return errors. +func (d *DummyDA) SetSubmitFailure(shouldFail bool) { + d.failSubmit.Store(shouldFail) +} + +// Height returns the current DA height. +func (d *DummyDA) Height() uint64 { + return d.height.Load() +} + +// SetHeight sets the DA height directly. +func (d *DummyDA) SetHeight(h uint64) { + d.height.Store(h) +} + +// StartHeightTicker starts a goroutine that increments height at the given interval. +// Returns a stop function that should be called to stop the ticker. +func (d *DummyDA) StartHeightTicker(interval time.Duration) func() { + if interval == 0 { + return func() {} + } + + d.tickerMu.Lock() + if d.tickerStop != nil { + d.tickerMu.Unlock() + return func() {} // already running + } + d.tickerStop = make(chan struct{}) + stopCh := d.tickerStop + d.tickerMu.Unlock() + + go func() { + ticker := time.NewTicker(interval) + defer ticker.Stop() + for { + select { + case <-ticker.C: + d.height.Add(1) + case <-stopCh: + return + } + } + }() + + return func() { + d.tickerMu.Lock() + defer d.tickerMu.Unlock() + if d.tickerStop != nil { + close(d.tickerStop) + d.tickerStop = nil + } + } +} + +// Reset clears all stored blobs and resets the height. +func (d *DummyDA) Reset() { + d.mu.Lock() + d.height.Store(0) + d.blobs = make(map[uint64]map[string][][]byte) + d.failSubmit.Store(false) + d.mu.Unlock() + + d.tickerMu.Lock() + if d.tickerStop != nil { + close(d.tickerStop) + d.tickerStop = nil + } + d.tickerMu.Unlock() +} diff --git a/tools/da-debug/go.mod b/tools/da-debug/go.mod index d8081e329d..23e9f8b6f7 100644 --- a/tools/da-debug/go.mod +++ b/tools/da-debug/go.mod @@ -3,16 +3,17 @@ module github.com/evstack/ev-node/tools/da-debug go 1.24.6 require ( + github.com/celestiaorg/go-square/v3 v3.0.2 github.com/evstack/ev-node v1.0.0-beta.6 - github.com/evstack/ev-node/core v1.0.0-beta.5 - github.com/evstack/ev-node/da v1.0.0-beta.6 github.com/rs/zerolog v1.34.0 - github.com/spf13/cobra v1.10.1 - google.golang.org/protobuf v1.36.9 + github.com/spf13/cobra v1.10.2 + google.golang.org/protobuf v1.36.10 ) require ( - github.com/celestiaorg/go-header v0.7.3 // indirect + github.com/celestiaorg/go-header v0.7.4 // indirect + github.com/celestiaorg/go-square/merkle v0.0.0-20240627094109-7d01436067a3 // indirect + github.com/celestiaorg/nmt v0.24.2 // indirect github.com/decred/dcrd/dcrec/secp256k1/v4 v4.4.0 // indirect github.com/filecoin-project/go-jsonrpc v0.9.0 // indirect github.com/gogo/protobuf v1.3.2 // indirect @@ -21,14 +22,14 @@ require ( github.com/gorilla/websocket v1.5.3 // indirect github.com/hashicorp/golang-lru/v2 v2.0.7 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect - github.com/ipfs/go-cid v0.5.0 // indirect - github.com/ipfs/go-log/v2 v2.8.0 // indirect + github.com/ipfs/go-cid v0.6.0 // indirect + github.com/ipfs/go-log/v2 v2.9.0 // indirect github.com/klauspost/cpuid/v2 v2.3.0 // indirect github.com/libp2p/go-buffer-pool v0.1.0 // indirect - github.com/libp2p/go-libp2p v0.43.0 // indirect + github.com/libp2p/go-libp2p v0.45.0 // indirect github.com/libp2p/go-libp2p-pubsub v0.15.0 // indirect github.com/libp2p/go-msgio v0.3.0 // indirect - github.com/mattn/go-colorable v0.1.13 // indirect + github.com/mattn/go-colorable v0.1.14 // indirect github.com/mattn/go-isatty v0.0.20 // indirect github.com/minio/sha256-simd v1.0.1 // indirect github.com/mr-tron/base58 v1.2.0 // indirect @@ -37,18 +38,21 @@ require ( github.com/multiformats/go-multiaddr v0.16.1 // indirect github.com/multiformats/go-multiaddr-fmt v0.1.0 // indirect github.com/multiformats/go-multibase v0.2.0 // indirect - github.com/multiformats/go-multicodec v0.9.2 // indirect + github.com/multiformats/go-multicodec v0.10.0 // indirect github.com/multiformats/go-multihash v0.2.3 // indirect github.com/multiformats/go-multistream v0.6.1 // indirect - github.com/multiformats/go-varint v0.0.7 // indirect + github.com/multiformats/go-varint v0.1.0 // indirect github.com/spaolacci/murmur3 v1.1.0 // indirect github.com/spf13/pflag v1.0.10 // indirect go.opencensus.io v0.24.0 // indirect go.uber.org/multierr v1.11.0 // indirect go.uber.org/zap v1.27.0 // indirect - golang.org/x/crypto v0.45.0 // indirect - golang.org/x/exp v0.0.0-20250811191247-51f88131bc50 // indirect - golang.org/x/sys v0.38.0 // indirect + golang.org/x/crypto v0.46.0 // indirect + golang.org/x/exp v0.0.0-20251023183803-a4bb9ffd2546 // indirect + golang.org/x/sync v0.19.0 // indirect + golang.org/x/sys v0.39.0 // indirect golang.org/x/xerrors v0.0.0-20240903120638-7835f813f4da // indirect lukechampine.com/blake3 v1.4.1 // indirect ) + +replace github.com/evstack/ev-node => ../.. diff --git a/tools/da-debug/go.sum b/tools/da-debug/go.sum index 0bee6cfd01..7fa6aea62f 100644 --- a/tools/da-debug/go.sum +++ b/tools/da-debug/go.sum @@ -4,10 +4,14 @@ github.com/benbjohnson/clock v1.3.5 h1:VvXlSJBzZpA/zum6Sj74hxwYI2DIxRWuNIoXAzHZz github.com/benbjohnson/clock v1.3.5/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA= github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= -github.com/celestiaorg/go-header v0.7.3 h1:3+kIa+YXT789gPGRh3a55qmdYq3yTTBIqTyum26AvN0= -github.com/celestiaorg/go-header v0.7.3/go.mod h1:eX9iTSPthVEAlEDLux40ZT/olXPGhpxHd+mEzJeDhd0= -github.com/celestiaorg/go-square/v3 v3.0.1 h1:44xnE3AUiZn/3q/uJ0c20AezFS0lywFTGG2lE/9jYKA= -github.com/celestiaorg/go-square/v3 v3.0.1/go.mod h1:Xc4ubl/7pbn/STD7w8Bnk/X1/PG3vk0ycOPW6tMOPX4= +github.com/celestiaorg/go-header v0.7.4 h1:kQx3bVvKV+H2etxRi4IUuby5VQydBONx3giHFXDcZ/o= +github.com/celestiaorg/go-header v0.7.4/go.mod h1:eX9iTSPthVEAlEDLux40ZT/olXPGhpxHd+mEzJeDhd0= +github.com/celestiaorg/go-square/merkle v0.0.0-20240627094109-7d01436067a3 h1:wP84mtwOCVNOTfS3zErICjxKLnh74Z1uf+tdrlSFjVM= +github.com/celestiaorg/go-square/merkle v0.0.0-20240627094109-7d01436067a3/go.mod h1:86qIYnEhmn/hfW+xvw98NOI3zGaDEB3x8JGjYo2FqLs= +github.com/celestiaorg/go-square/v3 v3.0.2 h1:eSQOgNII8inK9IhiBZ+6GADQeWbRq4HYY72BOgcduA4= +github.com/celestiaorg/go-square/v3 v3.0.2/go.mod h1:oFReMLsSDMRs82ICFEeFQFCqNvwdsbIM1BzCcb0f7dM= +github.com/celestiaorg/nmt v0.24.2 h1:LlpJSPOd6/Lw1Ig6HUhZuqiINHLka/ZSRTBzlNJpchg= +github.com/celestiaorg/nmt v0.24.2/go.mod h1:vgLBpWBi8F5KLxTdXSwb7AU4NhiIQ1AQRGa+PzdcLEA= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= @@ -29,12 +33,6 @@ github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymF github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= -github.com/evstack/ev-node v1.0.0-beta.6 h1:jjGWAUsjHDpuBjvM7KXnY6Y8uYHM8LOrn0hDrk5zE6E= -github.com/evstack/ev-node v1.0.0-beta.6/go.mod h1:ZABT4xTIg4bINUS08r8e8LFIUk5anWe799fZ320q+Mk= -github.com/evstack/ev-node/core v1.0.0-beta.5 h1:lgxE8XiF3U9pcFgh7xuKMgsOGvLBGRyd9kc9MR4WL0o= -github.com/evstack/ev-node/core v1.0.0-beta.5/go.mod h1:n2w/LhYQTPsi48m6lMj16YiIqsaQw6gxwjyJvR+B3sY= -github.com/evstack/ev-node/da v1.0.0-beta.6 h1:htzm4bbIGzNeuue4+/fEZTtjqpieLQWCtOWWnsNZvrY= -github.com/evstack/ev-node/da v1.0.0-beta.6/go.mod h1:Br+hq83JG0iIe9QW4grpm6iW8N86RdyDAhHck4+IJK8= github.com/filecoin-project/go-clock v0.1.0 h1:SFbYIM75M8NnFm1yMHhN9Ahy3W5bEZV9gd6MPfXbKVU= github.com/filecoin-project/go-clock v0.1.0/go.mod h1:4uB/O4PvOjlx1VCMdZ9MyDZXRm//gkj1ELEbxfI1AZs= github.com/filecoin-project/go-jsonrpc v0.9.0 h1:G47qEF52w7GholpI21vPSTVBFvsrip6geIoqNiqyZtQ= @@ -43,18 +41,6 @@ github.com/flynn/noise v1.1.0 h1:KjPQoQCEFdZDiP03phOvGi11+SVVhBG2wOWAorLsstg= github.com/flynn/noise v1.1.0/go.mod h1:xbMo+0i6+IGbYdJhF31t2eR1BIU0CYc12+BNAKwUTag= github.com/francoispqt/gojay v1.2.13 h1:d2m3sFjloqoIUQU3TsHBgj6qg/BVGlTBeHDUmyJnXKk= github.com/francoispqt/gojay v1.2.13/go.mod h1:ehT5mTG4ua4581f1++1WLG0vPdaA9HaiDsoyrBGkyDY= -github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S9k= -github.com/fsnotify/fsnotify v1.9.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0= -github.com/go-kit/kit v0.13.0 h1:OoneCcHKHQ03LfBpoQCUfCluwd2Vt3ohz+kvbJneZAU= -github.com/go-kit/kit v0.13.0/go.mod h1:phqEHMMUbyrCFCTgH48JueqrM3md2HcAZ8N3XE4FKDg= -github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI= -github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= -github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= -github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= -github.com/go-viper/mapstructure/v2 v2.4.0 h1:EBsztssimR/CONLSZZ04E8qAkxNYq4Qp9LvH92wZUgs= -github.com/go-viper/mapstructure/v2 v2.4.0/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM= -github.com/goccy/go-yaml v1.18.0 h1:8W7wMFS12Pcas7KU+VVkaiCng+kG8QiFeFwzFb+rwuw= -github.com/goccy/go-yaml v1.18.0/go.mod h1:XBurs7gK8ATbW4ZPGKgcbrY1Br56PdM69F7LkFRi1kA= github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= @@ -80,31 +66,23 @@ github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/ github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= -github.com/google/gopacket v1.1.19 h1:ves8RnFZPGiFnTS0uPQStjwru6uO6h+nlr9j6fL7kF8= -github.com/google/gopacket v1.1.19/go.mod h1:iJ8V8n6KS+z2U1A8pUwu8bW5SyEMkXJB8Yo/Vo+TKTo= +github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0= +github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= 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/hashicorp/golang-lru v1.0.2 h1:dV3g9Z/unq5DpblPpw+Oqcv4dU/1omnb4Ok8iPY6p1c= -github.com/hashicorp/golang-lru v1.0.2/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4= github.com/hashicorp/golang-lru/v2 v2.0.7 h1:a+bsQ5rvGLjzHuww6tVxozPZFVghXaHOwFs4luLUK2k= github.com/hashicorp/golang-lru/v2 v2.0.7/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM= github.com/huin/goupnp v1.3.0 h1:UvLUlWDNpoUdYzb2TCn+MuTWtcjXKSza2n6CBdQ0xXc= github.com/huin/goupnp v1.3.0/go.mod h1:gnGPsThkYa7bFi/KWmEysQRf48l2dvR5bxr2OFckNX8= github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= -github.com/ipfs/boxo v0.33.1 h1:89m+ksw+cYi0ecTNTJ71IRS5ZrLiovmO6XWHIOGhAEg= -github.com/ipfs/boxo v0.33.1/go.mod h1:KwlJTzv5fb1GLlA9KyMqHQmvP+4mrFuiE3PnjdrPJHs= -github.com/ipfs/go-cid v0.5.0 h1:goEKKhaGm0ul11IHA7I6p1GmKz8kEYniqFopaB5Otwg= -github.com/ipfs/go-cid v0.5.0/go.mod h1:0L7vmeNXpQpUS9vt+yEARkJ8rOg43DF3iPgn4GIN0mk= -github.com/ipfs/go-datastore v0.9.0 h1:WocriPOayqalEsueHv6SdD4nPVl4rYMfYGLD4bqCZ+w= -github.com/ipfs/go-datastore v0.9.0/go.mod h1:uT77w/XEGrvJWwHgdrMr8bqCN6ZTW9gzmi+3uK+ouHg= -github.com/ipfs/go-log/v2 v2.8.0 h1:SptNTPJQV3s5EF4FdrTu/yVdOKfGbDgn1EBZx4til2o= -github.com/ipfs/go-log/v2 v2.8.0/go.mod h1:2LEEhdv8BGubPeSFTyzbqhCqrwqxCbuTNTLWqgNAipo= -github.com/ipld/go-ipld-prime v0.21.0 h1:n4JmcpOlPDIxBcY037SVfpd1G+Sj1nKZah0m6QH9C2E= -github.com/ipld/go-ipld-prime v0.21.0/go.mod h1:3RLqy//ERg/y5oShXXdx5YIp50cFGOanyMctpPjsvxQ= +github.com/ipfs/go-cid v0.6.0 h1:DlOReBV1xhHBhhfy/gBNNTSyfOM6rLiIx9J7A4DGf30= +github.com/ipfs/go-cid v0.6.0/go.mod h1:NC4kS1LZjzfhK40UGmpXv5/qD2kcMzACYJNntCUiDhQ= +github.com/ipfs/go-log/v2 v2.9.0 h1:l4b06AwVXwldIzbVPZy5z7sKp9lHFTX0KWfTBCtHaOk= +github.com/ipfs/go-log/v2 v2.9.0/go.mod h1:UhIYAwMV7Nb4ZmihUxfIRM2Istw/y9cAk3xaK+4Zs2c= github.com/jackpal/go-nat-pmp v1.0.2 h1:KzKSgb7qkJvOUTqYl9/Hg/me3pWgBmERKrTGD7BdWus= github.com/jackpal/go-nat-pmp v1.0.2/go.mod h1:QPH045xvCAeXUZOxsnwmrtiCoxIr9eob+4orBN1SBKc= github.com/jbenet/go-temp-err-catcher v0.1.0 h1:zpb3ZH6wIE8Shj2sKS+khgRvf7T7RABoLk/+KKHggpk= @@ -119,38 +97,29 @@ github.com/koron/go-ssdp v0.0.6 h1:Jb0h04599eq/CY7rB5YEqPS83HmRfHP2azkxMN2rFtU= github.com/koron/go-ssdp v0.0.6/go.mod h1:0R9LfRJGek1zWTjN3JUNlm5INCDYGpRDfAptnct63fI= github.com/libp2p/go-buffer-pool v0.1.0 h1:oK4mSFcQz7cTQIfqbe4MIj9gLW+mnanjyFtc6cdF0Y8= github.com/libp2p/go-buffer-pool v0.1.0/go.mod h1:N+vh8gMqimBzdKkSMVuydVDq+UV5QTWy5HSiZacSbPg= -github.com/libp2p/go-cidranger v1.1.0 h1:ewPN8EZ0dd1LSnrtuwd4709PXVcITVeuwbag38yPW7c= -github.com/libp2p/go-cidranger v1.1.0/go.mod h1:KWZTfSr+r9qEo9OkI9/SIEeAtw+NNoU0dXIXt15Okic= github.com/libp2p/go-flow-metrics v0.3.0 h1:q31zcHUvHnwDO0SHaukewPYgwOBSxtt830uJtUx6784= github.com/libp2p/go-flow-metrics v0.3.0/go.mod h1:nuhlreIwEguM1IvHAew3ij7A8BMlyHQJ279ao24eZZo= -github.com/libp2p/go-libp2p v0.43.0 h1:b2bg2cRNmY4HpLK8VHYQXLX2d3iND95OjodLFymvqXU= -github.com/libp2p/go-libp2p v0.43.0/go.mod h1:IiSqAXDyP2sWH+J2gs43pNmB/y4FOi2XQPbsb+8qvzc= +github.com/libp2p/go-libp2p v0.45.0 h1:Pdhr2HsFXaYjtfiNcBP4CcRUONvbMFdH3puM9vV4Tiw= +github.com/libp2p/go-libp2p v0.45.0/go.mod h1:NovCojezAt4dnDd4fH048K7PKEqH0UFYYqJRjIIu8zc= github.com/libp2p/go-libp2p-asn-util v0.4.1 h1:xqL7++IKD9TBFMgnLPZR6/6iYhawHKHl950SO9L6n94= github.com/libp2p/go-libp2p-asn-util v0.4.1/go.mod h1:d/NI6XZ9qxw67b4e+NgpQexCIiFYJjErASrYW4PFDN8= -github.com/libp2p/go-libp2p-kad-dht v0.34.0 h1:yvJ/Vrt36GVjsqPxiGcuuwOloKuZLV9Aa7awIKyNXy0= -github.com/libp2p/go-libp2p-kad-dht v0.34.0/go.mod h1:JNbkES4W5tajS6uYivw6MPs0842cPHAwhgaPw8sQG4o= -github.com/libp2p/go-libp2p-kbucket v0.7.0 h1:vYDvRjkyJPeWunQXqcW2Z6E93Ywx7fX0jgzb/dGOKCs= -github.com/libp2p/go-libp2p-kbucket v0.7.0/go.mod h1:blOINGIj1yiPYlVEX0Rj9QwEkmVnz3EP8LK1dRKBC6g= github.com/libp2p/go-libp2p-pubsub v0.15.0 h1:cG7Cng2BT82WttmPFMi50gDNV+58K626m/wR00vGL1o= github.com/libp2p/go-libp2p-pubsub v0.15.0/go.mod h1:lr4oE8bFgQaifRcoc2uWhWWiK6tPdOEKpUuR408GFN4= -github.com/libp2p/go-libp2p-record v0.3.1 h1:cly48Xi5GjNw5Wq+7gmjfBiG9HCzQVkiZOUZ8kUl+Fg= -github.com/libp2p/go-libp2p-record v0.3.1/go.mod h1:T8itUkLcWQLCYMqtX7Th6r7SexyUJpIyPgks757td/E= -github.com/libp2p/go-libp2p-routing-helpers v0.7.5 h1:HdwZj9NKovMx0vqq6YNPTh6aaNzey5zHD7HeLJtq6fI= -github.com/libp2p/go-libp2p-routing-helpers v0.7.5/go.mod h1:3YaxrwP0OBPDD7my3D0KxfR89FlcX/IEbxDEDfAmj98= github.com/libp2p/go-libp2p-testing v0.12.0 h1:EPvBb4kKMWO29qP4mZGyhVzUyR25dvfUIK5WDu6iPUA= github.com/libp2p/go-libp2p-testing v0.12.0/go.mod h1:KcGDRXyN7sQCllucn1cOOS+Dmm7ujhfEyXQL5lvkcPg= github.com/libp2p/go-msgio v0.3.0 h1:mf3Z8B1xcFN314sWX+2vOTShIE0Mmn2TXn3YCUQGNj0= github.com/libp2p/go-msgio v0.3.0/go.mod h1:nyRM819GmVaF9LX3l03RMh10QdOroF++NBbxAb0mmDM= -github.com/libp2p/go-netroute v0.2.2 h1:Dejd8cQ47Qx2kRABg6lPwknU7+nBnFRpko45/fFPuZ8= -github.com/libp2p/go-netroute v0.2.2/go.mod h1:Rntq6jUAH0l9Gg17w5bFGhcC9a+vk4KNXs6s7IljKYE= +github.com/libp2p/go-netroute v0.3.0 h1:nqPCXHmeNmgTJnktosJ/sIef9hvwYCrsLxXmfNks/oc= +github.com/libp2p/go-netroute v0.3.0/go.mod h1:Nkd5ShYgSMS5MUKy/MU2T57xFoOKvvLR92Lic48LEyA= github.com/libp2p/go-reuseport v0.4.0 h1:nR5KU7hD0WxXCJbmw7r2rhRYruNRl2koHw8fQscQm2s= github.com/libp2p/go-reuseport v0.4.0/go.mod h1:ZtI03j/wO5hZVDFo2jKywN6bYKWLOy8Se6DrI2E1cLU= github.com/libp2p/go-yamux/v5 v5.0.1 h1:f0WoX/bEF2E8SbE4c/k1Mo+/9z0O4oC/hWEA+nfYRSg= github.com/libp2p/go-yamux/v5 v5.0.1/go.mod h1:en+3cdX51U0ZslwRdRLrvQsdayFt3TSUKvBGErzpWbU= github.com/marten-seemann/tcp v0.0.0-20210406111302-dfbc87cc63fd h1:br0buuQ854V8u83wA0rVZ8ttrq5CpaPZdvrK0LP2lOk= github.com/marten-seemann/tcp v0.0.0-20210406111302-dfbc87cc63fd/go.mod h1:QuCEs1Nt24+FYQEqAAncTDPJIuGs+LxK1MCiFL25pMU= -github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= +github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE= +github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8= 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= @@ -165,8 +134,6 @@ github.com/minio/blake2b-simd v0.0.0-20160723061019-3f5f724cb5b1/go.mod h1:pD8Rv github.com/minio/sha256-simd v0.1.1-0.20190913151208-6de447530771/go.mod h1:B5e1o+1/KgNmWrSQK08Y6Z1Vb5pwIktudl0J58iy0KM= github.com/minio/sha256-simd v1.0.1 h1:6kaan5IFmwTNynnKKpDHe6FWHohJOHhCPchzK49dzMM= github.com/minio/sha256-simd v1.0.1/go.mod h1:Pz6AKMiUdngCLpeTL/RJY1M9rUuPMYujV5xJjtbRSN8= -github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= -github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= github.com/mr-tron/base58 v1.1.2/go.mod h1:BinMc/sQntlIE1frQmRFPUoPA1Zkr8VRgBdjWI2mNwc= github.com/mr-tron/base58 v1.2.0 h1:T/HDJBh4ZCPbU39/+c3rRvE0uKBQlU27+QI8LJ4t64o= github.com/mr-tron/base58 v1.2.0/go.mod h1:BinMc/sQntlIE1frQmRFPUoPA1Zkr8VRgBdjWI2mNwc= @@ -183,21 +150,19 @@ github.com/multiformats/go-multiaddr-fmt v0.1.0 h1:WLEFClPycPkp4fnIzoFoV9FVd49/e github.com/multiformats/go-multiaddr-fmt v0.1.0/go.mod h1:hGtDIW4PU4BqJ50gW2quDuPVjyWNZxToGUh/HwTZYJo= github.com/multiformats/go-multibase v0.2.0 h1:isdYCVLvksgWlMW9OZRYJEa9pZETFivncJHmHnnd87g= github.com/multiformats/go-multibase v0.2.0/go.mod h1:bFBZX4lKCA/2lyOFSAoKH5SS6oPyjtnzK/XTFDPkNuk= -github.com/multiformats/go-multicodec v0.9.2 h1:YrlXCuqxjqm3bXl+vBq5LKz5pz4mvAsugdqy78k0pXQ= -github.com/multiformats/go-multicodec v0.9.2/go.mod h1:LLWNMtyV5ithSBUo3vFIMaeDy+h3EbkMTek1m+Fybbo= +github.com/multiformats/go-multicodec v0.10.0 h1:UpP223cig/Cx8J76jWt91njpK3GTAO1w02sdcjZDSuc= +github.com/multiformats/go-multicodec v0.10.0/go.mod h1:wg88pM+s2kZJEQfRCKBNU+g32F5aWBEjyFHXvZLTcLI= github.com/multiformats/go-multihash v0.0.8/go.mod h1:YSLudS+Pi8NHE7o6tb3D8vrpKa63epEDmG8nTduyAew= github.com/multiformats/go-multihash v0.2.3 h1:7Lyc8XfX/IY2jWb/gI7JP+o7JEq9hOa7BFvVU9RSh+U= github.com/multiformats/go-multihash v0.2.3/go.mod h1:dXgKXCXjBzdscBLk9JkjINiEsCKRVch90MdaGiKsvSM= github.com/multiformats/go-multistream v0.6.1 h1:4aoX5v6T+yWmc2raBHsTvzmFhOI8WVOer28DeBBEYdQ= github.com/multiformats/go-multistream v0.6.1/go.mod h1:ksQf6kqHAb6zIsyw7Zm+gAuVo57Qbq84E27YlYqavqw= -github.com/multiformats/go-varint v0.0.7 h1:sWSGR+f/eu5ABZA2ZpYKBILXTTs9JWpdEM/nEGOHFS8= -github.com/multiformats/go-varint v0.0.7/go.mod h1:r8PUYw/fD/SjBCiKOoDlGF6QawOELpZAu9eioSos/OU= +github.com/multiformats/go-varint v0.1.0 h1:i2wqFp4sdl3IcIxfAonHQV9qU5OsZ4Ts9IOoETFs5dI= +github.com/multiformats/go-varint v0.1.0/go.mod h1:5KVAVXegtfmNQQm/lCY+ATvDzvJJhSkUlGQV9wgObdI= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= 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/v2 v2.2.4 h1:mye9XuhQ6gvn5h28+VilKrrPoQVanw5PMw/TB0t5Ec4= -github.com/pelletier/go-toml/v2 v2.2.4/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY= github.com/pion/datachannel v1.5.10 h1:ly0Q26K1i6ZkGf42W7D4hQYR90pZwzFOjTq5AuCKk4o= github.com/pion/datachannel v1.5.10/go.mod h1:p/jJfC9arb29W7WrxyKbepTU20CFgyx5oLo8Rs4Py/M= github.com/pion/dtls/v2 v2.2.12 h1:KP7H5/c1EiVAAKUmXyCzPiQe5+bCJrpOeKg/L05dunk= @@ -241,8 +206,6 @@ github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINE github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/polydawn/refmt v0.89.0 h1:ADJTApkvkeBZsN0tBTx8QjpD9JkmxbKp0cxfr9qszm4= -github.com/polydawn/refmt v0.89.0/go.mod h1:/zvteZs/GwLtCgZ4BL6CBsk9IKIlexP43ObX9AxTqTw= github.com/prometheus/client_golang v1.23.2 h1:Je96obch5RDVy3FDMndoUsjAhG5Edi49h0RJWRi/o0o= github.com/prometheus/client_golang v1.23.2/go.mod h1:Tb1a6LWHB3/SPIzCoaDXI4I8UHKeFTEQ1YCr+0Gyqmg= github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= @@ -254,31 +217,21 @@ github.com/prometheus/procfs v0.17.0 h1:FuLQ+05u4ZI+SS/w9+BWEM2TXiHKsUQ9TADiRH7D github.com/prometheus/procfs v0.17.0/go.mod h1:oPQLaDAMRbA+u8H5Pbfq+dl3VDAvHxMUOVhe0wYB2zw= github.com/quic-go/qpack v0.5.1 h1:giqksBPnT/HDtZ6VhtFKgoLOWmlyo9Ei6u9PqzIMbhI= github.com/quic-go/qpack v0.5.1/go.mod h1:+PC4XFrEskIVkcLzpEkbLqq1uCoxPhQuvK5rH1ZgaEg= -github.com/quic-go/quic-go v0.54.0 h1:6s1YB9QotYI6Ospeiguknbp2Znb/jZYjZLRXn9kMQBg= -github.com/quic-go/quic-go v0.54.0/go.mod h1:e68ZEaCdyviluZmy44P6Iey98v/Wfz6HCjQEm+l8zTY= +github.com/quic-go/quic-go v0.55.0 h1:zccPQIqYCXDt5NmcEabyYvOnomjs8Tlwl7tISjJh9Mk= +github.com/quic-go/quic-go v0.55.0/go.mod h1:DR51ilwU1uE164KuWXhinFcKWGlEjzys2l8zUl5Ss1U= github.com/quic-go/webtransport-go v0.9.0 h1:jgys+7/wm6JarGDrW+lD/r9BGqBAmqY/ssklE09bA70= github.com/quic-go/webtransport-go v0.9.0/go.mod h1:4FUYIiUc75XSsF6HShcLeXXYZJ9AGwo/xh3L8M/P1ao= github.com/rs/xid v1.6.0/go.mod h1:7XoLgs4eV+QndskICGsho+ADou8ySMSjJKDIan90Nz0= github.com/rs/zerolog v1.34.0 h1:k43nTLIwcTVQAncfCw4KZ2VY6ukYoZaBPNOE8txlOeY= github.com/rs/zerolog v1.34.0/go.mod h1:bJsvje4Z08ROH4Nhs5iH600c3IkWhwp44iRc54W6wYQ= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= -github.com/sagikazarmark/locafero v0.11.0 h1:1iurJgmM9G3PA/I+wWYIOw/5SyBtxapeHDcg+AAIFXc= -github.com/sagikazarmark/locafero v0.11.0/go.mod h1:nVIGvgyzw595SUSUE6tvCp3YYTeHs15MvlmU87WwIik= -github.com/sourcegraph/conc v0.3.1-0.20240121214520-5f936abd7ae8 h1:+jumHNA0Wrelhe64i8F6HNlS8pkoyMv5sreGx2Ry5Rw= -github.com/sourcegraph/conc v0.3.1-0.20240121214520-5f936abd7ae8/go.mod h1:3n1Cwaq1E1/1lhQhtRK2ts/ZwZEhjcQeJQ1RuC6Q/8U= github.com/spaolacci/murmur3 v1.1.0 h1:7c1g84S4BPRrfL5Xrdp6fOJ206sU9y293DDHaoy0bLI= github.com/spaolacci/murmur3 v1.1.0/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= -github.com/spf13/afero v1.15.0 h1:b/YBCLWAJdFWJTN9cLhiXXcD7mzKn9Dm86dNnfyQw1I= -github.com/spf13/afero v1.15.0/go.mod h1:NC2ByUVxtQs4b3sIUphxK0NioZnmxgyCrfzeuq8lxMg= -github.com/spf13/cast v1.10.0 h1:h2x0u2shc1QuLHfxi+cTJvs30+ZAHOGRic8uyGTDWxY= -github.com/spf13/cast v1.10.0/go.mod h1:jNfB8QC9IA6ZuY2ZjDp0KtFO2LZZlg4S/7bzP6qqeHo= -github.com/spf13/cobra v1.10.1 h1:lJeBwCfmrnXthfAupyUTzJ/J4Nc1RsHC/mSRU2dll/s= -github.com/spf13/cobra v1.10.1/go.mod h1:7SmJGaTHFVBY0jW4NXGluQoLvhqFQM+6XSKD+P4XaB0= +github.com/spf13/cobra v1.10.2 h1:DMTTonx5m65Ic0GOoRY2c16WCbHxOOw6xxezuLaBpcU= +github.com/spf13/cobra v1.10.2/go.mod h1:7C1pvHqHw5A4vrJfjNwvOdzYu0Gml16OCs2GRiTUUS4= github.com/spf13/pflag v1.0.9/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/spf13/pflag v1.0.10 h1:4EBh2KAYBwaONj6b2Ye1GiHfwjqyROoF4RwYO+vPwFk= github.com/spf13/pflag v1.0.10/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= -github.com/spf13/viper v1.21.0 h1:x5S+0EU27Lbphp4UKm1C+1oQO+rKx36vfCoaVebLFSU= -github.com/spf13/viper v1.21.0/go.mod h1:P0lhsswPGWD/1lZJ9ny3fYnVqxiegrlNrEmgLjbTCAY= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= @@ -289,24 +242,18 @@ github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= -github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8= -github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU= -github.com/whyrusleeping/go-keyspace v0.0.0-20160322163242-5b898ac5add1 h1:EKhdznlJHPMoKr0XTrX+IlJs1LH3lyx2nfr1dOlZ79k= -github.com/whyrusleeping/go-keyspace v0.0.0-20160322163242-5b898ac5add1/go.mod h1:8UvriyWtv5Q5EOgjHaSseUEdkQfvwFv1I/In/O2M9gc= +github.com/tidwall/gjson v1.18.0 h1:FIDeeyB800efLX89e5a8Y0BNH+LOngJyGrIWxG2FKQY= +github.com/tidwall/gjson v1.18.0/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk= +github.com/tidwall/match v1.1.1 h1:+Ho715JplO36QYgwN9PGYNhgZvoUSc9X2c80KVTi+GA= +github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM= +github.com/tidwall/pretty v1.2.1 h1:qjsOFOWWQl+N3RsoF5/ssm1pHmJJwhjlSbZ51I6wMl4= +github.com/tidwall/pretty v1.2.1/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU= github.com/wlynxg/anet v0.0.5 h1:J3VJGi1gvo0JwZ/P1/Yc/8p63SoW98B5dHkYDmpgvvU= github.com/wlynxg/anet v0.0.5/go.mod h1:eay5PRQr7fIVAMbTbchTnO9gG65Hg/uYGdc7mguHxoA= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0= go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo= -go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA= -go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A= -go.opentelemetry.io/otel v1.38.0 h1:RkfdswUDRimDg0m2Az18RKOsnI8UDzppJAtj01/Ymk8= -go.opentelemetry.io/otel v1.38.0/go.mod h1:zcmtmQ1+YmQM9wrNsTGV/q/uyusom3P8RxwExxkZhjM= -go.opentelemetry.io/otel/metric v1.38.0 h1:Kl6lzIYGAh5M159u9NgiRkmoMKjvbsKtYRwgfrA6WpA= -go.opentelemetry.io/otel/metric v1.38.0/go.mod h1:kB5n/QoRM8YwmUahxvI3bO34eVtQf2i4utNVLr9gEmI= -go.opentelemetry.io/otel/trace v1.38.0 h1:Fxk5bKrDZJUH+AMyyIXGcFAPah0oRcT+LuNtJrmcNLE= -go.opentelemetry.io/otel/trace v1.38.0/go.mod h1:j1P9ivuFsTceSWe1oY+EeW3sc+Pp42sO++GHkg4wwhs= go.uber.org/dig v1.19.0 h1:BACLhebsYdpQ7IROQ1AGPjrXcP5dF80U3gKoFzbaq/4= go.uber.org/dig v1.19.0/go.mod h1:Us0rSJiThwCv2GteUN0Q7OKvU7n5J4dxZ9JKUXozFdE= go.uber.org/fx v1.24.0 h1:wE8mruvpg2kiiL1Vqd0CC+tr0/24XIB10Iwp2lLWzkg= @@ -319,26 +266,25 @@ go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8= go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E= -go.yaml.in/yaml/v2 v2.4.2 h1:DzmwEr2rDGHl7lsFgAHxmNz/1NlQ7xLIrlN2h5d1eGI= -go.yaml.in/yaml/v2 v2.4.2/go.mod h1:081UH+NErpNdqlCXm3TtEran0rJZGxAYx9hb/ELlsPU= -go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc= +go.yaml.in/yaml/v2 v2.4.3 h1:6gvOSjQoTB3vt1l+CU+tSyi/HOjfOjRLJ4YwYZGwRO0= +go.yaml.in/yaml/v2 v2.4.3/go.mod h1:zSxWcmIDjOzPXpjlTTbAsKokqkDNAVtZO0WOMiT90s8= go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190611184440-5c40567a22f8/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.45.0 h1:jMBrvKuj23MTlT0bQEOBcAE0mjg8mK9RXFhRH6nyF3Q= -golang.org/x/crypto v0.45.0/go.mod h1:XTGrrkGJve7CYK7J8PEww4aY7gM3qMCElcJQ8n8JdX4= +golang.org/x/crypto v0.46.0 h1:cKRW/pmt1pKAfetfu+RCEvjvZkA9RimPbh7bhFjGVBU= +golang.org/x/crypto v0.46.0/go.mod h1:Evb/oLKmMraqjZ2iQTwDwvCtJkczlDuTmdJXoZVzqU0= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= -golang.org/x/exp v0.0.0-20250811191247-51f88131bc50 h1:3yiSh9fhy5/RhCSntf4Sy0Tnx50DmMpQ4MQdKKk4yg4= -golang.org/x/exp v0.0.0-20250811191247-51f88131bc50/go.mod h1:rT6SFzZ7oxADUDx58pcaKFTcZ+inxAa9fTrYx/uVYwg= +golang.org/x/exp v0.0.0-20251023183803-a4bb9ffd2546 h1:mgKeJMpvi0yx/sU5GsxQ7p6s2wtOnGAHZWCHUM4KGzY= +golang.org/x/exp v0.0.0-20251023183803-a4bb9ffd2546/go.mod h1:j/pmGrbnkbPtQfxEe5D0VQhZC6qKbfKifgD0oM7sR70= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= 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.27.0 h1:kb+q2PyFnEADO2IEF935ehFUXlWiNjJWtRNgBLSfbxQ= -golang.org/x/mod v0.27.0/go.mod h1:rWI627Fq0DEoudcK+MBkNkCe0EetEaDSwJJkCcjpazc= +golang.org/x/mod v0.30.0 h1:fDEXFVZ/fmCKProc/yAXXUijritrDzahmwwefnjoPFk= +golang.org/x/mod v0.30.0/go.mod h1:lAsf5O2EvJeSFMiBxXDki7sCgAxEUcZHXoXMKT4GJKc= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -348,16 +294,16 @@ golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLL golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= -golang.org/x/net v0.47.0 h1:Mx+4dIFzqraBXUugkia1OOvlD6LemFo1ALMHjrXDOhY= -golang.org/x/net v0.47.0/go.mod h1:/jNxtkgq5yWUGYkaZGqo27cfGZ1c5Nen03aYrrKpVRU= +golang.org/x/net v0.48.0 h1:zyQRTTrjc33Lhh0fBgT/H3oZq9WuvRR5gPC70xpDiQU= +golang.org/x/net v0.48.0/go.mod h1:+ndRgGjkh8FGtu1w1FGbEC31if4VrNVMuKTgcAAnQRY= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 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.17.0 h1:l60nONMj9l5drqw6jlhIELNv9I0A4OFgRsG9k2oT9Ug= -golang.org/x/sync v0.17.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI= +golang.org/x/sync v0.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4= +golang.org/x/sync v0.19.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -365,12 +311,14 @@ golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.38.0 h1:3yZWxaJjBmCWXqhN1qh02AkOnCQ1poK6oF+a7xWL6Gc= -golang.org/x/sys v0.38.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= +golang.org/x/sys v0.39.0 h1:CvCKL8MeisomCi6qNZ+wbb0DN9E5AATixKsvNtMoMFk= +golang.org/x/sys v0.39.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= +golang.org/x/telemetry v0.0.0-20251111182119-bc8e575c7b54 h1:E2/AqCUMZGgd73TQkxUMcMla25GB9i/5HOdLr+uH7Vo= +golang.org/x/telemetry v0.0.0-20251111182119-bc8e575c7b54/go.mod h1:hKdjCMrbv9skySur+Nek8Hd0uJ0GuxJIoIX2payrIdQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.31.0 h1:aC8ghyu4JhP8VojJ2lEHBnochRno1sgL6nEi9WGFGMM= -golang.org/x/text v0.31.0/go.mod h1:tKRAlv61yKIjGGHX/4tP1LTbc13YSec1pxVEWXzfoeM= +golang.org/x/text v0.32.0 h1:ZD01bjUt1FQ9WJ0ClOL5vxgxOI/sVCNgX1YtKwcY0mU= +golang.org/x/text v0.32.0/go.mod h1:o/rUWzghvpD5TXrTIBuJU77MTaN0ljMWE47kxGJQ7jY= golang.org/x/time v0.12.0 h1:ScB/8o8olJvc+CQPWrK3fPZNfh7qgwCrY0zJmoEQLSE= golang.org/x/time v0.12.0/go.mod h1:CDIdPxbZBQxdj6cxyCIdrNogrJKMJ7pr37NYpMcMDSg= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= @@ -381,16 +329,14 @@ golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBn golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= -golang.org/x/tools v0.36.0 h1:kWS0uv/zsvHEle1LbV5LE8QujrxB3wfQyxHfhOk0Qkg= -golang.org/x/tools v0.36.0/go.mod h1:WBDiHKJK8YgLHlcQPYQzNCkUxUypCaa5ZegCVutKm+s= +golang.org/x/tools v0.39.0 h1:ik4ho21kwuQln40uelmciQPp9SipgNDdrafrYA4TmQQ= +golang.org/x/tools v0.39.0/go.mod h1:JnefbkDPyD8UU2kI5fuf8ZX4/yUeh9W877ZeBONxUqQ= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20240903120638-7835f813f4da h1:noIWHXmPHxILtqtCOPIhSt0ABwskkZKjD3bXGnZGpNY= golang.org/x/xerrors v0.0.0-20240903120638-7835f813f4da/go.mod h1:NDW/Ps6MPRej6fsCIbMTohpP40sJ/P/vI1MoTEGwX90= -gonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk= -gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= @@ -410,8 +356,8 @@ google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2 google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= -google.golang.org/protobuf v1.36.9 h1:w2gp2mA27hUeUzj9Ex9FBjsBm40zfaDtEWow293U7Iw= -google.golang.org/protobuf v1.36.9/go.mod h1:fuxRtAxBytpl4zzqUh6/eyUujkJdNiuEkXntxiD/uRU= +google.golang.org/protobuf v1.36.10 h1:AYd7cD/uASjIL6Q9LiTjz8JLcrh/88q5UObnmY3aOOE= +google.golang.org/protobuf v1.36.10/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= diff --git a/tools/da-debug/main.go b/tools/da-debug/main.go index 70c81edf64..19d370e3f8 100644 --- a/tools/da-debug/main.go +++ b/tools/da-debug/main.go @@ -13,8 +13,9 @@ import ( "github.com/spf13/cobra" "google.golang.org/protobuf/proto" - coreda "github.com/evstack/ev-node/core/da" - "github.com/evstack/ev-node/da/jsonrpc" + "github.com/celestiaorg/go-square/v3/share" + blobrpc "github.com/evstack/ev-node/pkg/da/jsonrpc" + datypes "github.com/evstack/ev-node/pkg/da/types" "github.com/evstack/ev-node/types" pb "github.com/evstack/ev-node/types/pb/evnode/v1" ) @@ -24,7 +25,6 @@ var ( authToken string timeout time.Duration verbose bool - maxBlobSize uint64 filterHeight uint64 ) @@ -41,7 +41,6 @@ A powerful DA debugging tool for inspecting blockchain data availability layers. rootCmd.PersistentFlags().StringVar(&authToken, "auth-token", "", "Authentication token for DA layer") rootCmd.PersistentFlags().DurationVar(&timeout, "timeout", 30*time.Second, "Request timeout") rootCmd.PersistentFlags().BoolVar(&verbose, "verbose", false, "Enable verbose logging") - rootCmd.PersistentFlags().Uint64Var(&maxBlobSize, "max-blob-size", 1970176, "Maximum blob size in bytes") // Add subcommands rootCmd.AddCommand(queryCmd()) @@ -104,11 +103,11 @@ func runQuery(cmd *cobra.Command, args []string) error { printBanner() printQueryInfo(height, namespace) - client, err := createDAClient() + client, closeFn, err := createDAClient() if err != nil { return err } - defer client.Close() + defer closeFn() ctx, cancel := context.WithTimeout(context.Background(), timeout) defer cancel() @@ -130,11 +129,11 @@ func runSearch(cmd *cobra.Command, args []string, searchHeight, searchRange uint printBanner() printSearchInfo(startHeight, namespace, searchHeight, searchRange) - client, err := createDAClient() + client, closeFn, err := createDAClient() if err != nil { return err } - defer client.Close() + defer closeFn() ctx, cancel := context.WithTimeout(context.Background(), timeout) defer cancel() @@ -142,46 +141,39 @@ func runSearch(cmd *cobra.Command, args []string, searchHeight, searchRange uint return searchForHeight(ctx, client, startHeight, namespace, searchHeight, searchRange) } -func searchForHeight(ctx context.Context, client *jsonrpc.Client, startHeight uint64, namespace []byte, targetHeight, searchRange uint64) error { +func searchForHeight(ctx context.Context, client *blobrpc.Client, startHeight uint64, namespace []byte, targetHeight, searchRange uint64) error { fmt.Printf("Searching for height %d in DA heights %d-%d...\n", targetHeight, startHeight, startHeight+searchRange-1) fmt.Println() + ns, err := share.NewNamespaceFromBytes(namespace) + if err != nil { + return fmt.Errorf("invalid namespace: %w", err) + } + foundBlobs := 0 for daHeight := startHeight; daHeight < startHeight+searchRange; daHeight++ { - result, err := client.DA.GetIDs(ctx, daHeight, namespace) + blobs, err := client.Blob.GetAll(ctx, daHeight, []share.Namespace{ns}) if err != nil { - if err.Error() == "blob: not found" || strings.Contains(err.Error(), "blob: not found") { - continue - } - if strings.Contains(err.Error(), "height") && strings.Contains(err.Error(), "future") { + if strings.Contains(err.Error(), "future") { fmt.Printf("Reached future height at DA height %d\n", daHeight) break } continue } - if result == nil || len(result.IDs) == 0 { - continue - } - - // Get the actual blob data - blobs, err := client.DA.Get(ctx, result.IDs, namespace) - if err != nil { - continue - } - // Check each blob for the target height - for i, blob := range blobs { + for _, blob := range blobs { + blobData := blob.Data() found := false var blobHeight uint64 // Try to decode as header first - if header := tryDecodeHeader(blob); header != nil { + if header := tryDecodeHeader(blobData); header != nil { blobHeight = header.Height() if blobHeight == targetHeight { found = true } - } else if data := tryDecodeData(blob); data != nil { + } else if data := tryDecodeData(blobData); data != nil { if data.Metadata != nil { blobHeight = data.Height() if blobHeight == targetHeight { @@ -194,13 +186,13 @@ func searchForHeight(ctx context.Context, client *jsonrpc.Client, startHeight ui foundBlobs++ fmt.Printf("FOUND at DA Height %d - BLOB %d\n", daHeight, foundBlobs) fmt.Println(strings.Repeat("-", 80)) - displayBlobInfo(result.IDs[i], blob) + displayBlobInfo(blobData) // Display the decoded content - if header := tryDecodeHeader(blob); header != nil { + if header := tryDecodeHeader(blobData); header != nil { printTypeHeader("SignedHeader", "") displayHeader(header) - } else if data := tryDecodeData(blob); data != nil { + } else if data := tryDecodeData(blobData); data != nil { printTypeHeader("SignedData", "") displayData(data) } @@ -220,40 +212,37 @@ func searchForHeight(ctx context.Context, client *jsonrpc.Client, startHeight ui return nil } -func queryHeight(ctx context.Context, client *jsonrpc.Client, height uint64, namespace []byte) error { - result, err := client.DA.GetIDs(ctx, height, namespace) +func queryHeight(ctx context.Context, client *blobrpc.Client, height uint64, namespace []byte) error { + ns, err := share.NewNamespaceFromBytes(namespace) if err != nil { - // Handle "blob not found" as a normal case - if err.Error() == "blob: not found" || strings.Contains(err.Error(), "blob: not found") { - fmt.Printf("No blobs found at height %d\n", height) + return fmt.Errorf("invalid namespace: %w", err) + } + + blobs, err := client.Blob.GetAll(ctx, height, []share.Namespace{ns}) + if err != nil { + if strings.Contains(err.Error(), "future") { + fmt.Printf("Height %d is in the future (not yet available)\n", height) return nil } - // Handle future height errors gracefully - if strings.Contains(err.Error(), "height") && strings.Contains(err.Error(), "future") { - fmt.Printf("Height %d is in the future (not yet available)\n", height) + if strings.Contains(err.Error(), "not found") { + fmt.Printf("No blobs found at height %d\n", height) return nil } - return fmt.Errorf("failed to get IDs: %w", err) + return fmt.Errorf("failed to get blobs: %w", err) } - if result == nil || len(result.IDs) == 0 { + if len(blobs) == 0 { fmt.Printf("No blobs found at height %d\n", height) return nil } - fmt.Printf("Found %d blob(s) at height %d\n", len(result.IDs), height) - fmt.Printf("Timestamp: %s\n", result.Timestamp.Format(time.RFC3339)) + fmt.Printf("Found %d blob(s) at height %d\n", len(blobs), height) fmt.Println() - // Get the actual blob data - blobs, err := client.DA.Get(ctx, result.IDs, namespace) - if err != nil { - return fmt.Errorf("failed to get blob data: %w", err) - } - // Process each blob with optional height filtering displayedBlobs := 0 - for i, blob := range blobs { + for _, blob := range blobs { + blobData := blob.Data() shouldDisplay := true var blobHeight uint64 @@ -262,12 +251,12 @@ func queryHeight(ctx context.Context, client *jsonrpc.Client, height uint64, nam shouldDisplay = false // Try to decode as header first to check height - if header := tryDecodeHeader(blob); header != nil { + if header := tryDecodeHeader(blobData); header != nil { blobHeight = header.Height() if blobHeight == filterHeight { shouldDisplay = true } - } else if data := tryDecodeData(blob); data != nil { + } else if data := tryDecodeData(blobData); data != nil { if data.Metadata != nil { blobHeight = data.Height() if blobHeight == filterHeight { @@ -283,18 +272,18 @@ func queryHeight(ctx context.Context, client *jsonrpc.Client, height uint64, nam displayedBlobs++ printBlobHeader(displayedBlobs, -1) // -1 indicates filtered mode - displayBlobInfo(result.IDs[i], blob) + displayBlobInfo(blobData) // Try to decode as header first - if header := tryDecodeHeader(blob); header != nil { + if header := tryDecodeHeader(blobData); header != nil { printTypeHeader("SignedHeader", "") displayHeader(header) - } else if data := tryDecodeData(blob); data != nil { + } else if data := tryDecodeData(blobData); data != nil { printTypeHeader("SignedData", "") displayData(data) } else { printTypeHeader("Raw Data", "") - displayRawData(blob) + displayRawData(blobData) } if displayedBlobs > 1 { @@ -345,15 +334,8 @@ func printBlobHeader(current, total int) { fmt.Println(strings.Repeat("-", 80)) } -func displayBlobInfo(id coreda.ID, blob []byte) { - fmt.Printf("ID: %s\n", formatHash(hex.EncodeToString(id))) +func displayBlobInfo(blob []byte) { fmt.Printf("Size: %s\n", formatSize(len(blob))) - - // Try to parse the ID to show height and commitment - if idHeight, commitment, err := coreda.SplitID(id); err == nil { - fmt.Printf("ID Height: %d\n", idHeight) - fmt.Printf("Commitment: %s\n", formatHash(hex.EncodeToString(commitment))) - } } func printTypeHeader(title, color string) { @@ -504,7 +486,7 @@ func tryDecodeData(bz []byte) *types.SignedData { return &signedData } -func createDAClient() (*jsonrpc.Client, error) { +func createDAClient() (*blobrpc.Client, func(), error) { logger := zerolog.New(zerolog.ConsoleWriter{Out: os.Stderr}).Level(zerolog.InfoLevel) if verbose { logger = logger.Level(zerolog.DebugLevel) @@ -513,12 +495,13 @@ func createDAClient() (*jsonrpc.Client, error) { ctx, cancel := context.WithTimeout(context.Background(), timeout) defer cancel() - client, err := jsonrpc.NewClient(ctx, logger, daURL, authToken, maxBlobSize) + client, err := blobrpc.NewClient(ctx, daURL, authToken, "") if err != nil { - return nil, fmt.Errorf("failed to create DA client: %w", err) + return nil, func() {}, fmt.Errorf("failed to create DA client: %w", err) } - return client, nil + closeFn := func() { client.Close() } + return client, closeFn, nil } func parseNamespace(ns string) ([]byte, error) { @@ -528,7 +511,7 @@ func parseNamespace(ns string) ([]byte, error) { } // If not valid hex or not 29 bytes, treat as string identifier - namespace := coreda.NamespaceFromString(ns) + namespace := datypes.NamespaceFromString(ns) return namespace.Bytes(), nil } diff --git a/tools/da-debug/main_test.go b/tools/da-debug/main_test.go index 09818b4bb5..d10bd3ffed 100644 --- a/tools/da-debug/main_test.go +++ b/tools/da-debug/main_test.go @@ -4,7 +4,7 @@ import ( "encoding/hex" "testing" - coreda "github.com/evstack/ev-node/core/da" + blobrpc "github.com/evstack/ev-node/pkg/da/jsonrpc" ) func TestParseNamespace(t *testing.T) { @@ -201,10 +201,7 @@ func TestIDSplitting(t *testing.T) { copy(id[8:], commitment) // Test splitting - parsedHeight, parsedCommitment, err := coreda.SplitID(id) - if err != nil { - t.Errorf("SplitID() unexpected error: %v", err) - } + parsedHeight, parsedCommitment := blobrpc.SplitID(id) if parsedHeight != height { t.Errorf("SplitID() height = %d, expected %d", parsedHeight, height) diff --git a/apps/local-da/Dockerfile b/tools/local-da/Dockerfile similarity index 100% rename from apps/local-da/Dockerfile rename to tools/local-da/Dockerfile diff --git a/da/cmd/local-da/README.md b/tools/local-da/README.md similarity index 97% rename from da/cmd/local-da/README.md rename to tools/local-da/README.md index 3513f17a6e..0db00aacf9 100644 --- a/da/cmd/local-da/README.md +++ b/tools/local-da/README.md @@ -110,5 +110,5 @@ output: [2] [xh][xh] -[da]: https://github.com/evstack/ev-node/blob/main/core/da/da.go#L11 +[da]: https://github.com/evstack/ev-node/blob/main/block/public.go [xh]: https://github.com/ducaale/xh diff --git a/da/cmd/local-da/local.go b/tools/local-da/local.go similarity index 84% rename from da/cmd/local-da/local.go rename to tools/local-da/local.go index bd36393016..86907fc4dd 100644 --- a/da/cmd/local-da/local.go +++ b/tools/local-da/local.go @@ -13,7 +13,8 @@ import ( "sync" "time" - coreda "github.com/evstack/ev-node/core/da" + blobrpc "github.com/evstack/ev-node/pkg/da/jsonrpc" + datypes "github.com/evstack/ev-node/pkg/da/types" "github.com/rs/zerolog" ) @@ -28,6 +29,7 @@ type LocalDA struct { mu *sync.Mutex // protects data and height data map[uint64][]kvp timestamps map[uint64]time.Time + blobData map[uint64][]*blobrpc.Blob maxBlobSize uint64 height uint64 privKey ed25519.PrivateKey @@ -40,12 +42,13 @@ type kvp struct { key, value []byte } -// NewLocalDA create new instance of DummyDA +// NewLocalDA creates a new in-memory LocalDA instance (testing only). func NewLocalDA(logger zerolog.Logger, opts ...func(*LocalDA) *LocalDA) *LocalDA { da := &LocalDA{ mu: new(sync.Mutex), data: make(map[uint64][]kvp), timestamps: make(map[uint64]time.Time), + blobData: make(map[uint64][]*blobrpc.Blob), maxBlobSize: DefaultMaxBlobSize, logger: logger, } @@ -57,8 +60,6 @@ func NewLocalDA(logger zerolog.Logger, opts ...func(*LocalDA) *LocalDA) *LocalDA return da } -var _ coreda.DA = &LocalDA{} - // validateNamespace checks that namespace is exactly 29 bytes func validateNamespace(ns []byte) error { if len(ns) != 29 { @@ -74,7 +75,7 @@ func (d *LocalDA) MaxBlobSize(ctx context.Context) (uint64, error) { } // Get returns Blobs for given IDs. -func (d *LocalDA) Get(ctx context.Context, ids []coreda.ID, ns []byte) ([]coreda.Blob, error) { +func (d *LocalDA) Get(ctx context.Context, ids []datypes.ID, ns []byte) ([]datypes.Blob, error) { if err := validateNamespace(ns); err != nil { d.logger.Error().Err(err).Msg("Get: invalid namespace") return nil, err @@ -82,7 +83,7 @@ func (d *LocalDA) Get(ctx context.Context, ids []coreda.ID, ns []byte) ([]coreda d.logger.Debug().Interface("ids", ids).Msg("Get called") d.mu.Lock() defer d.mu.Unlock() - blobs := make([]coreda.Blob, len(ids)) + blobs := make([]datypes.Blob, len(ids)) for i, id := range ids { if len(id) < 8 { d.logger.Error().Interface("id", id).Msg("Get: invalid ID length") @@ -98,7 +99,7 @@ func (d *LocalDA) Get(ctx context.Context, ids []coreda.ID, ns []byte) ([]coreda } if !found { d.logger.Warn().Interface("id", id).Uint64("height", height).Msg("Get: blob not found") - return nil, coreda.ErrBlobNotFound + return nil, datypes.ErrBlobNotFound } } d.logger.Debug().Int("count", len(blobs)).Msg("Get successful") @@ -106,7 +107,7 @@ func (d *LocalDA) Get(ctx context.Context, ids []coreda.ID, ns []byte) ([]coreda } // GetIDs returns IDs of Blobs at given DA height. -func (d *LocalDA) GetIDs(ctx context.Context, height uint64, ns []byte) (*coreda.GetIDsResult, error) { +func (d *LocalDA) GetIDs(ctx context.Context, height uint64, ns []byte) (*datypes.GetIDsResult, error) { if err := validateNamespace(ns); err != nil { d.logger.Error().Err(err).Msg("GetIDs: invalid namespace") return nil, err @@ -117,7 +118,7 @@ func (d *LocalDA) GetIDs(ctx context.Context, height uint64, ns []byte) (*coreda if height > d.height { d.logger.Error().Uint64("requested", height).Uint64("current", d.height).Msg("GetIDs: height in future") - return nil, fmt.Errorf("height %d is in the future: %w", height, coreda.ErrHeightFromFuture) + return nil, fmt.Errorf("height %d is in the future: %w", height, datypes.ErrHeightFromFuture) } kvps, ok := d.data[height] @@ -126,16 +127,16 @@ func (d *LocalDA) GetIDs(ctx context.Context, height uint64, ns []byte) (*coreda return nil, nil } - ids := make([]coreda.ID, len(kvps)) + ids := make([]datypes.ID, len(kvps)) for i, kv := range kvps { ids[i] = kv.key } d.logger.Debug().Int("count", len(ids)).Msg("GetIDs successful") - return &coreda.GetIDsResult{IDs: ids, Timestamp: d.timestamps[height]}, nil + return &datypes.GetIDsResult{IDs: ids, Timestamp: d.timestamps[height]}, nil } // GetProofs returns inclusion Proofs for all Blobs located in DA at given height. -func (d *LocalDA) GetProofs(ctx context.Context, ids []coreda.ID, ns []byte) ([]coreda.Proof, error) { +func (d *LocalDA) GetProofs(ctx context.Context, ids []datypes.ID, ns []byte) ([]datypes.Proof, error) { if err := validateNamespace(ns); err != nil { d.logger.Error().Err(err).Msg("GetProofs: invalid namespace") return nil, err @@ -149,7 +150,7 @@ func (d *LocalDA) GetProofs(ctx context.Context, ids []coreda.ID, ns []byte) ([] d.mu.Lock() defer d.mu.Unlock() - proofs := make([]coreda.Proof, len(blobs)) + proofs := make([]datypes.Proof, len(blobs)) for i, blob := range blobs { proofs[i] = d.getProof(ids[i], blob) } @@ -158,13 +159,13 @@ func (d *LocalDA) GetProofs(ctx context.Context, ids []coreda.ID, ns []byte) ([] } // Commit returns cryptographic Commitments for given blobs. -func (d *LocalDA) Commit(ctx context.Context, blobs []coreda.Blob, ns []byte) ([]coreda.Commitment, error) { +func (d *LocalDA) Commit(ctx context.Context, blobs []datypes.Blob, ns []byte) ([]datypes.Commitment, error) { if err := validateNamespace(ns); err != nil { d.logger.Error().Err(err).Msg("Commit: invalid namespace") return nil, err } d.logger.Debug().Int("numBlobs", len(blobs)).Msg("Commit called") - commits := make([]coreda.Commitment, len(blobs)) + commits := make([]datypes.Commitment, len(blobs)) for i, blob := range blobs { commits[i] = d.getHash(blob) } @@ -173,7 +174,7 @@ func (d *LocalDA) Commit(ctx context.Context, blobs []coreda.Blob, ns []byte) ([ } // SubmitWithOptions stores blobs in DA layer (options are ignored). -func (d *LocalDA) SubmitWithOptions(ctx context.Context, blobs []coreda.Blob, gasPrice float64, ns []byte, _ []byte) ([]coreda.ID, error) { +func (d *LocalDA) SubmitWithOptions(ctx context.Context, blobs []datypes.Blob, gasPrice float64, ns []byte, _ []byte) ([]datypes.ID, error) { if err := validateNamespace(ns); err != nil { d.logger.Error().Err(err).Msg("SubmitWithOptions: invalid namespace") return nil, err @@ -184,13 +185,13 @@ func (d *LocalDA) SubmitWithOptions(ctx context.Context, blobs []coreda.Blob, ga for i, blob := range blobs { if uint64(len(blob)) > d.maxBlobSize { d.logger.Error().Int("blobIndex", i).Int("blobSize", len(blob)).Uint64("maxBlobSize", d.maxBlobSize).Msg("SubmitWithOptions: blob size exceeds limit") - return nil, coreda.ErrBlobSizeOverLimit + return nil, datypes.ErrBlobSizeOverLimit } } d.mu.Lock() defer d.mu.Unlock() - ids := make([]coreda.ID, len(blobs)) + ids := make([]datypes.ID, len(blobs)) d.height += 1 d.timestamps[d.height] = time.Now() for i, blob := range blobs { @@ -203,7 +204,7 @@ func (d *LocalDA) SubmitWithOptions(ctx context.Context, blobs []coreda.Blob, ga } // Submit stores blobs in DA layer (options are ignored). -func (d *LocalDA) Submit(ctx context.Context, blobs []coreda.Blob, gasPrice float64, ns []byte) ([]coreda.ID, error) { +func (d *LocalDA) Submit(ctx context.Context, blobs []datypes.Blob, gasPrice float64, ns []byte) ([]datypes.ID, error) { if err := validateNamespace(ns); err != nil { d.logger.Error().Err(err).Msg("Submit: invalid namespace") return nil, err @@ -214,13 +215,13 @@ func (d *LocalDA) Submit(ctx context.Context, blobs []coreda.Blob, gasPrice floa for i, blob := range blobs { if uint64(len(blob)) > d.maxBlobSize { d.logger.Error().Int("blobIndex", i).Int("blobSize", len(blob)).Uint64("maxBlobSize", d.maxBlobSize).Msg("Submit: blob size exceeds limit") - return nil, coreda.ErrBlobSizeOverLimit + return nil, datypes.ErrBlobSizeOverLimit } } d.mu.Lock() defer d.mu.Unlock() - ids := make([]coreda.ID, len(blobs)) + ids := make([]datypes.ID, len(blobs)) d.height += 1 d.timestamps[d.height] = time.Now() for i, blob := range blobs { @@ -233,7 +234,7 @@ func (d *LocalDA) Submit(ctx context.Context, blobs []coreda.Blob, gasPrice floa } // Validate checks the Proofs for given IDs. -func (d *LocalDA) Validate(ctx context.Context, ids []coreda.ID, proofs []coreda.Proof, ns []byte) ([]bool, error) { +func (d *LocalDA) Validate(ctx context.Context, ids []datypes.ID, proofs []datypes.Proof, ns []byte) ([]bool, error) { if err := validateNamespace(ns); err != nil { d.logger.Error().Err(err).Msg("Validate: invalid namespace") return nil, err diff --git a/da/cmd/local-da/main.go b/tools/local-da/main.go similarity index 77% rename from da/cmd/local-da/main.go rename to tools/local-da/main.go index 5823b77156..b5c41229ba 100644 --- a/da/cmd/local-da/main.go +++ b/tools/local-da/main.go @@ -7,10 +7,9 @@ import ( "os" "os/signal" "syscall" + "time" "github.com/rs/zerolog" - - proxy "github.com/evstack/ev-node/da/jsonrpc" ) const ( @@ -46,15 +45,24 @@ func main() { } da := NewLocalDA(logger, opts...) - srv := proxy.NewServer(logger, host, port, da) - logger.Info().Str("host", host).Str("port", port).Uint64("maxBlobSize", maxBlobSize).Msg("Listening on") - if err := srv.Start(context.Background()); err != nil { - logger.Error().Err(err).Msg("error while serving") + addr := fmt.Sprintf("%s:%s", host, port) + srv, err := startBlobServer(logger, addr, da) + if err != nil { + logger.Error().Err(err).Msg("error while creating blob RPC server") + os.Exit(1) } + logger.Info().Str("host", host).Str("port", port).Uint64("maxBlobSize", maxBlobSize).Msg("Listening on") + interrupt := make(chan os.Signal, 1) signal.Notify(interrupt, os.Interrupt, syscall.SIGINT) <-interrupt fmt.Println("\nCtrl+C pressed. Exiting...") + + ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) + defer cancel() + if err := srv.Shutdown(ctx); err != nil { + logger.Error().Err(err).Msg("error shutting down server") + } os.Exit(0) } diff --git a/tools/local-da/rpc.go b/tools/local-da/rpc.go new file mode 100644 index 0000000000..60dd51ac40 --- /dev/null +++ b/tools/local-da/rpc.go @@ -0,0 +1,157 @@ +package main + +import ( + "context" + "errors" + "net/http" + "time" + + libshare "github.com/celestiaorg/go-square/v3/share" + fjrpc "github.com/filecoin-project/go-jsonrpc" + "github.com/rs/zerolog" + + jsonrpc "github.com/evstack/ev-node/pkg/da/jsonrpc" + datypes "github.com/evstack/ev-node/pkg/da/types" +) + +// blobServer exposes a minimal Celestia-like blob RPC surface backed by LocalDA. +type blobServer struct { + da *LocalDA + logger zerolog.Logger +} + +// Submit stores blobs and returns the height they were included at. +func (s *blobServer) Submit(_ context.Context, blobs []*jsonrpc.Blob, _ *jsonrpc.SubmitOptions) (uint64, error) { + s.da.mu.Lock() + defer s.da.mu.Unlock() + + s.da.height++ + height := s.da.height + + if len(blobs) == 0 { + s.da.timestamps[height] = time.Now() + return height, nil + } + + for i, b := range blobs { + if uint64(len(b.Data())) > s.da.maxBlobSize { + return 0, datypes.ErrBlobSizeOverLimit + } + if b.Commitment == nil { + // ensure commitment exists, compute from blob + if rebuilt, err := jsonrpc.NewBlob(b.ShareVersion(), b.Namespace(), b.Data(), b.Signer(), nil); err == nil { + blobs[i] = rebuilt + b = rebuilt + } + } + s.da.blobData[height] = append(s.da.blobData[height], b) + } + s.da.timestamps[height] = time.Now() + + return height, nil +} + +// Get returns a blob by height/namespace/commitment. +func (s *blobServer) Get(_ context.Context, height uint64, namespace libshare.Namespace, commitment jsonrpc.Commitment) (*jsonrpc.Blob, error) { + s.da.mu.Lock() + defer s.da.mu.Unlock() + + if height > s.da.height { + return nil, datypes.ErrHeightFromFuture + } + + blobs, ok := s.da.blobData[height] + if !ok { + return nil, datypes.ErrBlobNotFound + } + for _, b := range blobs { + if b.Namespace().Equals(namespace) && b.EqualCommitment(commitment) { + return b, nil + } + } + return nil, datypes.ErrBlobNotFound +} + +// GetAll returns blobs matching any of the provided namespaces at the given height. +func (s *blobServer) GetAll(_ context.Context, height uint64, namespaces []libshare.Namespace) ([]*jsonrpc.Blob, error) { + s.da.mu.Lock() + defer s.da.mu.Unlock() + + if height > s.da.height { + return nil, datypes.ErrHeightFromFuture + } + + blobs, ok := s.da.blobData[height] + if !ok { + return nil, datypes.ErrBlobNotFound + } + + // If no namespaces specified, return everything at height. + if len(namespaces) == 0 { + return blobs, nil + } + + out := make([]*jsonrpc.Blob, 0, len(blobs)) + for _, b := range blobs { + for _, ns := range namespaces { + if b.Namespace().Equals(ns) { + out = append(out, b) + break + } + } + } + + if len(out) == 0 { + return nil, datypes.ErrBlobNotFound + } + return out, nil +} + +// GetProof returns a placeholder proof; LocalDA does not generate real proofs. +func (s *blobServer) GetProof(_ context.Context, _ uint64, _ libshare.Namespace, _ jsonrpc.Commitment) (*jsonrpc.Proof, error) { + return &jsonrpc.Proof{}, nil +} + +// Included reports whether a commitment is present at a given height/namespace. +func (s *blobServer) Included(_ context.Context, height uint64, namespace libshare.Namespace, _ *jsonrpc.Proof, commitment jsonrpc.Commitment) (bool, error) { + _, err := s.Get(context.Background(), height, namespace, commitment) + if err != nil { + if errors.Is(err, datypes.ErrBlobNotFound) { + return false, nil + } + return false, err + } + return true, nil +} + +// GetCommitmentProof returns a placeholder commitment proof; LocalDA does not generate real proofs. +func (s *blobServer) GetCommitmentProof(_ context.Context, _ uint64, _ libshare.Namespace, _ []byte) (*jsonrpc.CommitmentProof, error) { + return &jsonrpc.CommitmentProof{}, nil +} + +// Subscribe returns a closed channel; LocalDA does not push live updates. +func (s *blobServer) Subscribe(_ context.Context, _ libshare.Namespace) (<-chan *jsonrpc.SubscriptionResponse, error) { + ch := make(chan *jsonrpc.SubscriptionResponse) + close(ch) + return ch, nil +} + +// startBlobServer starts an HTTP JSON-RPC server on addr serving the blob namespace. +func startBlobServer(logger zerolog.Logger, addr string, da *LocalDA) (*http.Server, error) { + rpc := fjrpc.NewServer() + rpc.Register("blob", &blobServer{da: da, logger: logger}) + + srv := &http.Server{ + Addr: addr, + Handler: http.HandlerFunc(rpc.ServeHTTP), + ReadHeaderTimeout: 2 * time.Second, + } + + go func() { + if err := srv.ListenAndServe(); err != nil && !errors.Is(err, http.ErrServerClosed) { + logger.Error().Err(err).Msg("blob RPC server failed") + } + }() + + return srv, nil +} diff --git a/types/CLAUDE.md b/types/CLAUDE.md index aafdd289a2..d5dcd7f9d8 100644 --- a/types/CLAUDE.md +++ b/types/CLAUDE.md @@ -86,7 +86,7 @@ The types package defines the core data structures and types used throughout ev- - Namespace support - Gas price configuration - Submission options handling -- **Status Codes** (defined in `core/da`): +- **Status Codes** (defined in `pkg/da/types`): - `StatusContextCanceled`: Submission canceled - `StatusNotIncludedInBlock`: Transaction timeout - `StatusAlreadyInMempool`: Duplicate transaction