Skip to content
Merged
Show file tree
Hide file tree
Changes from 4 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/core@v0.3.0

- 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
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
2 changes: 2 additions & 0 deletions docs/learn/config.md
Original file line number Diff line number Diff line change
Expand Up @@ -658,6 +658,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 +670,7 @@ curl http://localhost:7331/health/ready
```

Configure max blocks behind:

```yaml
node:
readiness_max_blocks_behind: 15
Expand Down
28 changes: 15 additions & 13 deletions pkg/sync/sync_service.go
Original file line number Diff line number Diff line change
Expand Up @@ -135,11 +135,9 @@ func (syncService *SyncService[H]) WriteToStoreAndBroadcast(ctx context.Context,
return fmt.Errorf("empty header/data cannot write to store or broadcast")
}

isGenesis := headerOrData.Height() == syncService.genesis.InitialHeight
if isGenesis { // when starting the syncer for the first time, we have no blocks, so initFromP2P didn't initialize the genesis block.
if err := syncService.initStore(ctx, headerOrData); err != nil {
return fmt.Errorf("failed to initialize the store: %w", err)
}
storeInitialized, err := syncService.initStore(ctx, headerOrData)
if err != nil {
return fmt.Errorf("failed to initialize the store: %w", err)
}

firstStart := false
Expand All @@ -156,10 +154,10 @@ func (syncService *SyncService[H]) WriteToStoreAndBroadcast(ctx context.Context,
// as we have already initialized the store for starting the syncer.
// Hence, we ignore the error. Exact reason: validation ignored
if (firstStart && errors.Is(err, pubsub.ValidationError{Reason: pubsub.RejectValidationIgnored})) ||
// for the genesis header, broadcast error is expected as we have already initialized the store
// for the genesis header (or any first header used to bootstrap the store), broadcast error is expected as we have already initialized the store
// for starting the syncer. Hence, we ignore the error.
// exact reason: validation failed, err header verification failed: known header: '1' <= current '1'
(isGenesis && errors.Is(err, pubsub.ValidationError{Reason: pubsub.RejectValidationFailed})) {
((storeInitialized) && errors.Is(err, pubsub.ValidationError{Reason: pubsub.RejectValidationFailed})) {

return nil
}
Expand Down Expand Up @@ -221,23 +219,27 @@ func (syncService *SyncService[H]) startSyncer(ctx context.Context) error {

// initStore initializes the store with the given initial header.
// it is a no-op if the store is already initialized.
func (syncService *SyncService[H]) initStore(ctx context.Context, initial H) error {
// Returns true when the store was initialized by this call.
func (syncService *SyncService[H]) initStore(ctx context.Context, initial H) (bool, error) {
if initial.IsZero() {
return errors.New("failed to initialize the store")
return false, errors.New("failed to initialize the store")
}

if _, err := syncService.store.Head(ctx); errors.Is(err, header.ErrNotFound) || errors.Is(err, header.ErrEmptyStore) {
if err := syncService.store.Append(ctx, initial); err != nil {
return err
return false, err
}

if err := syncService.store.Sync(ctx); err != nil {
return err
return false, err
}

return true, nil
} else if err != nil {
return false, err
}

return nil
return false, nil
}

// setupP2PInfrastructure sets up the P2P infrastructure (Exchange, ExchangeServer, Store)
Expand Down Expand Up @@ -328,7 +330,7 @@ func (syncService *SyncService[H]) initFromP2PWithRetry(ctx context.Context, pee
}
}

if err := syncService.initStore(ctx, trusted); err != nil {
if _, err := syncService.initStore(ctx, trusted); err != nil {
return false, fmt.Errorf("failed to initialize the store: %w", err)
}
if err := syncService.startSyncer(ctx); err != nil {
Expand Down
53 changes: 53 additions & 0 deletions pkg/sync/sync_service_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,59 @@ func TestHeaderSyncServiceRestart(t *testing.T) {
cancel()
}

func TestHeaderSyncServiceInitFromHigherHeight(t *testing.T) {
mainKV := sync.MutexWrap(datastore.NewMapDatastore())
pk, _, err := crypto.GenerateEd25519Key(cryptoRand.Reader)
require.NoError(t, err)
noopSigner, err := noop.NewNoopSigner(pk)
require.NoError(t, err)
rnd := rand.New(rand.NewSource(1)) // nolint:gosec // test code only
mn := mocknet.New()

chainId := "test-chain-id"

proposerAddr := []byte("test")
genesisDoc := genesispkg.Genesis{
ChainID: chainId,
StartTime: time.Now(),
InitialHeight: 1,
ProposerAddress: proposerAddr,
}
conf := config.DefaultConfig()
conf.RootDir = t.TempDir()
nodeKey, err := key.LoadOrGenNodeKey(filepath.Dir(conf.ConfigPath()))
require.NoError(t, err)
logger := zerolog.Nop()
priv := nodeKey.PrivKey
h, err := mn.AddPeer(priv, nil)
require.NoError(t, err)

p2pClient, err := p2p.NewClientWithHost(conf.P2P, nodeKey.PrivKey, mainKV, chainId, logger, p2p.NopMetrics(), h)
require.NoError(t, err)

ctx, cancel := context.WithCancel(t.Context())
defer cancel()
require.NoError(t, p2pClient.Start(ctx))
t.Cleanup(func() { _ = p2pClient.Close() })

svc, err := NewHeaderSyncService(mainKV, conf, genesisDoc, p2pClient, logger)
require.NoError(t, err)
require.NoError(t, svc.Start(ctx))
t.Cleanup(func() { _ = svc.Stop(context.Background()) })

headerConfig := types.HeaderConfig{
Height: genesisDoc.InitialHeight + 5,
DataHash: bytesN(rnd, 32),
AppHash: bytesN(rnd, 32),
Signer: noopSigner,
}
signedHeader, err := types.GetRandomSignedHeaderCustom(&headerConfig, genesisDoc.ChainID)
require.NoError(t, err)
require.NoError(t, signedHeader.Validate())

require.NoError(t, svc.WriteToStoreAndBroadcast(ctx, signedHeader))
}

func nextHeader(t *testing.T, previousHeader *types.SignedHeader, chainID string, noopSigner signer.Signer) *types.SignedHeader {
newSignedHeader := &types.SignedHeader{
Header: types.GetRandomNextHeader(previousHeader.Header, chainID),
Expand Down
Loading