Skip to content
This repository was archived by the owner on Feb 17, 2025. It is now read-only.

Commit adf526c

Browse files
authored
Fix tx OOC (node level) when first empty L2 block in batch (#3744)
* Fix tx OOC (node level) for first empty L2 block in batch * change log level for ooc (node level) when adding tx to the worker * fix check OOC (node level) when preexecuting the tx in RPC * Fix linter and test
1 parent d20b6b2 commit adf526c

File tree

9 files changed

+135
-57
lines changed

9 files changed

+135
-57
lines changed

pool/config_test.go

+14-9
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
11
package pool
22

33
import (
4+
"fmt"
45
"testing"
56

67
"github.com/0xPolygonHermez/zkevm-node/state"
8+
"github.com/stretchr/testify/assert"
79
)
810

911
func TestIsWithinConstraints(t *testing.T) {
@@ -20,9 +22,9 @@ func TestIsWithinConstraints(t *testing.T) {
2022
}
2123

2224
testCases := []struct {
23-
desc string
24-
counters state.ZKCounters
25-
expected bool
25+
desc string
26+
counters state.ZKCounters
27+
errExpected error
2628
}{
2729
{
2830
desc: "All constraints within limits",
@@ -37,7 +39,7 @@ func TestIsWithinConstraints(t *testing.T) {
3739
Steps: 2000,
3840
Sha256Hashes_V2: 4000,
3941
},
40-
expected: true,
42+
errExpected: nil,
4143
},
4244
{
4345
desc: "All constraints exceed limits",
@@ -52,14 +54,17 @@ func TestIsWithinConstraints(t *testing.T) {
5254
Steps: 5000,
5355
Sha256Hashes_V2: 6000,
5456
},
55-
expected: false,
57+
errExpected: fmt.Errorf("out of counters at node level (GasUsed, KeccakHashes, PoseidonHashes, PoseidonPaddings, MemAligns, Arithmetics, Binaries, Steps, Sha256Hashes)"),
5658
},
5759
}
5860

59-
for _, tC := range testCases {
60-
t.Run(tC.desc, func(t *testing.T) {
61-
if got := cfg.IsWithinConstraints(tC.counters); got != tC.expected {
62-
t.Errorf("Expected %v, got %v", tC.expected, got)
61+
for _, tc := range testCases {
62+
t.Run(tc.desc, func(t *testing.T) {
63+
err := cfg.CheckNodeLevelOOC(tc.counters)
64+
if tc.errExpected != nil {
65+
assert.EqualError(t, err, tc.errExpected.Error())
66+
} else {
67+
assert.NoError(t, err)
6368
}
6469
})
6570
}

pool/pool.go

+10-7
Original file line numberDiff line numberDiff line change
@@ -196,7 +196,16 @@ func (p *Pool) StoreTx(ctx context.Context, tx types.Transaction, ip string, isW
196196
return err
197197
}
198198

199+
var oocError error
199200
if preExecutionResponse.OOCError != nil {
201+
oocError = preExecutionResponse.OOCError
202+
} else {
203+
if err = p.batchConstraintsCfg.CheckNodeLevelOOC(preExecutionResponse.reservedZKCounters); err != nil {
204+
oocError = err
205+
}
206+
}
207+
208+
if oocError != nil {
200209
event := &event.Event{
201210
ReceivedAt: time.Now(),
202211
IPAddress: ip,
@@ -212,7 +221,7 @@ func (p *Pool) StoreTx(ctx context.Context, tx types.Transaction, ip string, isW
212221
log.Errorf("error adding event: %v", err)
213222
}
214223
// Do not add tx to the pool
215-
return fmt.Errorf("failed to add tx to the pool: %w", preExecutionResponse.OOCError)
224+
return fmt.Errorf("failed to add tx to the pool: %w", oocError)
216225
} else if preExecutionResponse.OOGError != nil {
217226
event := &event.Event{
218227
ReceivedAt: time.Now(),
@@ -332,12 +341,6 @@ func (p *Pool) preExecuteTx(ctx context.Context, tx types.Transaction) (preExecu
332341
if errors.Is(errorToCheck, runtime.ErrOutOfGas) {
333342
response.OOGError = err
334343
}
335-
} else {
336-
if !p.batchConstraintsCfg.IsWithinConstraints(processBatchResponse.UsedZkCounters) {
337-
err := fmt.Errorf("OutOfCounters Error (Node level) for tx: %s", tx.Hash().String())
338-
response.OOCError = err
339-
log.Error(err.Error())
340-
}
341344
}
342345

343346
response.usedZKCounters = processBatchResponse.UsedZkCounters

sequencer/finalizer.go

+22-5
Original file line numberDiff line numberDiff line change
@@ -404,9 +404,26 @@ func (f *finalizer) finalizeBatches(ctx context.Context) {
404404
f.finalizeWIPL2Block(ctx)
405405
}
406406

407-
tx, err := f.workerIntf.GetBestFittingTx(f.wipBatch.imRemainingResources, f.wipBatch.imHighReservedZKCounters)
407+
tx, oocTxs, err := f.workerIntf.GetBestFittingTx(f.wipBatch.imRemainingResources, f.wipBatch.imHighReservedZKCounters, (f.wipBatch.countOfL2Blocks == 0 && f.wipL2Block.isEmpty()))
408408

409-
// If we have txs pending to process but none of them fits into the wip batch, we close the wip batch and open a new one
409+
// Set as invalid txs in the worker pool that will never fit into an empty batch
410+
for _, oocTx := range oocTxs {
411+
log.Infof("tx %s doesn't fits in empty batch %d (node OOC), setting tx as invalid in the pool", oocTx.HashStr, f.wipL2Block.trackingNum, f.wipBatch.batchNumber)
412+
413+
f.LogEvent(ctx, event.Level_Info, event.EventID_NodeOOC,
414+
fmt.Sprintf("tx %s doesn't fits in empty batch %d (node OOC), from: %s, IP: %s", oocTx.HashStr, f.wipBatch.batchNumber, oocTx.FromStr, oocTx.IP), nil)
415+
416+
// Delete the transaction from the worker
417+
f.workerIntf.DeleteTx(oocTx.Hash, oocTx.From)
418+
419+
errMsg := "node OOC"
420+
err = f.poolIntf.UpdateTxStatus(ctx, oocTx.Hash, pool.TxStatusInvalid, false, &errMsg)
421+
if err != nil {
422+
log.Errorf("failed to update status to invalid in the pool for tx %s, error: %v", oocTx.Hash.String(), err)
423+
}
424+
}
425+
426+
// We have txs pending to process but none of them fits into the wip batch we close the wip batch and open a new one
410427
if err == ErrNoFittingTransaction {
411428
f.finalizeWIPBatch(ctx, state.NoTxFitsClosingReason)
412429
continue
@@ -690,11 +707,11 @@ func (f *finalizer) handleProcessTransactionResponse(ctx context.Context, tx *Tx
690707
} else {
691708
log.Infof("current tx %s needed resources exceeds the remaining batch resources, overflow resource: %s, updating metadata for tx in worker and continuing. counters: {batch: %s, used: %s, reserved: %s, needed: %s, high: %s}",
692709
tx.HashStr, overflowResource, f.logZKCounters(f.wipBatch.imRemainingResources.ZKCounters), f.logZKCounters(result.UsedZkCounters), f.logZKCounters(result.ReservedZkCounters), f.logZKCounters(neededZKCounters), f.logZKCounters(f.wipBatch.imHighReservedZKCounters))
693-
if !f.batchConstraints.IsWithinConstraints(result.ReservedZkCounters) {
694-
log.Infof("current tx %s reserved resources exceeds the max limit for batch resources (node OOC), setting tx as invalid in the pool", tx.HashStr)
710+
if err := f.batchConstraints.CheckNodeLevelOOC(result.ReservedZkCounters); err != nil {
711+
log.Infof("current tx %s reserved resources exceeds the max limit for batch resources (node OOC), setting tx as invalid in the pool, error: %v", tx.HashStr, err)
695712

696713
f.LogEvent(ctx, event.Level_Info, event.EventID_NodeOOC,
697-
fmt.Sprintf("tx %s exceeds node max limit batch resources (node OOC), from: %s, IP: %s", tx.HashStr, tx.FromStr, tx.IP), nil)
714+
fmt.Sprintf("tx %s exceeds node max limit batch resources (node OOC), from: %s, IP: %s, error: %v", tx.HashStr, tx.FromStr, tx.IP, err), nil)
698715

699716
// Delete the transaction from the txSorted list
700717
f.workerIntf.DeleteTx(tx.Hash, tx.From)

sequencer/interfaces.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -84,7 +84,7 @@ type stateInterface interface {
8484
}
8585

8686
type workerInterface interface {
87-
GetBestFittingTx(remainingResources state.BatchResources, highReservedCounters state.ZKCounters) (*TxTracker, error)
87+
GetBestFittingTx(remainingResources state.BatchResources, highReservedCounters state.ZKCounters, fistL2Block bool) (*TxTracker, []*TxTracker, error)
8888
UpdateAfterSingleSuccessfulTxExecution(from common.Address, touchedAddresses map[common.Address]*state.InfoReadWrite) []*TxTracker
8989
UpdateTxZKCounters(txHash common.Hash, from common.Address, usedZKCounters state.ZKCounters, reservedZKCounters state.ZKCounters)
9090
AddTxTracker(ctx context.Context, txTracker *TxTracker) (replacedTx *TxTracker, dropReason error)

sequencer/l2block.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -657,7 +657,7 @@ func (f *finalizer) openNewWIPL2Block(ctx context.Context, prevTimestamp uint64,
657657

658658
f.wipBatch.imHighReservedZKCounters = newHighZKCounters
659659
} else {
660-
log.Infof("new wip L2 block [%d] reserved resources exceeds the remaining batch resources, overflow resource: %s, closing WIP batch and creating new one. counters: {batch: %s, used: %s, reserved: %s, needed: %s, high: %s}",
660+
log.Infof("new wip L2 block [%d] needed resources exceeds the remaining batch resources, overflow resource: %s, closing WIP batch and creating new one. counters: {batch: %s, used: %s, reserved: %s, needed: %s, high: %s}",
661661
f.wipL2Block.trackingNum, overflowResource,
662662
f.logZKCounters(f.wipBatch.imRemainingResources.ZKCounters), f.logZKCounters(f.wipL2Block.usedZKCountersOnNew), f.logZKCounters(f.wipL2Block.reservedZKCountersOnNew), f.logZKCounters(neededZKCounters), f.logZKCounters(f.wipBatch.imHighReservedZKCounters))
663663
}

sequencer/mock_worker.go

+21-12
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

sequencer/worker.go

+25-10
Original file line numberDiff line numberDiff line change
@@ -64,8 +64,8 @@ func (w *Worker) addTxTracker(ctx context.Context, tx *TxTracker, mutex *sync.Mu
6464
}
6565

6666
// Make sure the transaction's reserved ZKCounters are within the constraints.
67-
if !w.batchConstraints.IsWithinConstraints(tx.ReservedZKCounters) {
68-
log.Errorf("outOfCounters error (node level) for tx %s", tx.Hash.String())
67+
if err := w.batchConstraints.CheckNodeLevelOOC(tx.ReservedZKCounters); err != nil {
68+
log.Infof("out of counters (node level) when adding tx %s from address %s, error: %v", tx.Hash, tx.From, err)
6969
mutexUnlock(mutex)
7070
return nil, pool.ErrOutOfCounters
7171
}
@@ -405,7 +405,7 @@ func (w *Worker) DeleteTxPendingToStore(txHash common.Hash, addr common.Address)
405405
}
406406

407407
// GetBestFittingTx gets the most efficient tx that fits in the available batch resources
408-
func (w *Worker) GetBestFittingTx(remainingResources state.BatchResources, highReservedCounters state.ZKCounters) (*TxTracker, error) {
408+
func (w *Worker) GetBestFittingTx(remainingResources state.BatchResources, highReservedCounters state.ZKCounters, isFistL2BlockAndEmpty bool) (*TxTracker, []*TxTracker, error) {
409409
w.workerMutex.Lock()
410410
defer w.workerMutex.Unlock()
411411

@@ -417,7 +417,7 @@ func (w *Worker) GetBestFittingTx(remainingResources state.BatchResources, highR
417417
w.reorgedTxs = w.reorgedTxs[1:]
418418
if addrQueue, found := w.pool[reorgedTx.FromStr]; found {
419419
if addrQueue.readyTx != nil && addrQueue.readyTx.Hash == reorgedTx.Hash {
420-
return reorgedTx, nil
420+
return reorgedTx, nil, nil
421421
} else {
422422
log.Warnf("reorged tx %s is not the ready tx for addrQueue %s, this shouldn't happen", reorgedTx.Hash, reorgedTx.From)
423423
}
@@ -427,12 +427,14 @@ func (w *Worker) GetBestFittingTx(remainingResources state.BatchResources, highR
427427
}
428428

429429
if w.txSortedList.len() == 0 {
430-
return nil, ErrTransactionsListEmpty
430+
return nil, nil, ErrTransactionsListEmpty
431431
}
432432

433433
var (
434-
tx *TxTracker
435-
foundMutex sync.RWMutex
434+
tx *TxTracker
435+
foundMutex sync.RWMutex
436+
oocTxs []*TxTracker
437+
oocTxsMutex sync.Mutex
436438
)
437439

438440
nGoRoutines := runtime.NumCPU()
@@ -457,7 +459,14 @@ func (w *Worker) GetBestFittingTx(remainingResources state.BatchResources, highR
457459
needed, _ := getNeededZKCounters(highReservedCounters, txCandidate.UsedZKCounters, txCandidate.ReservedZKCounters)
458460
fits, _ := bresources.Fits(state.BatchResources{ZKCounters: needed, Bytes: txCandidate.Bytes})
459461
if !fits {
460-
// We don't add this Tx
462+
// If we are looking for a tx for the first empty L2 block in the batch and this tx doesn't fits in the batch, then this tx will never fit in any batch.
463+
// We add the tx to the oocTxs slice. That slice will be returned to set these txs as invalid (and delete them from the worker) from the finalizer code
464+
if isFistL2BlockAndEmpty {
465+
oocTxsMutex.Lock()
466+
oocTxs = append(oocTxs, txCandidate)
467+
oocTxsMutex.Unlock()
468+
}
469+
// We continue looking for a tx that fits in the batch
461470
continue
462471
}
463472

@@ -477,9 +486,15 @@ func (w *Worker) GetBestFittingTx(remainingResources state.BatchResources, highR
477486
if foundAt != -1 {
478487
log.Debugf("best fitting tx %s found at index %d with gasPrice %d", tx.HashStr, foundAt, tx.GasPrice)
479488
w.wipTx = tx
480-
return tx, nil
489+
return tx, oocTxs, nil
481490
} else {
482-
return nil, ErrNoFittingTransaction
491+
// If the length of the oocTxs slice is equal to the length of the txSortedList this means that all the txs are ooc,
492+
// therefore we need to return an error indicating that the list is empty
493+
if w.txSortedList.len() == len(oocTxs) {
494+
return nil, oocTxs, ErrTransactionsListEmpty
495+
} else {
496+
return nil, oocTxs, ErrNoFittingTransaction
497+
}
483498
}
484499
}
485500

sequencer/worker_test.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -258,7 +258,7 @@ func TestWorkerGetBestTx(t *testing.T) {
258258
ct := 0
259259

260260
for {
261-
tx, _ := worker.GetBestFittingTx(rc, state.ZKCounters{})
261+
tx, _, _ := worker.GetBestFittingTx(rc, state.ZKCounters{}, true)
262262
if tx != nil {
263263
if ct >= len(expectedGetBestTx) {
264264
t.Fatalf("Error getting more best tx than expected. Expected=%d, Actual=%d", len(expectedGetBestTx), ct+1)

state/config.go

+40-11
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
package state
22

33
import (
4+
"fmt"
5+
46
"github.com/0xPolygonHermez/zkevm-node/config/types"
57
"github.com/0xPolygonHermez/zkevm-node/db"
68
)
@@ -71,15 +73,42 @@ type BatchConstraintsCfg struct {
7173
MaxSHA256Hashes uint32 `mapstructure:"MaxSHA256Hashes"`
7274
}
7375

74-
// IsWithinConstraints checks if the counters are within the batch constraints
75-
func (c BatchConstraintsCfg) IsWithinConstraints(counters ZKCounters) bool {
76-
return counters.GasUsed <= c.MaxCumulativeGasUsed &&
77-
counters.KeccakHashes <= c.MaxKeccakHashes &&
78-
counters.PoseidonHashes <= c.MaxPoseidonHashes &&
79-
counters.PoseidonPaddings <= c.MaxPoseidonPaddings &&
80-
counters.MemAligns <= c.MaxMemAligns &&
81-
counters.Arithmetics <= c.MaxArithmetics &&
82-
counters.Binaries <= c.MaxBinaries &&
83-
counters.Steps <= c.MaxSteps &&
84-
counters.Sha256Hashes_V2 <= c.MaxSHA256Hashes
76+
// CheckNodeLevelOOC checks if the counters are within the batch constraints
77+
func (c BatchConstraintsCfg) CheckNodeLevelOOC(counters ZKCounters) error {
78+
oocList := ""
79+
80+
if counters.GasUsed > c.MaxCumulativeGasUsed {
81+
oocList += "GasUsed, "
82+
}
83+
if counters.KeccakHashes > c.MaxKeccakHashes {
84+
oocList += "KeccakHashes, "
85+
}
86+
if counters.PoseidonHashes > c.MaxPoseidonHashes {
87+
oocList += "PoseidonHashes, "
88+
}
89+
if counters.PoseidonPaddings > c.MaxPoseidonPaddings {
90+
oocList += "PoseidonPaddings, "
91+
}
92+
if counters.MemAligns > c.MaxMemAligns {
93+
oocList += "MemAligns, "
94+
}
95+
if counters.Arithmetics > c.MaxArithmetics {
96+
oocList += "Arithmetics, "
97+
}
98+
if counters.Binaries > c.MaxBinaries {
99+
oocList += "Binaries, "
100+
}
101+
if counters.Steps > c.MaxSteps {
102+
oocList += "Steps, "
103+
}
104+
if counters.Sha256Hashes_V2 > c.MaxSHA256Hashes {
105+
oocList += "Sha256Hashes, "
106+
}
107+
108+
if oocList != "" {
109+
oocList = oocList[:len(oocList)-2] // Remove last comma and blank space
110+
return fmt.Errorf("out of counters at node level (%s)", oocList)
111+
}
112+
113+
return nil
85114
}

0 commit comments

Comments
 (0)