Skip to content
Merged
Show file tree
Hide file tree
Changes from 10 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
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
1 change: 1 addition & 0 deletions apps/evm/single/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ func main() {
rollcmd.VersionCmd,
rollcmd.NetInfoCmd,
rollcmd.StoreUnsafeCleanCmd,
rollcmd.StoreP2PInspectCmd,
rollcmd.KeysCmd(),
)

Expand Down
1 change: 1 addition & 0 deletions apps/grpc/single/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ the Evolve execution gRPC interface.`,
evcmd.VersionCmd,
evcmd.NetInfoCmd,
evcmd.StoreUnsafeCleanCmd,
evcmd.StoreP2PInspectCmd,
evcmd.KeysCmd(),
)

Expand Down
1 change: 1 addition & 0 deletions apps/testapp/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ func main() {
rollcmd.VersionCmd,
rollcmd.NetInfoCmd,
rollcmd.StoreUnsafeCleanCmd,
rollcmd.StoreP2PInspectCmd,
rollcmd.KeysCmd(),
cmds.NewRollbackCmd(),
initCmd,
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 [here](./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
74 changes: 2 additions & 72 deletions node/full_node_integration_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -260,10 +260,9 @@ func TestSingleSequencerTwoFullNodesBlockSyncSpeed(t *testing.T) {

// TestDataExchange verifies data exchange and synchronization between nodes in various network topologies.
//
// This test runs three sub-tests:
// This test runs two sub-tests:
// 1. Single sequencer and single full
// 2. Single sequencer and two full nodes.
// 3. Single sequencer and single full node with trusted hash.
//
// Each sub-test checks data exchange and synchronization to ensure correct data propagation and consistency across nodes.
func TestDataExchange(t *testing.T) {
Expand All @@ -273,17 +272,13 @@ func TestDataExchange(t *testing.T) {
t.Run("SingleSequencerTwoFullNodes", func(t *testing.T) {
testSingleSequencerTwoFullNodes(t, Data)
})
t.Run("SingleSequencerSingleFullNodeTrustedHash", func(t *testing.T) {
testSingleSequencerSingleFullNodeTrustedHash(t, Data)
})
}

// TestHeaderExchange verifies header exchange and synchronization between nodes in various network topologies.
//
// This test runs three sub-tests:
// This test runs two sub-tests:
// 1. Single sequencer and single full
// 2. Single sequencer and two full nodes.
// 3. Single sequencer and single full node with trusted hash.
//
// Each sub-test checks header exchange and synchronization to ensure correct header propagation and consistency across nodes.
func TestHeaderExchange(t *testing.T) {
Expand All @@ -293,9 +288,6 @@ func TestHeaderExchange(t *testing.T) {
t.Run("SingleSequencerTwoFullNodes", func(t *testing.T) {
testSingleSequencerTwoFullNodes(t, Header)
})
t.Run("SingleSequencerSingleFullNodeTrustedHash", func(t *testing.T) {
testSingleSequencerSingleFullNodeTrustedHash(t, Header)
})
}

// testSingleSequencerSingleFullNode sets up a single sequencer and a single full node, starts the sequencer, waits for it to produce a block, then starts the full
Expand Down Expand Up @@ -392,68 +384,6 @@ func testSingleSequencerTwoFullNodes(t *testing.T, source Source) {
shutdownAndWait(t, cancels, &runningWg, 5*time.Second)
}

// testSingleSequencerSingleFullNodeTrustedHash sets up a single sequencer and a single full node with a trusted hash, starts the sequencer, waits for it to produce a block, then starts the full node with the trusted hash.
// It waits for both nodes to reach a target block height (using the provided 'source' to determine block inclusion), verifies that both nodes are fully synced, and then shuts them down.
func testSingleSequencerSingleFullNodeTrustedHash(t *testing.T, source Source) {
require := require.New(t)

// Set up one sequencer and one full node
config := getTestConfig(t, 1)
numNodes := 2
nodes, cleanups := createNodesWithCleanup(t, numNodes, config)
for _, cleanup := range cleanups {
defer cleanup()
}

ctxs, cancels := createNodeContexts(numNodes)
var runningWg sync.WaitGroup
errChan := make(chan error, numNodes)

// Start the sequencer first
startNodeInBackground(t, nodes, ctxs, &runningWg, 0, errChan)

// Wait for the sequencer to produce at first block
require.NoError(waitForFirstBlock(nodes[0], source))

// Get the hash of the first block (using the correct source)
var trustedHash string
switch source {
case Data:
trustedHashValue, err := nodes[0].dSyncService.Store().GetByHeight(ctxs[0], 1)
require.NoError(err)
trustedHash = trustedHashValue.Hash().String()
case Header:
trustedHashValue, err := nodes[0].hSyncService.Store().GetByHeight(ctxs[0], 1)
require.NoError(err)
trustedHash = trustedHashValue.Hash().String()
default:
t.Fatalf("unsupported source for trusted hash test: %v", source)
}

// Set the trusted hash in the full node
nodeConfig := nodes[1].nodeConfig
nodeConfig.Node.TrustedHash = trustedHash

// Add a small delay to ensure P2P services are fully ready
time.Sleep(500 * time.Millisecond)

// Start the full node
startNodeInBackground(t, nodes, ctxs, &runningWg, 1, errChan)

blocksToWaitFor := uint64(3)
// Wait for both nodes to reach at least blocksToWaitFor blocks
for _, nodeItem := range nodes {
requireEmptyChan(t, errChan)
require.NoError(waitForAtLeastNBlocks(nodeItem, blocksToWaitFor, source))
}

// Verify both nodes are synced using the helper
require.NoError(verifyNodesSynced(nodes[0], nodes[1], source))

// Cancel all node contexts to signal shutdown and wait
shutdownAndWait(t, cancels, &runningWg, 5*time.Second)
}

// TestTwoChainsInOneNamespace verifies that two chains in the same namespace can coexist without any issues.
func TestTwoChainsInOneNamespace(t *testing.T) {
cases := []struct {
Expand Down
11 changes: 10 additions & 1 deletion node/light.go
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,16 @@ func (ln *LightNode) Run(parentCtx context.Context) error {
return ln.hSyncService.Store().Height()
}

handler, err := rpcserver.NewServiceHandler(ln.Store, ln.P2P, nil, ln.Logger, ln.nodeConfig, bestKnown)
handler, err := rpcserver.NewServiceHandler(
ln.Store,
ln.hSyncService.Store(),
nil,
ln.P2P,
nil,
ln.Logger,
ln.nodeConfig,
bestKnown,
)
if err != nil {
return fmt.Errorf("error creating RPC handler: %w", err)
}
Expand Down
Loading
Loading