Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
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
5 changes: 5 additions & 0 deletions cmd/utils/flags_xlayer.go
Original file line number Diff line number Diff line change
Expand Up @@ -472,6 +472,11 @@ var (
Usage: "Enable data stream batch optimization",
Value: false,
}
TxVerifyStartBlock = cli.Uint64Flag{
Name: "zkevm.tx-verify-start-block",
Usage: "Start block for RPC sync mode and transaction verification. When non-zero, enables both: 1) RPC sync mode (fetches blocks from upstream L2 RPC instead of DataStream), 2) Transaction verification (compares local vs upstream execution results). 0 = both features disabled, >0 = both features enabled starting from this block",
Value: 0,
}
)

func setGPOXLayer(ctx *cli.Context, cfg *gaspricecfg.Config) {
Expand Down
8 changes: 8 additions & 0 deletions consensus/misc/eip1559_zk.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (
"github.com/ledgerwatch/erigon-lib/chain"
"github.com/ledgerwatch/erigon/core/types"
"github.com/ledgerwatch/erigon/params"
"github.com/ledgerwatch/log/v3"
)

func CalcBaseFeeZk(config *chain.Config, parent *types.Header) *big.Int {
Expand All @@ -22,5 +23,12 @@ func CalcBaseFeeZk(config *chain.Config, parent *types.Header) *big.Int {
return new(big.Int).SetUint64(params.InitialBaseFee)
}

// Defensive check: if parent.BaseFee is nil (e.g., legacy zkEVM blocks before EIP-1559),
// return InitialBaseFee to prevent nil pointer dereference
if parent.BaseFee == nil {
log.Warn("BaseFee is nil, using InitialBaseFee", "blockNum", parent.Number.Uint64())
return new(big.Int).SetUint64(params.InitialBaseFee)
}

return CalcBaseFee(config, parent)
}
62 changes: 61 additions & 1 deletion core/rawdb/accessors_chain.go
Original file line number Diff line number Diff line change
Expand Up @@ -455,11 +455,33 @@ func CanonicalTransactions(db kv.Getter, baseTxId uint64, amount uint32) ([]type
if txs[i], decodeErr = types.UnmarshalTransactionFromBinary(v, false /* blobTxnsAreWrappedWithBlobs */); decodeErr != nil {
return decodeErr
}

// DEBUG: Log each transaction read
currentTxId := baseTxId + uint64(i)
if dynamicTx, ok := txs[i].(*types.DynamicFeeTransaction); ok {
log.Debug("[DEBUG CanonicalTransactions] Read EIP-1559 tx",
"txId", currentTxId,
"hash", txs[i].Hash().Hex(),
"feeCap", dynamicTx.FeeCap.String(),
"tip", dynamicTx.Tip.String())
} else {
log.Debug("[DEBUG CanonicalTransactions] Read tx",
"txId", currentTxId,
"hash", txs[i].Hash().Hex(),
"type", txs[i].Type())
}

i++
return nil
}); err != nil {
return nil, err
}

log.Debug("[DEBUG CanonicalTransactions] Summary",
"requestedFrom", baseTxId,
"requestedAmount", amount,
"actuallyRead", i)

txs = txs[:i] // user may request big "amount", but db can return small "amount". Return as much as we found.
return txs, nil
}
Expand Down Expand Up @@ -649,7 +671,19 @@ func ReadBody(db kv.Getter, hash common.Hash, number uint64) (*types.Body, uint6
if bodyForStorage.TxAmount < 2 {
panic(fmt.Sprintf("block body hash too few txs amount: %d, %d", number, bodyForStorage.TxAmount))
}
return body, bodyForStorage.BaseTxId + 1, bodyForStorage.TxAmount - 2 // 1 system txn in the begining of block, and 1 at the end

// DEBUG: Log zkEVM -2 logic
readBaseTxId := bodyForStorage.BaseTxId + 1
readTxAmount := bodyForStorage.TxAmount - 2
log.Debug("[DEBUG ReadBody] zkEVM -2 logic",
"blockNum", number,
"hash", hash.Hex(),
"storedBaseTxId", bodyForStorage.BaseTxId,
"storedTxAmount", bodyForStorage.TxAmount,
"readingFromTxId", readBaseTxId,
"readingAmount", readTxAmount)

return body, readBaseTxId, readTxAmount // 1 system txn in the begining of block, and 1 at the end
}

func HasSenders(db kv.Getter, hash common.Hash, number uint64) (bool, error) {
Expand Down Expand Up @@ -713,6 +747,16 @@ func WriteBody(db kv.RwTx, hash common.Hash, number uint64, body *types.Body) (e
Uncles: body.Uncles,
Withdrawals: body.Withdrawals,
}

// DEBUG: Log zkEVM +2 logic
log.Debug("[DEBUG WriteBody] zkEVM +2 logic",
"blockNum", number,
"hash", hash.Hex(),
"txCount", len(body.Transactions),
"baseTxId", baseTxId,
"txAmount", data.TxAmount,
"writingToTxId", baseTxId+1)

if err = WriteBodyForStorage(db, hash, number, &data); err != nil {
return fmt.Errorf("failed to write body: %w", err)
}
Expand Down Expand Up @@ -1056,6 +1100,22 @@ func ReadBlockWithSenders(db kv.Getter, hash common.Hash, number uint64) (*types
if block == nil {
return nil, nil, nil
}

// DEBUG: Log block transactions after ReadBlock
log.Debug("[DEBUG ReadBlockWithSenders] After ReadBlock",
"blockNum", number,
"hash", hash.Hex(),
"txCount", len(block.Transactions()))
for i, tx := range block.Transactions() {
if dynamicTx, ok := tx.(*types.DynamicFeeTransaction); ok {
log.Debug("[DEBUG ReadBlockWithSenders] Transaction",
"idx", i,
"hash", tx.Hash().Hex(),
"feeCap", dynamicTx.FeeCap.String(),
"tip", dynamicTx.Tip.String())
}
}

senders, err := ReadSenders(db, hash, number)
if err != nil {
return nil, nil, err
Expand Down
11 changes: 10 additions & 1 deletion core/state_processor.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@
package core

import (
"fmt"

"github.com/ledgerwatch/erigon-lib/chain"
libcommon "github.com/ledgerwatch/erigon-lib/common"

Expand All @@ -26,6 +28,7 @@ import (
"github.com/ledgerwatch/erigon/core/vm"
"github.com/ledgerwatch/erigon/core/vm/evmtypes"
"github.com/ledgerwatch/erigon/crypto"
"github.com/ledgerwatch/log/v3"
)

// applyTransaction attempts to apply a transaction to the given state database
Expand All @@ -42,8 +45,14 @@ func applyTransaction(config *chain.Config, engine consensus.EngineReader, gp *G
msg.SetCheckNonce(!cfg.StatelessExec)

// apply effective gas percentage here, so it is actual for all further calculations
if evm.ChainRules().IsForkID5Dragonfruit {
// IMPORTANT: Skip effective gas price calculation if effectiveGasPricePercentage is 0
// This can happen for OP Stack blocks (post-zkEVM migration) which should not use
// zkEVM's Fork5Dragonfruit effective gas price logic.
// When effectiveGasPricePercentage=0, CalculateEffectiveGas divides by 256, which
// incorrectly reduces GasPrice/FeeCap, causing "tip higher than fee cap" errors.
if evm.ChainRules().IsForkID5Dragonfruit && effectiveGasPricePercentage > 0 {
msg.SetGasPrice(CalculateEffectiveGas(msg.GasPrice(), effectiveGasPricePercentage))
log.Warn(fmt.Sprintf("[xlayer apply transaction, ⚠️effectiveGasPricePercentage > 0] %d, txHash: %s", effectiveGasPricePercentage, tx.Hash().Hex()))
}

if msg.FeeCap().IsZero() && engine != nil {
Expand Down
38 changes: 37 additions & 1 deletion core/state_processor_zkevm.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import (

"github.com/ledgerwatch/erigon-lib/common"
libcommon "github.com/ledgerwatch/erigon-lib/common"
"github.com/ledgerwatch/log/v3"

"github.com/ledgerwatch/erigon-lib/chain"

Expand All @@ -36,17 +37,52 @@ import (
func GetTxContext(config *chain.Config, engine consensus.EngineReader, ibs *state.IntraBlockState, header *types.Header, tx types.Transaction, evm *vm.EVM, effectiveGasPricePercentage uint8) (types.Message, evmtypes.TxContext, error) {
rules := evm.ChainRules()

// DEBUG: Log tx values before AsMessage
if dynamicTx, ok := tx.(*types.DynamicFeeTransaction); ok {
log.Debug("[DEBUG GetTxContext] Before AsMessage",
"txHash", tx.Hash().Hex(),
"tx.FeeCap", dynamicTx.FeeCap.String(),
"tx.Tip", dynamicTx.Tip.String())
}

msg, err := tx.AsMessage(*types.MakeSigner(config, header.Number.Uint64(), 0), header.BaseFee, rules)
if err != nil {
return types.Message{}, evmtypes.TxContext{}, err
}

// DEBUG: Log msg values after AsMessage
log.Debug("[DEBUG GetTxContext] After AsMessage",
"txHash", tx.Hash().Hex(),
"msg.FeeCap", msg.FeeCap().String(),
"msg.Tip", msg.Tip().String())

msg.SetEffectiveGasPricePercentage(effectiveGasPricePercentage)
msg.SetCheckNonce(!evm.Config().StatelessExec)

// apply effective gas percentage here, so it is actual for all further calculations
if evm.ChainRules().IsForkID5Dragonfruit {
// IMPORTANT: Skip effective gas price calculation if effectiveGasPricePercentage is 0
// This can happen for OP Stack blocks (post-zkEVM migration) which should not use
// zkEVM's Fork5Dragonfruit effective gas price logic.
// When effectiveGasPricePercentage=0, CalculateEffectiveGas divides by 256, which
// incorrectly reduces FeeCap (e.g., 100000000 → 390625), causing "tip higher than fee cap" errors.
if evm.ChainRules().IsForkID5Dragonfruit && effectiveGasPricePercentage > 0 {
log.Debug("[DEBUG GetTxContext] Before CalculateEffectiveGas",
"txHash", tx.Hash().Hex(),
"msg.FeeCap", msg.FeeCap().String(),
"effectiveGasPricePercentage", effectiveGasPricePercentage,
"IsForkID5Dragonfruit", true)

msg.SetGasPrice(CalculateEffectiveGas(msg.GasPrice(), effectiveGasPricePercentage))
msg.SetFeeCap(CalculateEffectiveGas(msg.FeeCap(), effectiveGasPricePercentage))

log.Debug("[DEBUG GetTxContext] After CalculateEffectiveGas",
"txHash", tx.Hash().Hex(),
"msg.FeeCap", msg.FeeCap().String())
} else if evm.ChainRules().IsForkID5Dragonfruit && effectiveGasPricePercentage == 0 {
log.Debug("[DEBUG GetTxContext] Skipping CalculateEffectiveGas",
"txHash", tx.Hash().Hex(),
"msg.FeeCap", msg.FeeCap().String(),
"reason", "effectiveGasPricePercentage is 0 (likely OP Stack block)")
}

if msg.FeeCap().IsZero() && engine != nil {
Expand Down
17 changes: 17 additions & 0 deletions core/state_transition.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import (
libcommon "github.com/ledgerwatch/erigon-lib/common"
"github.com/ledgerwatch/erigon-lib/txpool/txpoolcfg"
types2 "github.com/ledgerwatch/erigon-lib/types"
"github.com/ledgerwatch/log/v3"

"github.com/ledgerwatch/erigon-lib/common/hexutil"
"github.com/ledgerwatch/erigon-lib/common/hexutility"
Expand Down Expand Up @@ -162,6 +163,13 @@ func NewStateTransition(evm *vm.EVM, msg Message, gp *GasPool) *StateTransition

gas := msg.GasPrice()

// DEBUG: Log msg values when creating StateTransition
log.Debug("[DEBUG NewStateTransition] Creating StateTransition",
"from", msg.From().Hex(),
"msg.FeeCap", msg.FeeCap().String(),
"msg.Tip", msg.Tip().String(),
"msg.GasPrice", gas.String())

return &StateTransition{
gp: gp,
evm: evm,
Expand Down Expand Up @@ -327,6 +335,15 @@ func (st *StateTransition) preCheck(gasBailout bool) error {
// Skip the checks if gas fields are zero and baseFee was explicitly disabled (eth_call)
skipCheck := st.evm.Config().NoBaseFee && st.gasFeeCap.IsZero() && st.tip.IsZero()
if !skipCheck {
// DEBUG: Log values before checking
log.Debug("[DEBUG CheckEip1559TxGasFeeCap] Before check",
"from", st.msg.From().Hex(),
"st.gasFeeCap", st.gasFeeCap.String(),
"st.tip", st.tip.String(),
"msg.FeeCap", st.msg.FeeCap().String(),
"msg.Tip", st.msg.Tip().String(),
"baseFee", st.evm.Context.BaseFee.String())

if err := CheckEip1559TxGasFeeCap(st.msg.From(), st.gasFeeCap, st.tip, st.evm.Context.BaseFee, st.msg.IsFree()); err != nil {
return err
}
Expand Down
7 changes: 7 additions & 0 deletions eth/ethconfig/config_xlayer.go
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,13 @@ type XLayerConfig struct {
SequencerPaused bool

DataStreamBatchOptimizationEnabled bool

// TxVerifyStartBlock enables both RPC sync mode and transaction verification
// when set to a non-zero value. The RPC sync mode fetches blocks from upstream
// L2 RPC instead of DataStream, and transaction verification compares local
// execution results with upstream starting from this block number.
// 0 = both features disabled, >0 = both features enabled starting from this block
TxVerifyStartBlock uint64
}

var DefaultXLayerConfig = XLayerConfig{}
Expand Down
17 changes: 16 additions & 1 deletion eth/stagedsync/stage_execute_zkevm.go
Original file line number Diff line number Diff line change
Expand Up @@ -348,6 +348,21 @@ func getPreexecuteValues(cfg ExecuteBlockCfg, ctx context.Context, tx kv.RwTx, b
block.HeaderNoCopy().BaseFee = misc.CalcBaseFeeZk(cfg.chainConfig, parentHeader)
}

// DEBUG: Log block transactions before returning from getPreexecuteValues
log.Debug("[DEBUG getPreexecuteValues] Before returning",
"blockNum", blockNum,
"hash", preExecuteHeaderHash.Hex(),
"txCount", len(block.Transactions()))
for i, tx := range block.Transactions() {
if dynamicTx, ok := tx.(*types.DynamicFeeTransaction); ok {
log.Debug("[DEBUG getPreexecuteValues] Transaction",
"idx", i,
"hash", tx.Hash().Hex(),
"feeCap", dynamicTx.FeeCap.String(),
"tip", dynamicTx.Tip.String())
}
}

return preExecuteHeaderHash, block, senders, nil
}

Expand All @@ -368,7 +383,7 @@ func postExecuteCommitValues(
// if datastream hash was wrong, remove old data
if blockHash != datastreamBlockHash {
if cfg.chainConfig.IsForkId9Elderberry2(blockNum) {
log.Warn(fmt.Sprintf("[%s] Blockhash mismatch", logPrefix), "blockNumber", blockNum, "datastreamBlockHash", datastreamBlockHash, "calculatedBlockHash", blockHash)
log.Debug(fmt.Sprintf("[%s] Blockhash mismatch", logPrefix), "blockNumber", blockNum, "datastreamBlockHash", datastreamBlockHash, "calculatedBlockHash", blockHash)
}
if err := rawdbZk.DeleteSenders(tx, datastreamBlockHash, blockNum); err != nil {
return fmt.Errorf("DeleteSenders: %w", err)
Expand Down
Loading