-
Notifications
You must be signed in to change notification settings - Fork 260
Expand file tree
/
Copy pathstore.go
More file actions
199 lines (172 loc) · 6.14 KB
/
store.go
File metadata and controls
199 lines (172 loc) · 6.14 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
package evm
import (
"context"
"encoding/binary"
"errors"
"fmt"
ds "github.com/ipfs/go-datastore"
"google.golang.org/protobuf/proto"
pb "github.com/evstack/ev-node/execution/evm/types/pb/execution/evm/v1"
)
// Store prefix for execution/evm data - keeps it isolated from other ev-node data
const evmStorePrefix = "evm/"
// lastPrunedExecMetaKey is the datastore key used to track the highest
// execution height for which ExecMeta has been pruned. All ExecMeta entries
// for heights <= this value are considered pruned.
const lastPrunedExecMetaKey = evmStorePrefix + "last-pruned-execmeta-height"
// ExecMeta stages
const (
ExecStageStarted = "started"
ExecStageBuilt = "built"
ExecStageSubmitted = "submitted"
ExecStagePromoted = "promoted"
)
// ExecMeta tracks execution state per height for idempotent execution.
// This enables crash recovery and prevents sibling block creation on retries.
type ExecMeta struct {
// Height is the block height this execution metadata is for
Height uint64
// ParentHash is the EL parent block hash used for this execution
ParentHash []byte
// PayloadID is the Engine API payload ID (optional, set after forkchoiceUpdatedV3)
PayloadID []byte
// BlockHash is the EL block hash once built (set after getPayloadV4)
BlockHash []byte
// StateRoot is the state root from the execution payload
StateRoot []byte
// TxHash is the hash of the transaction list for sanity checks
TxHash []byte
// Timestamp is the block timestamp
Timestamp int64
// Stage indicates the current execution stage:
// "started" - forkchoiceUpdatedV3 called, payloadID obtained
// "built" - getPayloadV4 called, payload retrieved
// "submitted" - newPayloadV4 called, payload marked VALID
// "promoted" - final forkchoiceUpdatedV3 called, block is head
Stage string
// UpdatedAtUnix is the Unix timestamp when this metadata was last updated
UpdatedAtUnix int64
}
// ToProto converts ExecMeta into protobuf representation.
func (em *ExecMeta) ToProto() *pb.ExecMeta {
return &pb.ExecMeta{
Height: em.Height,
ParentHash: em.ParentHash,
PayloadId: em.PayloadID,
BlockHash: em.BlockHash,
StateRoot: em.StateRoot,
TxHash: em.TxHash,
Timestamp: em.Timestamp,
Stage: em.Stage,
UpdatedAtUnix: em.UpdatedAtUnix,
}
}
// FromProto fills ExecMeta with data from its protobuf representation.
func (em *ExecMeta) FromProto(other *pb.ExecMeta) error {
if other == nil {
return nil
}
em.Height = other.Height
em.ParentHash = append([]byte(nil), other.ParentHash...)
em.PayloadID = append([]byte(nil), other.PayloadId...)
em.BlockHash = append([]byte(nil), other.BlockHash...)
em.StateRoot = append([]byte(nil), other.StateRoot...)
em.TxHash = append([]byte(nil), other.TxHash...)
em.Timestamp = other.Timestamp
em.Stage = other.Stage
em.UpdatedAtUnix = other.UpdatedAtUnix
return nil
}
// EVMStore wraps a ds.Batching datastore with a prefix for EVM execution data.
// This keeps EVM-specific data isolated from other ev-node data.
type EVMStore struct {
db ds.Batching
}
// NewEVMStore creates a new EVMStore wrapping the given datastore.
func NewEVMStore(db ds.Batching) *EVMStore {
return &EVMStore{db: db}
}
// execMetaKey returns the datastore key for ExecMeta at a given height.
func execMetaKey(height uint64) ds.Key {
heightBytes := make([]byte, 8)
binary.BigEndian.PutUint64(heightBytes, height)
return ds.NewKey(evmStorePrefix + "execmeta/" + string(heightBytes))
}
// GetExecMeta retrieves execution metadata for the given height.
// Returns nil, nil if not found.
func (s *EVMStore) GetExecMeta(ctx context.Context, height uint64) (*ExecMeta, error) {
key := execMetaKey(height)
data, err := s.db.Get(ctx, key)
if err != nil {
if errors.Is(err, ds.ErrNotFound) {
return nil, nil
}
return nil, fmt.Errorf("failed to get exec meta: %w", err)
}
var pbMeta pb.ExecMeta
if err := proto.Unmarshal(data, &pbMeta); err != nil {
return nil, fmt.Errorf("failed to unmarshal exec meta: %w", err)
}
meta := &ExecMeta{}
if err := meta.FromProto(&pbMeta); err != nil {
return nil, fmt.Errorf("failed to convert exec meta from proto: %w", err)
}
return meta, nil
}
// SaveExecMeta persists execution metadata for the given height.
func (s *EVMStore) SaveExecMeta(ctx context.Context, meta *ExecMeta) error {
key := execMetaKey(meta.Height)
data, err := proto.Marshal(meta.ToProto())
if err != nil {
return fmt.Errorf("failed to marshal exec meta: %w", err)
}
if err := s.db.Put(ctx, key, data); err != nil {
return fmt.Errorf("failed to save exec meta: %w", err)
}
return nil
}
// PruneExecMeta removes ExecMeta entries up to and including the given height.
// It is safe to call this multiple times with the same or increasing heights;
// previously pruned ranges will be skipped based on the last-pruned marker.
func (s *EVMStore) PruneExecMeta(ctx context.Context, height uint64) error {
// Load last pruned height, if any.
var lastPruned uint64
data, err := s.db.Get(ctx, ds.NewKey(lastPrunedExecMetaKey))
if err != nil {
if !errors.Is(err, ds.ErrNotFound) {
return fmt.Errorf("failed to get last pruned execmeta height: %w", err)
}
} else if len(data) == 8 {
lastPruned = binary.BigEndian.Uint64(data)
}
// Nothing new to prune.
if height <= lastPruned {
return nil
}
batch, err := s.db.Batch(ctx)
if err != nil {
return fmt.Errorf("failed to create batch for execmeta pruning: %w", err)
}
for h := lastPruned + 1; h <= height; h++ {
key := execMetaKey(h)
if err := batch.Delete(ctx, key); err != nil {
if !errors.Is(err, ds.ErrNotFound) {
return fmt.Errorf("failed to delete exec meta at height %d: %w", h, err)
}
}
}
// Persist updated last pruned height.
buf := make([]byte, 8)
binary.BigEndian.PutUint64(buf, height)
if err := batch.Put(ctx, ds.NewKey(lastPrunedExecMetaKey), buf); err != nil {
return fmt.Errorf("failed to update last pruned execmeta height: %w", err)
}
if err := batch.Commit(ctx); err != nil {
return fmt.Errorf("failed to commit execmeta pruning batch: %w", err)
}
return nil
}
// Sync ensures all pending writes are flushed to disk.
func (s *EVMStore) Sync(ctx context.Context) error {
return s.db.Sync(ctx, ds.NewKey(evmStorePrefix))
}