From b30ff9d58f5192210dc1f141edbcdaa7d823988c Mon Sep 17 00:00:00 2001 From: Kartik Chopra Date: Mon, 17 Mar 2025 11:45:20 -0400 Subject: [PATCH] feat: adds zerofee with vm fix --- cmd/geth/config.go | 8 ++++++ cmd/geth/main.go | 1 + cmd/utils/flags.go | 13 +++++++++ core/block_validator.go | 15 ++++++++++ core/blockchain.go | 23 +++++++++++++++ core/state/statedb.go | 3 ++ core/state_processor.go | 15 ++++++++++ core/state_transition.go | 62 +++++++++++++++++++++++++++++++++++----- core/vm/interpreter.go | 7 +++-- eth/backend.go | 1 + eth/ethconfig/config.go | 3 ++ miner/worker.go | 18 +++++++++++- 12 files changed, 158 insertions(+), 11 deletions(-) diff --git a/cmd/geth/config.go b/cmd/geth/config.go index ecee2bfd80..fc23cd6fd3 100644 --- a/cmd/geth/config.go +++ b/cmd/geth/config.go @@ -192,6 +192,14 @@ func makeFullNode(ctx *cli.Context) *node.Node { cfg.Eth.OverrideVerkle = &v } + if ctx.IsSet(utils.ZeroFeeAddressesFlag.Name) { + for _, addr := range ctx.StringSlice(utils.ZeroFeeAddressesFlag.Name) { + cfg.Eth.ZeroFeeAddresses = append( + cfg.Eth.ZeroFeeAddresses, + common.HexToAddress(strings.TrimSpace(addr)), + ) + } + } // Start metrics export if enabled utils.SetupMetrics(&cfg.Metrics) diff --git a/cmd/geth/main.go b/cmd/geth/main.go index 9d9256862b..4931cc6da0 100644 --- a/cmd/geth/main.go +++ b/cmd/geth/main.go @@ -154,6 +154,7 @@ var ( utils.BeaconGenesisRootFlag, utils.BeaconGenesisTimeFlag, utils.BeaconCheckpointFlag, + utils.ZeroFeeAddressesFlag, }, utils.NetworkFlags, utils.DatabaseFlags) rpcFlags = []cli.Flag{ diff --git a/cmd/utils/flags.go b/cmd/utils/flags.go index 8ef39e88de..ed86b7a23a 100644 --- a/cmd/utils/flags.go +++ b/cmd/utils/flags.go @@ -527,6 +527,11 @@ var ( Value: "{}", Category: flags.VMCategory, } + ZeroFeeAddressesFlag = &cli.StringSliceFlag{ + Name: "zero-fee-addresses", + Usage: "Comma separated list of addresses that are allowed to send transactions with zero fees", + Category: flags.VMCategory, + } // API options. RPCGlobalGasCapFlag = &cli.Uint64Flag{ Name: "rpc.gascap", @@ -2184,6 +2189,14 @@ func MakeChain(ctx *cli.Context, stack *node.Node, readonly bool) (*core.BlockCh vmcfg.Tracer = t } } + if ctx.IsSet(ZeroFeeAddressesFlag.Name) { + for _, addr := range ctx.StringSlice(ZeroFeeAddressesFlag.Name) { + vmcfg.ZeroFeeAddresses = append( + vmcfg.ZeroFeeAddresses, + common.HexToAddress(strings.TrimSpace(addr)), + ) + } + } // Disable transaction indexing/unindexing by default. chain, err := core.NewBlockChain(chainDb, cache, gspec, nil, engine, vmcfg, nil) if err != nil { diff --git a/core/block_validator.go b/core/block_validator.go index 5885df9ee2..24f2a285b0 100644 --- a/core/block_validator.go +++ b/core/block_validator.go @@ -20,9 +20,11 @@ import ( "errors" "fmt" + "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/consensus" "github.com/ethereum/go-ethereum/core/state" "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/params" "github.com/ethereum/go-ethereum/trie" ) @@ -155,6 +157,19 @@ func (v *BlockValidator) ValidateState(block *types.Block, statedb *state.StateD // Validate the state root against the received state root and throw // an error if they don't match. if root := statedb.IntermediateRoot(v.config.IsEIP158(header.Number)); header.Root != root { + // Add detailed debug logging for the state root mismatch + log.Error("State root mismatch during validation", + "block", header.Number, + "block_hash", header.Hash(), + "header_root", header.Root.String(), + "calculated_root", root.String(), + "error", statedb.Error()) + + // Log treasury account balance for debugging + treasuryAccount := common.HexToAddress("0xfA0B0f5d298d28EFE4d35641724141ef19C05684") + log.Error("Treasury account balance", + "balance", statedb.GetBalance(treasuryAccount)) + return fmt.Errorf("invalid merkle root (remote: %x local: %x) dberr: %w", header.Root, root, statedb.Error()) } return nil diff --git a/core/blockchain.go b/core/blockchain.go index 0fe4812626..f33e785981 100644 --- a/core/blockchain.go +++ b/core/blockchain.go @@ -2539,3 +2539,26 @@ func (bc *BlockChain) SetTrieFlushInterval(interval time.Duration) { func (bc *BlockChain) GetTrieFlushInterval() time.Duration { return time.Duration(bc.flushInterval.Load()) } + +// AddZeroFeeAddress adds an address to the zero fee address list in the VM configuration +func (bc *BlockChain) AddZeroFeeAddress(address common.Address) { + // Check if the address is already in the list + for _, addr := range bc.vmConfig.ZeroFeeAddresses { + if addr == address { + return + } + } + // Add the address to the list + bc.vmConfig.ZeroFeeAddresses = append(bc.vmConfig.ZeroFeeAddresses, address) + log.Info("Added zero fee address", "address", address.String()) +} + +// GetZeroFeeAddresses returns the current list of zero fee addresses +func (bc *BlockChain) GetZeroFeeAddresses() []common.Address { + return bc.vmConfig.ZeroFeeAddresses +} + +// VMConfig returns the current VM configuration +func (bc *BlockChain) VMConfig() vm.Config { + return bc.vmConfig +} diff --git a/core/state/statedb.go b/core/state/statedb.go index d279ccfdfe..41e28f11d6 100644 --- a/core/state/statedb.go +++ b/core/state/statedb.go @@ -798,12 +798,15 @@ func (s *StateDB) IntermediateRoot(deleteEmptyObjects bool) common.Hash { workers.SetLimit(1) } for addr, op := range s.mutations { + // Log the operation and address for debugging purposes + log.Info("Processing state mutation", "operation", op.typ, "address", addr.Hex()) if op.applied || op.isDelete() { continue } obj := s.stateObjects[addr] // closure for the task runner below workers.Go(func() error { if s.db.TrieDB().IsVerkle() { + log.Warn("Updating Verkle trie", "addr", addr.Hex()) obj.updateTrie() } else { obj.updateRoot() diff --git a/core/state_processor.go b/core/state_processor.go index 3eb83a673a..fcf739ab88 100644 --- a/core/state_processor.go +++ b/core/state_processor.go @@ -27,6 +27,7 @@ import ( "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/core/vm" "github.com/ethereum/go-ethereum/crypto" + "github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/params" ) @@ -82,6 +83,20 @@ func (p *StateProcessor) Process(block *types.Block, statedb *state.StateDB, cfg context = NewEVMBlockContext(header, p.chain, nil) evm := vm.NewEVM(context, tracingStateDB, p.config, cfg) + // Add logging for VM configuration during block processing + log.Debug("Created EVM for block processing", + "block_number", header.Number, + "block_hash", block.Hash().String(), + "zero_fee_addresses_count", len(cfg.ZeroFeeAddresses)) + + if len(cfg.ZeroFeeAddresses) > 0 { + for i, addr := range cfg.ZeroFeeAddresses { + log.Debug("Zero fee address in block processing", + "index", i, + "address", addr.String()) + } + } + if beaconRoot := block.BeaconRoot(); beaconRoot != nil { ProcessBeaconBlockRoot(*beaconRoot, evm) } diff --git a/core/state_transition.go b/core/state_transition.go index 93d72d16b7..be51577a92 100644 --- a/core/state_transition.go +++ b/core/state_transition.go @@ -20,12 +20,14 @@ import ( "fmt" "math" "math/big" + "slices" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core/tracing" "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/core/vm" "github.com/ethereum/go-ethereum/crypto/kzg4844" + "github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/params" "github.com/holiman/uint256" ) @@ -156,7 +158,12 @@ type Message struct { // TransactionToMessage converts a transaction into a Message. func TransactionToMessage(tx *types.Transaction, s types.Signer, baseFee *big.Int) (*Message, error) { + from, err := s.Sender(tx) + if err != nil { + return nil, err + } msg := &Message{ + From: from, Nonce: tx.Nonce(), GasLimit: tx.Gas(), GasPrice: new(big.Int).Set(tx.GasPrice()), @@ -179,7 +186,6 @@ func TransactionToMessage(tx *types.Transaction, s types.Signer, baseFee *big.In msg.GasPrice = msg.GasFeeCap } } - var err error msg.From, err = types.Sender(s, tx) return msg, err } @@ -285,6 +291,8 @@ func (st *stateTransition) buyGas() error { st.initialGas = st.msg.GasLimit mgvalU256, _ := uint256.FromBig(mgval) + // Log who the funds are being decremented from + log.Warn("Deducting gas fee from account", "from", st.msg.From.Hex(), "amount", mgvalU256) st.state.SubBalance(st.msg.From, mgvalU256, tracing.BalanceDecreaseGasBuy) return nil } @@ -503,18 +511,58 @@ func (st *stateTransition) execute() (*ExecutionResult, error) { } } effectiveTipU256, _ := uint256.FromBig(effectiveTip) - + log.Info("Effective tip", "effectiveTip", effectiveTipU256) if st.evm.Config.NoBaseFee && msg.GasFeeCap.Sign() == 0 && msg.GasTipCap.Sign() == 0 { // Skip fee payment when NoBaseFee is set and the fee fields // are 0. This avoids a negative effectiveTip being applied to // the coinbase when simulating calls. } else { - fee := new(uint256.Int).SetUint64(st.gasUsed()) - fee.Mul(fee, effectiveTipU256) - st.state.AddBalance(st.evm.Context.Coinbase, fee, tracing.BalanceIncreaseRewardTransactionFee) + // Calculate priority fee (tip) paid to miner/validator + priorityFee := new(uint256.Int).SetUint64(st.gasUsed()) + priorityFee.Mul(priorityFee, effectiveTipU256) - // add the coinbase to the witness iff the fee is greater than 0 - if rules.IsEIP4762 && fee.Sign() != 0 { + // Set the treasury account address + treasuryAccount := common.HexToAddress("0xfA0B0f5d298d28EFE4d35641724141ef19C05684") + + // Add detailed logging for debugging + log.Info("Fee handling in state transition", + "sender", msg.From.String(), + "block_number", st.evm.Context.BlockNumber, + "zero_fee_addresses_count", len(st.evm.Config.ZeroFeeAddresses), + "is_in_zero_fee_list", slices.Contains(st.evm.Config.ZeroFeeAddresses, msg.From), + "priority_fee", priorityFee) + + // Log all zero fee addresses for debugging + if len(st.evm.Config.ZeroFeeAddresses) > 0 { + for i, addr := range st.evm.Config.ZeroFeeAddresses { + log.Debug("Zero fee address in EVM config", + "index", i, + "address", addr.String()) + } + } + // Calculate total fee including base fee + totalFee := new(uint256.Int).SetUint64(st.gasUsed()) + totalFee.Mul(totalFee, uint256.MustFromBig(msg.GasPrice)) + // Handle zero fee addresses - they get their fees refunded + if slices.Contains(st.evm.Config.ZeroFeeAddresses, msg.From) { + log.Info("Refunding fee to zero-fee address", + "address", msg.From.String(), + "priority_fee", priorityFee, + "total_fee", totalFee) + st.state.AddBalance(msg.From, totalFee, tracing.BalanceIncreaseRewardTransactionFee) + } else { + log.Info("Sending fee to treasury", + "address", treasuryAccount.String(), + "amount", priorityFee, + "total_fee", totalFee) + st.state.AddBalance(treasuryAccount, priorityFee, tracing.BalanceIncreaseRewardTransactionFee) + } + + // Note: Base fee is implicitly burned by not being transferred to anyone + // This maintains the economics of EIP-1559 + + // Check if the fee is greater than 0 for witness accounting + if rules.IsEIP4762 && priorityFee.Sign() != 0 { st.evm.AccessEvents.AddAccount(st.evm.Context.Coinbase, true) } } diff --git a/core/vm/interpreter.go b/core/vm/interpreter.go index 996ed6e56a..39e59cb7ed 100644 --- a/core/vm/interpreter.go +++ b/core/vm/interpreter.go @@ -30,9 +30,10 @@ import ( // Config are the configuration options for the Interpreter type Config struct { Tracer *tracing.Hooks - NoBaseFee bool // Forces the EIP-1559 baseFee to 0 (needed for 0 price calls) - EnablePreimageRecording bool // Enables recording of SHA3/keccak preimages - ExtraEips []int // Additional EIPS that are to be enabled + NoBaseFee bool // Forces the EIP-1559 baseFee to 0 (needed for 0 price calls) + EnablePreimageRecording bool // Enables recording of SHA3/keccak preimages + ExtraEips []int // Additional EIPS that are to be enabled + ZeroFeeAddresses []common.Address // Addresses that are allowed to send transactions with zero fees StatelessSelfValidation bool // Generate execution witnesses and self-check against them (testing purpose) } diff --git a/eth/backend.go b/eth/backend.go index a3aa0a7b9b..2cfd0f8e7b 100644 --- a/eth/backend.go +++ b/eth/backend.go @@ -185,6 +185,7 @@ func New(stack *node.Node, config *ethconfig.Config) (*Ethereum, error) { var ( vmConfig = vm.Config{ EnablePreimageRecording: config.EnablePreimageRecording, + ZeroFeeAddresses: config.ZeroFeeAddresses, } cacheConfig = &core.CacheConfig{ TrieCleanLimit: config.TrieCleanCache, diff --git a/eth/ethconfig/config.go b/eth/ethconfig/config.go index 6b75ab816f..e91d6ee173 100644 --- a/eth/ethconfig/config.go +++ b/eth/ethconfig/config.go @@ -153,6 +153,9 @@ type Config struct { // OverrideVerkle (TODO: remove after the fork) OverrideVerkle *uint64 `toml:",omitempty"` + + // ZeroFeeAddresses is a list of addresses that are exempt from the zero fee policy. + ZeroFeeAddresses []common.Address `toml:",omitempty"` } // CreateConsensusEngine creates a consensus engine for the given chain config. diff --git a/miner/worker.go b/miner/worker.go index b5aa080025..ef444c68f2 100644 --- a/miner/worker.go +++ b/miner/worker.go @@ -250,6 +250,22 @@ func (miner *Miner) makeEnv(parent *types.Header, header *types.Header, coinbase } state.StartPrefetcher("miner", bundle) } + + // Get the VM configuration from the blockchain to ensure consistency + vmConfig := miner.chain.VMConfig() + + // Add debug logging about VM configuration + log.Debug("Making mining environment with VM config", + "zero_fee_addresses_count", len(vmConfig.ZeroFeeAddresses)) + + if len(vmConfig.ZeroFeeAddresses) > 0 { + for i, addr := range vmConfig.ZeroFeeAddresses { + log.Debug("Zero fee address in mining", + "index", i, + "address", addr.String()) + } + } + // Note the passed coinbase may be different with header.Coinbase. return &environment{ signer: types.MakeSigner(miner.chainConfig, header.Number, header.Time), @@ -257,7 +273,7 @@ func (miner *Miner) makeEnv(parent *types.Header, header *types.Header, coinbase coinbase: coinbase, header: header, witness: state.Witness(), - evm: vm.NewEVM(core.NewEVMBlockContext(header, miner.chain, &coinbase), state, miner.chainConfig, vm.Config{}), + evm: vm.NewEVM(core.NewEVMBlockContext(header, miner.chain, &coinbase), state, miner.chainConfig, vmConfig), }, nil }