Skip to content

Commit 48b0795

Browse files
committed
x1
1 parent b646e66 commit 48b0795

File tree

8 files changed

+983
-1
lines changed

8 files changed

+983
-1
lines changed

block/components.go

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -188,6 +188,13 @@ func NewSyncComponents(
188188
}, nil
189189
}
190190

191+
// RaftNode interface for leader election (nil for non-raft mode)
192+
type RaftNode interface {
193+
IsLeader() bool
194+
Propose(ctx context.Context, data []byte) error
195+
GetStateMachine() interface{}
196+
}
197+
191198
// NewAggregatorComponents creates components for an aggregator full node that can produce and sync blocks.
192199
// Aggregator nodes have full capabilities - they can produce blocks, sync from P2P and DA,
193200
// and submit headers/data to DA. Requires a signer for block production and DA submission.
@@ -204,6 +211,7 @@ func NewAggregatorComponents(
204211
logger zerolog.Logger,
205212
metrics *Metrics,
206213
blockOpts BlockOptions,
214+
raftNode RaftNode,
207215
) (*Components, error) {
208216
cacheManager, err := cache.NewManager(config, store, logger)
209217
if err != nil {
@@ -227,6 +235,7 @@ func NewAggregatorComponents(
227235
logger,
228236
blockOpts,
229237
errorCh,
238+
raftNode,
230239
)
231240
if err != nil {
232241
return nil, fmt.Errorf("failed to create executor: %w", err)

block/internal/executing/executor.go

Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import (
1818
coresequencer "github.com/evstack/ev-node/core/sequencer"
1919
"github.com/evstack/ev-node/pkg/config"
2020
"github.com/evstack/ev-node/pkg/genesis"
21+
"github.com/evstack/ev-node/pkg/raft"
2122
"github.com/evstack/ev-node/pkg/signer"
2223
"github.com/evstack/ev-node/pkg/store"
2324
"github.com/evstack/ev-node/types"
@@ -28,6 +29,13 @@ type broadcaster[T any] interface {
2829
WriteToStoreAndBroadcast(ctx context.Context, payload T) error
2930
}
3031

32+
// RaftNode interface for leader election and state replication
33+
type RaftNode interface {
34+
IsLeader() bool
35+
Propose(ctx context.Context, data []byte) error
36+
GetStateMachine() interface{}
37+
}
38+
3139
// Executor handles block production, transaction processing, and state management
3240
type Executor struct {
3341
// Core components
@@ -64,6 +72,9 @@ type Executor struct {
6472
ctx context.Context
6573
cancel context.CancelFunc
6674
wg sync.WaitGroup
75+
76+
// Raft for leader election and state replication
77+
raftNode RaftNode
6778
}
6879

6980
// NewExecutor creates a new block executor.
@@ -86,6 +97,7 @@ func NewExecutor(
8697
logger zerolog.Logger,
8798
options common.BlockOptions,
8899
errorCh chan<- error,
100+
raftNode RaftNode,
89101
) (*Executor, error) {
90102
if signer == nil {
91103
return nil, errors.New("signer cannot be nil")
@@ -116,6 +128,7 @@ func NewExecutor(
116128
txNotifyCh: make(chan struct{}, 1),
117129
errorCh: errorCh,
118130
logger: logger.With().Str("component", "executor").Logger(),
131+
raftNode: raftNode,
119132
}, nil
120133
}
121134

@@ -291,6 +304,34 @@ func (e *Executor) produceBlock() error {
291304
}
292305
}()
293306

307+
// Check leadership if raft is enabled
308+
if e.raftNode != nil {
309+
if !e.raftNode.IsLeader() {
310+
e.logger.Debug().Msg("not leader, skipping block production")
311+
return nil
312+
}
313+
314+
// New leader catch-up: apply latest block from raft if behind
315+
if sm := e.raftNode.GetStateMachine(); sm != nil {
316+
if blockSM, ok := sm.(*raft.BlockStateMachine); ok {
317+
raftHeader, raftData := blockSM.GetLastBlock()
318+
if raftHeader != nil && raftData != nil {
319+
currentState := e.GetLastState()
320+
if raftHeader.Height() > currentState.LastBlockHeight {
321+
e.logger.Info().
322+
Uint64("current_height", currentState.LastBlockHeight).
323+
Uint64("raft_height", raftHeader.Height()).
324+
Msg("new leader catching up from raft")
325+
326+
if err := e.applyRaftBlock(raftHeader, raftData); err != nil {
327+
return fmt.Errorf("catch up from raft: %w", err)
328+
}
329+
}
330+
}
331+
}
332+
}
333+
}
334+
294335
currentState := e.GetLastState()
295336
newHeight := currentState.LastBlockHeight + 1
296337

@@ -378,6 +419,21 @@ func (e *Executor) produceBlock() error {
378419
return fmt.Errorf("failed to update state: %w", err)
379420
}
380421

422+
// Replicate full block via raft before broadcasting to P2P
423+
// This ensures follower nodes have the full block data and can apply it
424+
if e.raftNode != nil {
425+
blockStateData, err := raft.CreateBlockStateData(header, data)
426+
if err != nil {
427+
return fmt.Errorf("failed to create block state data: %w", err)
428+
}
429+
430+
if err := e.raftNode.Propose(e.ctx, blockStateData); err != nil {
431+
return fmt.Errorf("failed to propose block to raft: %w", err)
432+
}
433+
434+
e.logger.Debug().Uint64("height", newHeight).Msg("full block replicated via raft")
435+
}
436+
381437
// broadcast header and data to P2P network
382438
g, ctx := errgroup.WithContext(e.ctx)
383439
g.Go(func() error { return e.headerBroadcaster.WriteToStoreAndBroadcast(ctx, header) })
@@ -629,6 +685,56 @@ func (e *Executor) recordBlockMetrics(data *types.Data) {
629685
e.metrics.CommittedHeight.Set(float64(data.Metadata.Height))
630686
}
631687

688+
// applyRaftBlock applies a block received via raft (for follower nodes)
689+
func (e *Executor) applyRaftBlock(header *types.SignedHeader, data *types.Data) error {
690+
e.logger.Info().Uint64("height", header.Height()).Msg("applying block from raft")
691+
692+
currentState := e.GetLastState()
693+
694+
// Skip if already processed
695+
if header.Height() <= currentState.LastBlockHeight {
696+
e.logger.Debug().Uint64("height", header.Height()).Msg("block already applied, skipping")
697+
return nil
698+
}
699+
700+
// Validate block
701+
if err := e.validateBlock(currentState, header, data); err != nil {
702+
return fmt.Errorf("validate block: %w", err)
703+
}
704+
705+
// Apply block to execution client
706+
newState, err := e.applyBlock(e.ctx, header.Header, data)
707+
if err != nil {
708+
return fmt.Errorf("apply block: %w", err)
709+
}
710+
711+
// Save block data
712+
if err := e.store.SaveBlockData(e.ctx, header, data, &header.Signature); err != nil {
713+
return fmt.Errorf("save block data: %w", err)
714+
}
715+
716+
// Update store height
717+
if err := e.store.SetHeight(e.ctx, header.Height()); err != nil {
718+
return fmt.Errorf("set height: %w", err)
719+
}
720+
721+
// Update state
722+
if err := e.updateState(e.ctx, newState); err != nil {
723+
return fmt.Errorf("update state: %w", err)
724+
}
725+
726+
e.recordBlockMetrics(data)
727+
728+
e.logger.Info().Uint64("height", header.Height()).Int("txs", len(data.Txs)).Msg("applied block from raft")
729+
730+
return nil
731+
}
732+
733+
// GetRaftStateMachine returns a reference to the raft block state machine if raft is enabled
734+
func (e *Executor) GetRaftStateMachine() interface{} {
735+
return e.raftNode
736+
}
737+
632738
// BatchData represents batch data from sequencer
633739
type BatchData struct {
634740
*coresequencer.Batch

go.mod

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,14 +29,16 @@ require (
2929
golang.org/x/crypto v0.42.0
3030
golang.org/x/net v0.44.0
3131
golang.org/x/sync v0.17.0
32-
google.golang.org/protobuf v1.36.9
32+
google.golang.org/protobuf v1.36.10
3333
)
3434

3535
require (
3636
github.com/benbjohnson/clock v1.3.5 // indirect
3737
github.com/beorn7/perks v1.0.1 // indirect
3838
github.com/celestiaorg/go-libp2p-messenger v0.2.2 // indirect
3939
github.com/cespare/xxhash/v2 v2.3.0 // indirect
40+
github.com/coreos/go-semver v0.3.1 // indirect
41+
github.com/coreos/go-systemd/v22 v22.5.0 // indirect
4042
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
4143
github.com/davidlazar/go-crypto v0.0.0-20200604182044-b73af7476f6c // indirect
4244
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.4.0 // indirect
@@ -51,12 +53,17 @@ require (
5153
github.com/go-logr/stdr v1.2.2 // indirect
5254
github.com/go-viper/mapstructure/v2 v2.4.0 // indirect
5355
github.com/gogo/protobuf v1.3.2 // indirect
56+
github.com/golang-jwt/jwt/v5 v5.2.2 // indirect
5457
github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 // indirect
58+
github.com/golang/protobuf v1.5.4 // indirect
59+
github.com/google/btree v1.1.3 // indirect
5560
github.com/google/flatbuffers v24.12.23+incompatible // indirect
61+
github.com/google/go-cmp v0.7.0 // indirect
5662
github.com/google/gopacket v1.1.19 // indirect
5763
github.com/google/uuid v1.6.0 // indirect
5864
github.com/gopherjs/gopherjs v0.0.0-20190812055157-5d271430af9f // indirect
5965
github.com/gorilla/websocket v1.5.3 // indirect
66+
github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.1 // indirect
6067
github.com/hashicorp/golang-lru v1.0.2 // indirect
6168
github.com/hashicorp/golang-lru/v2 v2.0.7 // indirect
6269
github.com/huin/goupnp v1.3.0 // indirect
@@ -67,6 +74,7 @@ require (
6774
github.com/ipld/go-ipld-prime v0.21.0 // indirect
6875
github.com/jackpal/go-nat-pmp v1.0.2 // indirect
6976
github.com/jbenet/go-temp-err-catcher v0.1.0 // indirect
77+
github.com/jonboulle/clockwork v0.5.0 // indirect
7078
github.com/klauspost/compress v1.18.0 // indirect
7179
github.com/klauspost/cpuid/v2 v2.3.0 // indirect
7280
github.com/koron/go-ssdp v0.0.6 // indirect
@@ -138,8 +146,18 @@ require (
138146
github.com/subosito/gotenv v1.6.0 // indirect
139147
github.com/whyrusleeping/go-keyspace v0.0.0-20160322163242-5b898ac5add1 // indirect
140148
github.com/wlynxg/anet v0.0.5 // indirect
149+
github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2 // indirect
150+
go.etcd.io/bbolt v1.4.3 // indirect
151+
go.etcd.io/etcd/api/v3 v3.6.5 // indirect
152+
go.etcd.io/etcd/client/pkg/v3 v3.6.5 // indirect
153+
go.etcd.io/etcd/client/v3 v3.6.5 // indirect
154+
go.etcd.io/etcd/pkg/v3 v3.6.5 // indirect
155+
go.etcd.io/etcd/raft/v3 v3.5.23 // indirect
156+
go.etcd.io/etcd/server/v3 v3.6.5 // indirect
157+
go.etcd.io/raft/v3 v3.6.0 // indirect
141158
go.opencensus.io v0.24.0 // indirect
142159
go.opentelemetry.io/auto/sdk v1.1.0 // indirect
160+
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.59.0 // indirect
143161
go.opentelemetry.io/otel v1.38.0 // indirect
144162
go.opentelemetry.io/otel/metric v1.38.0 // indirect
145163
go.opentelemetry.io/otel/trace v1.38.0 // indirect
@@ -157,6 +175,10 @@ require (
157175
golang.org/x/time v0.12.0 // indirect
158176
golang.org/x/tools v0.36.0 // indirect
159177
gonum.org/v1/gonum v0.16.0 // indirect
178+
google.golang.org/genproto/googleapis/api v0.0.0-20251006185510-65f7160b3a87 // indirect
179+
google.golang.org/genproto/googleapis/rpc v0.0.0-20251006185510-65f7160b3a87 // indirect
180+
google.golang.org/grpc v1.73.0 // indirect
160181
gopkg.in/yaml.v3 v3.0.1 // indirect
161182
lukechampine.com/blake3 v1.4.1 // indirect
183+
sigs.k8s.io/json v0.0.0-20211020170558-c049b76a60c6 // indirect
162184
)

0 commit comments

Comments
 (0)