|
1 | | -package executing |
| 1 | +package reaping |
2 | 2 |
|
3 | 3 | import ( |
4 | 4 | "context" |
5 | 5 | "crypto/sha256" |
6 | 6 | "encoding/hex" |
| 7 | + "errors" |
| 8 | + "fmt" |
| 9 | + "sync" |
7 | 10 | "time" |
8 | 11 |
|
9 | 12 | ds "github.com/ipfs/go-datastore" |
10 | 13 | "github.com/rs/zerolog" |
11 | 14 |
|
| 15 | + "github.com/evstack/ev-node/block/internal/executing" |
12 | 16 | coreexecutor "github.com/evstack/ev-node/core/execution" |
13 | 17 | coresequencer "github.com/evstack/ev-node/core/sequencer" |
| 18 | + "github.com/evstack/ev-node/pkg/genesis" |
| 19 | + "github.com/evstack/ev-node/pkg/store" |
14 | 20 | ) |
15 | 21 |
|
| 22 | +// DefaultInterval is the default reaper interval |
| 23 | +const DefaultInterval = 1 * time.Second |
| 24 | + |
16 | 25 | // Reaper is responsible for periodically retrieving transactions from the executor, |
17 | 26 | // filtering out already seen transactions, and submitting new transactions to the sequencer. |
18 | 27 | type Reaper struct { |
19 | 28 | exec coreexecutor.Executor |
20 | 29 | sequencer coresequencer.Sequencer |
21 | 30 | chainID string |
22 | 31 | interval time.Duration |
23 | | - logger zerolog.Logger |
24 | | - ctx context.Context |
25 | 32 | seenStore ds.Batching |
26 | | - executor *Executor |
| 33 | + executor *executing.Executor |
| 34 | + |
| 35 | + // shared components |
| 36 | + logger zerolog.Logger |
| 37 | + |
| 38 | + // Lifecycle |
| 39 | + ctx context.Context |
| 40 | + cancel context.CancelFunc |
| 41 | + wg sync.WaitGroup |
27 | 42 | } |
28 | 43 |
|
29 | 44 | // NewReaper creates a new Reaper instance with persistent seenTx storage. |
30 | | -func NewReaper(ctx context.Context, exec coreexecutor.Executor, sequencer coresequencer.Sequencer, chainID string, interval time.Duration, logger zerolog.Logger, store ds.Batching, executor *Executor) *Reaper { |
31 | | - if interval <= 0 { |
32 | | - interval = DefaultInterval |
| 45 | +func NewReaper(exec coreexecutor.Executor, sequencer coresequencer.Sequencer, genesis genesis.Genesis, logger zerolog.Logger, executor *executing.Executor) (*Reaper, error) { |
| 46 | + if executor == nil { |
| 47 | + return nil, errors.New("executor cannot be nil") |
| 48 | + } |
| 49 | + |
| 50 | + store, err := store.NewDefaultInMemoryKVStore() |
| 51 | + if err != nil { |
| 52 | + return nil, fmt.Errorf("failed to create reaper store: %w", err) |
33 | 53 | } |
| 54 | + |
34 | 55 | return &Reaper{ |
35 | 56 | exec: exec, |
36 | 57 | sequencer: sequencer, |
37 | | - chainID: chainID, |
38 | | - interval: interval, |
| 58 | + chainID: genesis.ChainID, |
| 59 | + interval: DefaultInterval, // eventually this can be made configurable via config.Node |
39 | 60 | logger: logger.With().Str("component", "reaper").Logger(), |
40 | | - ctx: ctx, |
41 | 61 | seenStore: store, |
42 | 62 | executor: executor, |
43 | | - } |
| 63 | + }, nil |
44 | 64 | } |
45 | 65 |
|
46 | | -// Start begins the reaping process at the specified interval. |
47 | | -func (r *Reaper) Start(ctx context.Context) { |
48 | | - r.ctx = ctx |
49 | | - ticker := time.NewTicker(r.interval) |
50 | | - defer ticker.Stop() |
| 66 | +// Start begins the execution component |
| 67 | +func (r *Reaper) Start(ctx context.Context) error { |
| 68 | + r.ctx, r.cancel = context.WithCancel(ctx) |
| 69 | + |
| 70 | + // Start repear loop |
| 71 | + r.wg.Add(1) |
| 72 | + go func() { |
| 73 | + defer r.wg.Done() |
| 74 | + r.reaperLoop() |
| 75 | + }() |
51 | 76 |
|
52 | 77 | r.logger.Info().Dur("interval", r.interval).Msg("reaper started") |
| 78 | + return nil |
| 79 | +} |
| 80 | + |
| 81 | +func (r *Reaper) reaperLoop() { |
| 82 | + ticker := time.NewTicker(r.interval) |
| 83 | + defer ticker.Stop() |
53 | 84 |
|
54 | 85 | for { |
55 | 86 | select { |
56 | | - case <-ctx.Done(): |
57 | | - r.logger.Info().Msg("reaper stopped") |
| 87 | + case <-r.ctx.Done(): |
58 | 88 | return |
59 | 89 | case <-ticker.C: |
60 | 90 | r.SubmitTxs() |
61 | 91 | } |
62 | 92 | } |
63 | 93 | } |
64 | 94 |
|
| 95 | +// Stop shuts down the reaper component |
| 96 | +func (r *Reaper) Stop() error { |
| 97 | + if r.cancel != nil { |
| 98 | + r.cancel() |
| 99 | + } |
| 100 | + r.wg.Wait() |
| 101 | + |
| 102 | + r.logger.Info().Msg("reaper stopped") |
| 103 | + return nil |
| 104 | +} |
| 105 | + |
65 | 106 | // SubmitTxs retrieves transactions from the executor and submits them to the sequencer. |
66 | 107 | func (r *Reaper) SubmitTxs() { |
67 | 108 | txs, err := r.exec.GetTxs(r.ctx) |
@@ -109,14 +150,19 @@ func (r *Reaper) SubmitTxs() { |
109 | 150 | } |
110 | 151 |
|
111 | 152 | // Notify the executor that new transactions are available |
112 | | - if r.executor != nil && len(newTxs) > 0 { |
| 153 | + if len(newTxs) > 0 { |
113 | 154 | r.logger.Debug().Msg("notifying executor of new transactions") |
114 | 155 | r.executor.NotifyNewTransactions() |
115 | 156 | } |
116 | 157 |
|
117 | 158 | r.logger.Debug().Msg("successfully submitted txs") |
118 | 159 | } |
119 | 160 |
|
| 161 | +// SeenStore returns the datastore used to track seen transactions. |
| 162 | +func (r *Reaper) SeenStore() ds.Datastore { |
| 163 | + return r.seenStore |
| 164 | +} |
| 165 | + |
120 | 166 | func hashTx(tx []byte) string { |
121 | 167 | hash := sha256.Sum256(tx) |
122 | 168 | return hex.EncodeToString(hash[:]) |
|
0 commit comments