diff --git a/accounts/abi/bind/backends/simulated.go b/accounts/abi/bind/backends/simulated.go index d7953dcb89a9..7a7ab8d85290 100644 --- a/accounts/abi/bind/backends/simulated.go +++ b/accounts/abi/bind/backends/simulated.go @@ -300,14 +300,14 @@ func (b *SimulatedBackend) EstimateGas(ctx context.Context, call ethereum.CallMs func (b *SimulatedBackend) callContract(ctx context.Context, call ethereum.CallMsg, block *types.Block, statedb *state.StateDB) ([]byte, uint64, bool, error) { // Ensure message is initialized properly. // EIP1559 guards - if b.config.IsEIP1559Finalized(block.Number()) && call.GasPremium == nil || call.FeeCap == nil || call.GasPrice != nil { - return nil, 0, false, fmt.Errorf("after block %d EIP1559 is finalized and transactions must contain a GasPremium and FeeCap and not contain a GasPrice", b.config.EIP1559FinalizedBlock.Uint64()) + if b.config.IsEIP1559Finalized(block.Number()) && (call.GasPremium == nil || call.FeeCap == nil || call.GasPrice != nil) { + return nil, 0, false, core.ErrTxNotEIP1559 } - if !b.config.IsEIP1559(block.Number()) && call.GasPremium != nil || call.FeeCap != nil || call.GasPrice == nil { - return nil, 0, false, fmt.Errorf("before block %d EIP1559 is not activated and transactions must contain a GasPrice and not contain a GasPremium or FeeCap", b.config.EIP1559Block.Uint64()) + if !b.config.IsEIP1559(block.Number()) && (call.GasPremium != nil || call.FeeCap != nil || call.GasPrice == nil) { + return nil, 0, false, core.ErrTxIsEIP1559 } if call.GasPrice != nil && (call.GasPremium != nil || call.FeeCap != nil) { - return nil, 0, false, errors.New("if GasPrice is set, GasPremium and FeeCap must not be set") + return nil, 0, false, core.ErrTxSetsLegacyAndEIP1559Fields } if call.FeeCap != nil && call.GasPremium == nil { return nil, 0, false, errors.New("if FeeCap is set, GasPremium must be set") @@ -350,23 +350,17 @@ func (b *SimulatedBackend) SendTransaction(ctx context.Context, tx *types.Transa defer b.mu.Unlock() // EIP1559 guards - if b.config.IsEIP1559Finalized(b.blockchain.CurrentBlock().Number()) && tx.GasPremium() == nil || tx.FeeCap() == nil || tx.GasPrice() != nil { - return fmt.Errorf("after block %d EIP1559 is finalized and transactions must contain a GasPremium and FeeCap and not contain a GasPrice", b.config.EIP1559FinalizedBlock.Uint64()) + if b.config.IsEIP1559Finalized(b.blockchain.CurrentBlock().Number()) && (tx.GasPremium() == nil || tx.FeeCap() == nil || tx.GasPrice() != nil) { + return core.ErrTxNotEIP1559 } - if !b.config.IsEIP1559(b.blockchain.CurrentBlock().Number()) && tx.GasPremium() != nil || tx.FeeCap() != nil || tx.GasPrice() == nil { - return fmt.Errorf("before block %d EIP1559 is not activated and transactions must contain a GasPrice and not contain a GasPremium or FeeCap", b.config.EIP1559Block.Uint64()) + if !b.config.IsEIP1559(b.blockchain.CurrentBlock().Number()) && (tx.GasPremium() != nil || tx.FeeCap() != nil || tx.GasPrice() == nil) { + return core.ErrTxIsEIP1559 } if tx.GasPrice() != nil && (tx.GasPremium() != nil || tx.FeeCap() != nil) { - return errors.New("if GasPrice is set, GasPremium and FeeCap must not be set") + return core.ErrTxSetsLegacyAndEIP1559Fields } - if tx.FeeCap() != nil && tx.GasPremium() == nil { - return errors.New("if FeeCap is set, GasPremium must be set") - } - if tx.GasPremium() != nil && tx.FeeCap() == nil { - return errors.New("if GasPremium is set, FeeCap must be set") - } - if tx.GasPrice() == nil && tx.GasPremium() == nil { - return errors.New("either GasPrice or GasPremium and FeeCap need to be set") + if tx.GasPrice() == nil && (tx.GasPremium() == nil || tx.FeeCap() == nil) { + return core.ErrMissingGasFields } sender, err := types.Sender(types.NewEIP155Signer(b.config.ChainID), tx) diff --git a/core/chain_makers.go b/core/chain_makers.go index 5b0f363db840..6a3363c41c66 100644 --- a/core/chain_makers.go +++ b/core/chain_makers.go @@ -52,7 +52,7 @@ type BlockGen struct { // SetCoinbase sets the coinbase of the generated block. // It can be called at most once. func (b *BlockGen) SetCoinbase(addr common.Address) { - if b.gasPool != nil || b.gasPool1559 != nil { + if b.gasPool != nil { if len(b.txs) > 0 { panic("coinbase must be set before adding transactions") } @@ -60,7 +60,6 @@ func (b *BlockGen) SetCoinbase(addr common.Address) { } b.header.Coinbase = addr b.gasPool = new(GasPool).AddGas(b.header.GasLimit) - b.gasPool1559 = new(GasPool).AddGas(params.MaxGasEIP1559) } // SetExtra sets the extra data field of the generated block. @@ -101,9 +100,12 @@ func (b *BlockGen) AddTx(tx *types.Transaction) { // added. If contract code relies on the BLOCKHASH instruction, // the block in chain will be returned. func (b *BlockGen) AddTxWithChain(bc *BlockChain, tx *types.Transaction) { - if b.gasPool == nil || b.gasPool1559 == nil { + if b.gasPool == nil { b.SetCoinbase(common.Address{}) } + if b.gasPool1559 == nil && b.config.IsEIP1559(b.header.Number) { + b.gasPool1559 = new(GasPool).AddGas(params.MaxGasEIP1559) + } b.statedb.Prepare(tx.Hash(), common.Hash{}, len(b.txs)) receipt, err := ApplyTransaction(b.config, bc, &b.header.Coinbase, b.gasPool, b.gasPool1559, b.statedb, b.header, tx, &b.header.GasUsed, vm.Config{}) if err != nil { diff --git a/core/state_transition.go b/core/state_transition.go index a6478e64e76e..9998d849a0c9 100644 --- a/core/state_transition.go +++ b/core/state_transition.go @@ -50,17 +50,18 @@ The state transitioning model does all the necessary work to work out a valid ne 6) Derive new state root */ type StateTransition struct { - gp *GasPool - gp1559 *GasPool - msg Message - gas uint64 - gasPrice *big.Int - initialGas uint64 - value *big.Int - data []byte - state vm.StateDB - evm *vm.EVM - isEIP1559 bool + gp *GasPool + gp1559 *GasPool + msg Message + gas uint64 + gasPrice *big.Int + initialGas uint64 + value *big.Int + data []byte + state vm.StateDB + evm *vm.EVM + isEIP1559 bool + eip1559GasPrice *big.Int } // Message represents a message sent to a contract. @@ -121,10 +122,7 @@ func IntrinsicGas(data []byte, contractCreation, isEIP155 bool, isEIP2028 bool) // NewStateTransition initialises and returns a new state transition object. func NewStateTransition(evm *vm.EVM, msg Message, gp, gp1559 *GasPool) *StateTransition { isEIP1559 := evm.ChainConfig().IsEIP1559(evm.BlockNumber) && msg.GasPremium() != nil && msg.FeeCap() != nil && evm.BaseFee != nil && gp1559 != nil - // check that msg doesn't try to set both legacy and 1559 params - // check that legacy has legacy params set - // reject 1559 trxs if we are before the 1559 fork blocknumber - return &StateTransition{ + st := &StateTransition{ gp: gp, gp1559: gp1559, evm: evm, @@ -135,6 +133,14 @@ func NewStateTransition(evm *vm.EVM, msg Message, gp, gp1559 *GasPool) *StateTra state: evm.StateDB, isEIP1559: isEIP1559, } + if isEIP1559 { + // EP1559 gasPrice = min(BASEFEE + tx.fee_premium, tx.fee_cap) + st.eip1559GasPrice = new(big.Int).Add(evm.BaseFee, msg.GasPremium()) + if st.eip1559GasPrice.Cmp(msg.FeeCap()) > 0 { + st.eip1559GasPrice.Set(msg.FeeCap()) + } + } + return st } // ApplyMessage computes the new state by applying the given message @@ -173,13 +179,8 @@ func (st *StateTransition) buyGas() error { } func (st *StateTransition) buyGasEIP1559() error { - // gasPrice = min(BASEFEE + tx.fee_premium, tx.fee_cap) - gasPrice := new(big.Int).Add(st.evm.BaseFee, st.msg.GasPremium()) - if gasPrice.Cmp(st.msg.FeeCap()) > 0 { - gasPrice.Set(st.msg.FeeCap()) - } // tx.origin pays gasPrice * tx.gas - mgval := new(big.Int).Mul(new(big.Int).SetUint64(st.msg.Gas()), gasPrice) + mgval := new(big.Int).Mul(new(big.Int).SetUint64(st.msg.Gas()), st.eip1559GasPrice) if st.state.GetBalance(st.msg.From()).Cmp(mgval) < 0 { return errInsufficientBalanceForGas } @@ -218,10 +219,26 @@ func (st *StateTransition) preCheck() error { return ErrNonceTooLow } } - // If we are past the EIP1559 finalization block and this transaction does not conform with EIP1559, throw an error + // If we have reached the EIP1559 finalization block and we do not conform with EIP1559, throw an error if st.evm.ChainConfig().IsEIP1559Finalized(st.evm.BlockNumber) && !st.isEIP1559 { return ErrTxNotEIP1559 } + // If we are before the EIP1559 initialization block, throw an error if we have EIP1559 fields or do not have a GasPrice + if !st.evm.ChainConfig().IsEIP1559(st.evm.BlockNumber) && (st.msg.GasPremium() != nil || st.msg.FeeCap() != nil || st.gp1559 != nil || st.evm.BaseFee != nil || st.msg.GasPrice() == nil) { + return ErrTxIsEIP1559 + } + // If transaction has both legacy and EIP1559 fields, throw an error + if (st.msg.GasPremium() != nil || st.msg.FeeCap() != nil) && st.msg.GasPrice() != nil { + return ErrTxSetsLegacyAndEIP1559Fields + } + // We need a BaseFee if we are past EIP1559 initialization + if st.evm.ChainConfig().IsEIP1559(st.evm.BlockNumber) && st.evm.BaseFee == nil { + return ErrNoBaseFee + } + // We need either a GasPrice or a FeeCap and GasPremium to be set + if st.msg.GasPrice() == nil && (st.msg.GasPremium() == nil || st.msg.FeeCap() == nil) { + return ErrMissingGasFields + } return st.buyGas() } @@ -272,12 +289,8 @@ func (st *StateTransition) TransitionDb() (ret []byte, usedGas uint64, failed bo } st.refundGas() if st.isEIP1559 { - gasPrice := new(big.Int).Add(st.evm.BaseFee, st.msg.GasPremium()) - if gasPrice.Cmp(st.msg.FeeCap()) > 0 { - gasPrice.Set(st.msg.FeeCap()) - } // block.coinbase gains (gasprice - BASEFEE) * gasused - coinBaseCredit := new(big.Int).Mul(new(big.Int).Sub(gasPrice, st.evm.BaseFee), new(big.Int).SetUint64(st.gasUsed())) + coinBaseCredit := new(big.Int).Mul(new(big.Int).Sub(st.eip1559GasPrice, st.evm.BaseFee), new(big.Int).SetUint64(st.gasUsed())) // If gasprice < BASEFEE (due to the fee_cap), this means that the block.coinbase loses funds from this operation; // in this case, check that the post-balance is non-negative and throw an exception if it is negative. if coinBaseCredit.Sign() < 0 { @@ -287,7 +300,7 @@ func (st *StateTransition) TransitionDb() (ret []byte, usedGas uint64, failed bo return nil, 0, vmerr != nil, errInsufficientCoinbaseBalance } } - st.state.AddBalance(st.evm.Coinbase, new(big.Int).Mul(new(big.Int).SetUint64(st.gasUsed()), gasPrice)) + st.state.AddBalance(st.evm.Coinbase, coinBaseCredit) return ret, st.gasUsed(), vmerr != nil, err } @@ -331,13 +344,7 @@ func (st *StateTransition) refundGasEIP1559() { // tx.origin gets refunded gasprice * (tx.gas - gasused) txGasSubUsed := new(big.Int).Sub(new(big.Int).SetUint64(st.msg.Gas()), new(big.Int).SetUint64(st.gasUsed())) - - // gasPrice = min(BASEFEE + tx.fee_premium, tx.fee_cap) - gasPrice := new(big.Int).Add(st.evm.BaseFee, st.msg.GasPremium()) - if gasPrice.Cmp(st.msg.FeeCap()) > 0 { - gasPrice.Set(st.msg.FeeCap()) - } - remaining := new(big.Int).Mul(gasPrice, txGasSubUsed) + remaining := new(big.Int).Mul(st.eip1559GasPrice, txGasSubUsed) st.state.AddBalance(st.msg.From(), remaining) // Also return remaining gas to the block gas counter so it is diff --git a/core/tx_pool.go b/core/tx_pool.go index 1ead0b72c9f4..db3c55ea4b82 100644 --- a/core/tx_pool.go +++ b/core/tx_pool.go @@ -77,9 +77,24 @@ var ( // making the transaction invalid, rather a DOS protection. ErrOversizedData = errors.New("oversized data") - // ErrTxNotEIP1559 is returned if we have passed the EIP1559 finalized block height + // ErrTxNotEIP1559 is returned if we have reached the EIP1559 finalized block height // and the input transaction does not conform to with EIP1559 - ErrTxNotEIP1559 = errors.New("transaction does not conform with EIP1559") + ErrTxNotEIP1559 = fmt.Errorf("after block %d EIP1559 is finalized and transactions must contain a GasPremium and FeeCap and not contain a GasPrice", params.EIP1559ForkFinalizedBlockNumber) + + // ErrTxIsEIP1559 is returned if we have not reached the EIP1559 initialization block height + // and the input transaction is not of the legacy type + ErrTxIsEIP1559 = fmt.Errorf("before block %d EIP1559 is not activated and transactions must contain a GasPrice and not contain a GasPremium or FeeCap", params.EIP1559ForkBlockNumber) + + // ErrTxSetsLegacyAndEIP1559Fields is returned if a transaction attempts to set + // both legacy (GasPrice) and EIP1559 (GasPremium and FeeCap) fields + ErrTxSetsLegacyAndEIP1559Fields = errors.New("transaction sets both legacy and EIP1559 fields") + + // ErrNoBaseFee is returned if we are past the EIP1559 initialization block but + // the current header does not provide a BaseFee + ErrNoBaseFee = errors.New("current header does not provide the BaseFee needed to process EIP1559 transactions") + + // ErrMissingGasFields is returned if neither GasPrice nor GasPremium and FeeCap are set + ErrMissingGasFields = errors.New("either GasPrice or GasPremium and FeeCap need to be set") ) var ( @@ -514,6 +529,33 @@ func (pool *TxPool) local() map[common.Address]types.Transactions { // validateTx checks whether a transaction is valid according to the consensus // rules and adheres to some heuristic limits of the local node (price and size). func (pool *TxPool) validateTx(tx *types.Transaction, local bool) error { + // EIP1559 guards + if pool.chainconfig.IsEIP1559(pool.chain.CurrentBlock().Number()) && pool.chain.CurrentBlock().BaseFee() == nil { + return ErrNoBaseFee + } + if pool.chainconfig.IsEIP1559Finalized(pool.chain.CurrentBlock().Number()) && (tx.GasPremium() == nil || tx.FeeCap() == nil || tx.GasPrice() != nil) { + return ErrTxNotEIP1559 + } + if !pool.chainconfig.IsEIP1559(pool.chain.CurrentBlock().Number()) && (tx.GasPremium() != nil || tx.FeeCap() != nil || tx.GasPrice() == nil) { + return ErrTxIsEIP1559 + } + if tx.GasPrice() != nil && (tx.GasPremium() != nil || tx.FeeCap() != nil) { + return ErrTxSetsLegacyAndEIP1559Fields + } + if tx.GasPrice() == nil && (tx.GasPremium() == nil || tx.FeeCap() == nil) { + return ErrMissingGasFields + } + // Set the gasPrice to the tx.GasPrice() if it is non nil (legacy transaction) + var gasPrice *big.Int + if tx.GasPrice() != nil { + gasPrice = tx.GasPrice() + } else { // Derive the gasPrice from the tx.GasPremium() and tx.FeeCap() (EIP1559 transaction) + gasPrice = new(big.Int).Add(pool.chain.CurrentBlock().BaseFee(), tx.GasPremium()) + if gasPrice.Cmp(tx.FeeCap()) > 0 { + gasPrice.Set(tx.FeeCap()) + } + } + // Heuristic limit, reject transactions over 32KB to prevent DOS attacks if tx.Size() > 32*1024 { return ErrOversizedData @@ -534,7 +576,7 @@ func (pool *TxPool) validateTx(tx *types.Transaction, local bool) error { } // Drop non-local transactions under our own minimal accepted gas price local = local || pool.locals.contains(from) // account may be local even if the transaction arrived from the network - if !local && pool.gasPrice.Cmp(tx.GasPrice()) > 0 { + if !local && pool.gasPrice.Cmp(gasPrice) > 0 { return ErrUnderpriced } // Ensure the transaction adheres to nonce ordering diff --git a/internal/ethapi/api.go b/internal/ethapi/api.go index 226db972e0df..3b89fea9e63b 100644 --- a/internal/ethapi/api.go +++ b/internal/ethapi/api.go @@ -763,14 +763,14 @@ func DoCall(ctx context.Context, b Backend, args CallArgs, blockNrOrHash rpc.Blo defer func(start time.Time) { log.Debug("Executing EVM call finished", "runtime", time.Since(start)) }(time.Now()) // EIP1559 guards - if b.ChainConfig().IsEIP1559Finalized(b.CurrentBlock().Number()) && args.GasPremium == nil || args.FeeCap == nil || args.GasPrice != nil { - return nil, 0, false, fmt.Errorf("after block %d EIP1559 is finalized and transactions must contain a GasPremium and FeeCap and not contain a GasPrice", b.ChainConfig().EIP1559FinalizedBlock.Uint64()) + if b.ChainConfig().IsEIP1559Finalized(b.CurrentBlock().Number()) && (args.GasPremium == nil || args.FeeCap == nil || args.GasPrice != nil) { + return nil, 0, false, core.ErrTxNotEIP1559 } - if !b.ChainConfig().IsEIP1559(b.CurrentBlock().Number()) && args.GasPremium != nil || args.FeeCap != nil || args.GasPrice == nil { - return nil, 0, false, fmt.Errorf("before block %d EIP1559 is not activated and transactions must contain a GasPrice and not contain a GasPremium or FeeCap", b.ChainConfig().EIP1559Block.Uint64()) + if !b.ChainConfig().IsEIP1559(b.CurrentBlock().Number()) && (args.GasPremium != nil || args.FeeCap != nil || args.GasPrice == nil) { + return nil, 0, false, core.ErrTxIsEIP1559 } if args.GasPrice != nil && (args.GasPremium != nil || args.FeeCap != nil) { - return nil, 0, false, errors.New("if GasPrice is set, GasPremium and FeeCap must not be set") + return nil, 0, false, core.ErrTxSetsLegacyAndEIP1559Fields } if args.FeeCap != nil && args.GasPremium == nil { return nil, 0, false, errors.New("if FeeCap is set, GasPremium must be set") @@ -1395,14 +1395,14 @@ type SendTxArgs struct { // setDefaults is a helper function that fills in default values for unspecified tx fields. func (args *SendTxArgs) setDefaults(ctx context.Context, b Backend) error { // EIP1559 guards - if b.ChainConfig().IsEIP1559Finalized(b.CurrentBlock().Number()) && args.GasPremium == nil || args.FeeCap == nil || args.GasPrice != nil { - return fmt.Errorf("after block %d EIP1559 is finalized and transactions must contain a GasPremium and FeeCap and not contain a GasPrice", b.ChainConfig().EIP1559FinalizedBlock.Uint64()) + if b.ChainConfig().IsEIP1559Finalized(b.CurrentBlock().Number()) && (args.GasPremium == nil || args.FeeCap == nil || args.GasPrice != nil) { + return core.ErrTxNotEIP1559 } - if !b.ChainConfig().IsEIP1559(b.CurrentBlock().Number()) && args.GasPremium != nil || args.FeeCap != nil || args.GasPrice == nil { - return fmt.Errorf("before block %d EIP1559 is not activated and transactions must contain a GasPrice and not contain a GasPremium or FeeCap", b.ChainConfig().EIP1559Block.Uint64()) + if !b.ChainConfig().IsEIP1559(b.CurrentBlock().Number()) && (args.GasPremium != nil || args.FeeCap != nil || args.GasPrice == nil) { + return core.ErrTxIsEIP1559 } if args.GasPrice != nil && (args.GasPremium != nil || args.FeeCap != nil) { - return errors.New("if GasPrice is set, GasPremium and FeeCap must not be set") + return core.ErrTxSetsLegacyAndEIP1559Fields } if args.FeeCap != nil && args.GasPremium == nil { return errors.New("if FeeCap is set, GasPremium must be set") @@ -1559,20 +1559,17 @@ func (s *PublicTransactionPoolAPI) SendRawTransaction(ctx context.Context, encod return common.Hash{}, err } // EIP1559 guards - if s.b.ChainConfig().IsEIP1559Finalized(s.b.CurrentBlock().Number()) && tx.GasPremium() == nil || tx.FeeCap() == nil || tx.GasPrice() != nil { - return common.Hash{}, fmt.Errorf("after block %d EIP1559 is finalized and transactions must contain a GasPremium and FeeCap and not contain a GasPrice", s.b.ChainConfig().EIP1559FinalizedBlock.Uint64()) + if s.b.ChainConfig().IsEIP1559Finalized(s.b.CurrentBlock().Number()) && (tx.GasPremium() == nil || tx.FeeCap() == nil || tx.GasPrice() != nil) { + return common.Hash{}, core.ErrTxNotEIP1559 } - if !s.b.ChainConfig().IsEIP1559(s.b.CurrentBlock().Number()) && tx.GasPremium() != nil || tx.FeeCap() != nil || tx.GasPrice() == nil { - return common.Hash{}, fmt.Errorf("before block %d EIP1559 is not activated and transactions must contain a GasPrice and not contain a GasPremium or FeeCap", s.b.ChainConfig().EIP1559Block.Uint64()) + if !s.b.ChainConfig().IsEIP1559(s.b.CurrentBlock().Number()) && (tx.GasPremium() != nil || tx.FeeCap() != nil || tx.GasPrice() == nil) { + return common.Hash{}, core.ErrTxIsEIP1559 } if tx.GasPrice() != nil && (tx.GasPremium() != nil || tx.FeeCap() != nil) { - return common.Hash{}, errors.New("if GasPrice is set, GasPremium and FeeCap must not be set") + return common.Hash{}, core.ErrTxSetsLegacyAndEIP1559Fields } - if tx.FeeCap() != nil && tx.GasPremium() == nil { - return common.Hash{}, errors.New("if FeeCap is set, GasPremium must be set") - } - if tx.GasPremium() != nil && tx.FeeCap() == nil { - return common.Hash{}, errors.New("if GasPremium is set, FeeCap must be set") + if tx.GasPrice() == nil && (tx.GasPremium() == nil || tx.FeeCap() == nil) { + return common.Hash{}, core.ErrMissingGasFields } return SubmitTransaction(ctx, s.b, tx) }