Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
bd0c95d
sync service fix for when we are not on genesis but have an empty store
tac0turtle Nov 12, 2025
1e203ba
remove isgenesis
tac0turtle Nov 12, 2025
1911431
lint
tac0turtle Nov 12, 2025
c378824
Merge branch 'main' into marko/sync_service_fix
tac0turtle Nov 13, 2025
3327cf6
panic test
tac0turtle Nov 13, 2025
f235c72
cmd: p2p store info (#2835)
tac0turtle Nov 13, 2025
3d78fd1
add p2p store info
tac0turtle Nov 14, 2025
d85c1d8
lint
tac0turtle Nov 14, 2025
f728e4d
refactor: remove trusted hash (#2838)
tac0turtle Nov 14, 2025
a9175d3
Merge branch 'main' into marko/sync_service_fix
tac0turtle Nov 14, 2025
453fb82
increase timeout
tac0turtle Nov 14, 2025
f26536b
fix timeouts
tac0turtle Nov 14, 2025
d8e9fa1
remove extra context
tac0turtle Nov 14, 2025
5e0396a
allow execution to be ahead as we will check apphash if anything is
tac0turtle Nov 15, 2025
a15b177
implement atomic store initialization in SyncService
tac0turtle Nov 17, 2025
7c97e7e
Merge branch 'main' into marko/sync_service_fix
tac0turtle Nov 18, 2025
1f840cc
fix: reduce default DA timeout and clean up unused fields in P2P stor…
tac0turtle Nov 18, 2025
1b424f5
chore: update changelog
tac0turtle Nov 18, 2025
312c563
fix: prevent race condition during store initialization in WriteToSto…
tac0turtle Nov 18, 2025
4a66f9e
remove redundant race condition handling in initFromP2PWithRetry
tac0turtle Nov 18, 2025
6c0654e
add read-only mode for key-value store and update store-info command
tac0turtle Nov 19, 2025
6787bed
remove extra readonly opener
tac0turtle Nov 19, 2025
b628459
check the store and other things
tac0turtle Nov 19, 2025
4834564
remove panic and store cmd
tac0turtle Nov 19, 2025
b36171e
fix lint
tac0turtle Nov 20, 2025
6e6d2e8
remove changelog entry
tac0turtle Nov 20, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,15 +12,25 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Added

- Enhanced health check system with separate liveness (`/health/live`) and readiness (`/health/ready`) HTTP endpoints. Readiness endpoint includes P2P listening check and aggregator block production rate validation (5x block time threshold). ([#2800](https://github.com/evstack/ev-node/pull/2800))
- Added `GetP2PStoreInfo` RPC method to retrieve head/tail metadata for go-header stores used by P2P sync ([#2835](https://github.com/evstack/ev-node/pull/2835))
- Added protobuf definitions for `P2PStoreEntry` and `P2PStoreSnapshot` messages to support P2P store inspection

### Changed

- Remove GasPrice and GasMultiplier from DA interface and configuration to use celestia-node's native fee estimation. ([#2822](https://github.com/evstack/ev-node/pull/2822))
- Use cache instead of in memory store for reaper. Persist cache on reload. Autoclean after 24 hours. ([#2811](https://github.com/evstack/ev-node/pull/2811))
- Improved P2P sync service store initialization to be atomic and prevent race conditions ([#2838](https://github.com/evstack/ev-node/pull/2838))
- Enhanced P2P bootstrap behavior to intelligently detect starting height from local store instead of requiring trusted hash
- Relaxed execution layer height validation in block replay to allow execution to be ahead of target height, enabling recovery from manual intervention scenarios

### Removed

- **BREAKING:** Removed `evnode.v1.HealthService` gRPC endpoint. Use HTTP endpoints: `GET /health/live` and `GET /health/ready`. ([#2800](https://github.com/evstack/ev-node/pull/2800))
- **BREAKING:** Removed `TrustedHash` configuration option and `--evnode.node.trusted_hash` flag. Sync service now automatically determines starting height from local store state ([#2838](https://github.com/evstack/ev-node/pull/2838))

### Fixed

- Fixed sync service initialization issue when node is not on genesis but has an empty store

## v1.0.0-beta.9

Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ Ev-node is the basis of the Evolve Stack. For more in-depth information about Ev
[![GoDoc](https://godoc.org/github.com/evstack/ev-node?status.svg)](https://godoc.org/github.com/evstack/ev-node)
<!-- markdownlint-enable MD013 -->

> **⚠️ Version Notice**: Do not use tags or releases before v1.*. Pre-v1 releases are not stable and should be considered abandoned.
> **⚠️ Version Notice**: Do not use tags or releases before v1.*. Pre-v1 releases are not stable and should be considered abandoned.

## Using Evolve

Expand Down
2 changes: 1 addition & 1 deletion RELEASE.md
Original file line number Diff line number Diff line change
Expand Up @@ -303,7 +303,7 @@ go get github.com/evstack/ev-node/[email protected]

- Wait 5-30 minutes for propagation
- Use `go list -m` to verify availability
- Check https://proxy.golang.org/
- Check <https://proxy.golang.org/>

**Dependency version conflicts**

Expand Down
12 changes: 7 additions & 5 deletions block/internal/common/replay.go
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ func NewReplayer(
// This is useful for crash recovery scenarios where ev-node is ahead of the execution layer.
//
// Returns:
// - error if sync fails or if execution layer is ahead of ev-node (unexpected state)
// - error if sync fails
func (s *Replayer) SyncToHeight(ctx context.Context, targetHeight uint64) error {
// Check if the executor implements HeightProvider
execHeightProvider, ok := s.exec.(coreexecutor.HeightProvider)
Expand All @@ -67,13 +67,15 @@ func (s *Replayer) SyncToHeight(ctx context.Context, targetHeight uint64) error
Uint64("exec_layer_height", execHeight).
Msg("execution layer height check")

// If execution layer is ahead, this is unexpected, fail hard
// If execution layer is ahead, skip syncing and continue. This can happen if execution
// progressed independently (e.g. after manual intervention). We log it for visibility but
// do not treat it as fatal.
if execHeight > targetHeight {
s.logger.Error().
s.logger.Warn().
Uint64("target_height", targetHeight).
Uint64("exec_layer_height", execHeight).
Msg("execution layer is ahead of target height - this should not happen")
return fmt.Errorf("execution layer height (%d) is ahead of target height (%d)", execHeight, targetHeight)
Msg("execution layer is ahead of target height - skipping replay")
return nil
}

// If execution layer is behind, sync the missing blocks
Expand Down
8 changes: 5 additions & 3 deletions block/internal/common/replay_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -141,10 +141,12 @@ func TestReplayer_SyncToHeight_ExecutorAhead(t *testing.T) {

mockExec.On("GetLatestHeight", mock.Anything).Return(execHeight, nil)

// Execute sync - should fail
// Execute sync - should just log and continue without error
err := syncer.SyncToHeight(ctx, targetHeight)
require.Error(t, err)
require.Contains(t, err.Error(), "execution layer height (101) is ahead of target height (100)")
require.NoError(t, err)

// No replay should be attempted
mockExec.AssertNotCalled(t, "ExecuteTxs", mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything)
}

func TestReplayer_SyncToHeight_NoHeightProvider(t *testing.T) {
Expand Down
6 changes: 3 additions & 3 deletions block/internal/syncing/da_retriever_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -128,9 +128,9 @@ func TestDARetriever_RetrieveFromDA_Timeout(t *testing.T) {
assert.Contains(t, err.Error(), "context deadline exceeded")
assert.Len(t, events, 0)

// Verify timeout occurred approximately at expected time (with some tolerance)
assert.Greater(t, duration, 9*time.Second, "should timeout after approximately 10 seconds")
assert.Less(t, duration, 12*time.Second, "should not take much longer than timeout")
// Verify timeout occurred approximately at the helper timeout (with some tolerance)
assert.Greater(t, duration, defaultDATimeout-2*time.Second, "should timeout close to the helper timeout")
assert.Less(t, duration, defaultDATimeout+time.Second, "should not take much longer than timeout")
}

func TestDARetriever_RetrieveFromDA_TimeoutFast(t *testing.T) {
Expand Down
6 changes: 1 addition & 5 deletions block/internal/syncing/syncer.go
Original file line number Diff line number Diff line change
Expand Up @@ -299,11 +299,7 @@ func (s *Syncer) fetchDAUntilCaughtUp() error {

daHeight := s.GetDAHeight()

// Create a new context with a timeout for the DA call
ctx, cancel := context.WithTimeout(s.ctx, 5*time.Second)
defer cancel()

events, err := s.daRetriever.RetrieveFromDA(ctx, daHeight)
events, err := s.daRetriever.RetrieveFromDA(s.ctx, daHeight)
if err != nil {
switch {
case errors.Is(err, coreda.ErrBlobNotFound):
Expand Down
2 changes: 1 addition & 1 deletion docs/guides/full-node.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 [here](./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#setting-up-a-full-node).**

## Prerequisites

Expand Down
21 changes: 2 additions & 19 deletions docs/learn/config.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@ This document provides a comprehensive reference for all configuration options a
- [Maximum Pending Blocks](#maximum-pending-blocks)
- [Lazy Mode (Lazy Aggregator)](#lazy-mode-lazy-aggregator)
- [Lazy Block Interval](#lazy-block-interval)
- [Trusted Hash](#trusted-hash)
- [Data Availability Configuration (`da`)](#data-availability-configuration-da)
- [DA Service Address](#da-service-address)
- [DA Authentication Token](#da-authentication-token)
Expand Down Expand Up @@ -275,24 +274,6 @@ _Example:_ `--rollkit.node.lazy_block_interval 1m`
_Default:_ `"30s"`
_Constant:_ `FlagLazyBlockTime`

### Trusted Hash

**Description:**
The initial trusted hash used to bootstrap the header exchange service. This allows nodes to start synchronizing from a specific, trusted point in the chain history instead of from the genesis block. When provided, the node will fetch the corresponding header/block from peers using this hash. If not provided, the node attempts to sync from genesis.

**YAML:**

```yaml
node:
trusted_hash: "YOUR_TRUSTED_HASH_HEX_STRING"
```

**Command-line Flag:**
`--rollkit.node.trusted_hash <string>`
_Example:_ `--rollkit.node.trusted_hash ABCDEF012345...`
_Default:_ `""` (empty, sync from genesis)
_Constant:_ `FlagTrustedHash`

## Data Availability Configuration (`da`)

Parameters for connecting and interacting with the Data Availability (DA) layer, which Evolve uses to publish block data.
Expand Down Expand Up @@ -658,6 +639,7 @@ curl http://localhost:7331/health/live
#### `/health/ready`

Returns `200 OK` if the node can serve correct data. Checks:

- P2P is listening (if enabled)
- Has synced blocks
- Not too far behind network
Expand All @@ -669,6 +651,7 @@ curl http://localhost:7331/health/ready
```

Configure max blocks behind:

```yaml
node:
readiness_max_blocks_behind: 15
Expand Down
7 changes: 3 additions & 4 deletions docs/learn/specs/header-sync.md
Original file line number Diff line number Diff line change
Expand Up @@ -68,10 +68,9 @@ The Executor component (in aggregator nodes) broadcasts headers and data in para
- Chain IDs for pubsub topics are also separated:
- Headers: `{chainID}-headerSync` creates topic like `/gm-headerSync/header-sub/v0.0.1`
- Data: `{chainID}-dataSync` creates topic like `/gm-dataSync/header-sub/v0.0.1`
- Both stores must be initialized with genesis items before starting:
- Header store needs genesis header
- Data store needs genesis data (if applicable)
- Genesis items can be loaded via `NodeConfig.TrustedHash` or P2P network query
- Both stores must contain at least one item before the syncer starts:
- On first boot, the services fetch the configured genesis height from peers
- On restart, each store reuses its latest item to derive the initial height requested from peers
- Sync services work only when connected to P2P network via `P2PConfig.Seeds`
- Node context is passed to all components for graceful shutdown
- Headers and data are linked through DataHash but synced independently
Expand Down
117 changes: 117 additions & 0 deletions docs/src/openapi-rpc.json
Original file line number Diff line number Diff line change
Expand Up @@ -472,6 +472,48 @@
}
}
}
},
"/evnode.v1.StoreService/GetP2PStoreInfo": {
"post": {
"tags": [
"Store Service"
],
"summary": "Inspect go-header stores",
"description": "Returns head/tail information for the header and data go-header stores used by P2P sync.",
"operationId": "getP2PStoreInfo",
"requestBody": {
"description": "Empty request",
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Empty"
},
"examples": {
"default": {
"summary": "Get go-header store snapshots",
"value": {}
}
}
}
}
},
"responses": {
"200": {
"description": "Snapshots returned successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/GetP2PStoreInfoResponse"
}
}
}
},
"500": {
"$ref": "#/components/responses/InternalError"
}
}
}
}
},
"components": {
Expand Down Expand Up @@ -1012,6 +1054,81 @@
"description": "Additional error details"
}
}
},
"P2PStoreEntry": {
"type": "object",
"description": "Head or tail entry for a go-header store",
"required": [
"height",
"hash"
],
"properties": {
"height": {
"type": "integer",
"format": "int64",
"description": "Block height"
},
"hash": {
"type": "string",
"format": "byte",
"description": "Header/data hash (base64-encoded)"
},
"time": {
"type": "string",
"format": "date-time",
"description": "Entry timestamp"
}
}
},
"P2PStoreSnapshot": {
"type": "object",
"description": "Snapshot of a go-header store",
"required": [
"label",
"height",
"head_present",
"tail_present"
],
"properties": {
"label": {
"type": "string",
"description": "Human friendly store label"
},
"height": {
"type": "integer",
"format": "int64",
"description": "Highest contiguous height"
},
"head_present": {
"type": "boolean",
"description": "Whether a head entry exists"
},
"head": {
"$ref": "#/components/schemas/P2PStoreEntry"
},
"tail_present": {
"type": "boolean",
"description": "Whether a tail entry exists"
},
"tail": {
"$ref": "#/components/schemas/P2PStoreEntry"
}
}
},
"GetP2PStoreInfoResponse": {
"type": "object",
"description": "Snapshot of the header and data go-header stores",
"required": [
"stores"
],
"properties": {
"stores": {
"type": "array",
"items": {
"$ref": "#/components/schemas/P2PStoreSnapshot"
}
}
}
}
}
}
Expand Down
11 changes: 10 additions & 1 deletion node/full.go
Original file line number Diff line number Diff line change
Expand Up @@ -291,7 +291,16 @@ func (n *FullNode) Run(parentCtx context.Context) error {
return min(hHeight, dHeight)
}

handler, err := rpcserver.NewServiceHandler(n.Store, n.p2pClient, n.genesis.ProposerAddress, n.Logger, n.nodeConfig, bestKnownHeightProvider)
handler, err := rpcserver.NewServiceHandler(
n.Store,
n.hSyncService.Store(),
n.dSyncService.Store(),
n.p2pClient,
n.genesis.ProposerAddress,
n.Logger,
n.nodeConfig,
bestKnownHeightProvider,
)
if err != nil {
return fmt.Errorf("error creating RPC handler: %w", err)
}
Expand Down
Loading
Loading