diff --git a/core/state_transition.go b/core/state_transition.go index d285d03fe245..1bd1ef6b37b3 100644 --- a/core/state_transition.go +++ b/core/state_transition.go @@ -22,7 +22,6 @@ import ( "math/big" "github.com/ethereum/go-ethereum/common" - cmath "github.com/ethereum/go-ethereum/common/math" "github.com/ethereum/go-ethereum/core/tracing" "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/core/vm" @@ -149,14 +148,24 @@ type Message struct { // When SkipFromEOACheck is true, the message sender is not checked to be an EOA. SkipFromEOACheck bool + + //[rollup-geth] + EffectiveGasTip *big.Int + EffectiveGasPrices types.VectorFeeBigint + EffectiveGasTips types.VectorFeeBigint + + GasLimits types.VectorGasLimit + GasFeeCaps types.VectorFeeBigint + GasTipCaps types.VectorFeeBigint } +// TODO: go through all the references of this function call and see where we have to update to our newly added TransactionToMessageEIP7706 + // TransactionToMessage converts a transaction into a Message. func TransactionToMessage(tx *types.Transaction, s types.Signer, baseFee *big.Int) (*Message, error) { msg := &Message{ Nonce: tx.Nonce(), GasLimit: tx.Gas(), - GasPrice: new(big.Int).Set(tx.GasPrice()), GasFeeCap: new(big.Int).Set(tx.GasFeeCap()), GasTipCap: new(big.Int).Set(tx.GasTipCap()), To: tx.To(), @@ -167,11 +176,15 @@ func TransactionToMessage(tx *types.Transaction, s types.Signer, baseFee *big.In SkipFromEOACheck: false, BlobHashes: tx.BlobHashes(), BlobGasFeeCap: tx.BlobGasFeeCap(), + + //[rollup-geth] + GasFeeCaps: tx.GasFeeCaps(), + GasTipCaps: tx.GasTipCaps(), + GasLimits: tx.GasLimits(), + EffectiveGasTip: tx.EffectiveGasTipValue(baseFee), + GasPrice: tx.EffectiveGasPrice(baseFee), //[rollup-geth] } - // If baseFee provided, set gasPrice to effectiveGasPrice. - if baseFee != nil { - msg.GasPrice = cmath.BigMin(msg.GasPrice.Add(msg.GasTipCap, baseFee), msg.GasFeeCap) - } + var err error msg.From, err = types.Sender(s, tx) return msg, err @@ -237,50 +250,6 @@ func (st *StateTransition) to() common.Address { return *st.msg.To } -func (st *StateTransition) buyGas() error { - mgval := new(big.Int).SetUint64(st.msg.GasLimit) - mgval.Mul(mgval, st.msg.GasPrice) - balanceCheck := new(big.Int).Set(mgval) - if st.msg.GasFeeCap != nil { - balanceCheck.SetUint64(st.msg.GasLimit) - balanceCheck = balanceCheck.Mul(balanceCheck, st.msg.GasFeeCap) - } - balanceCheck.Add(balanceCheck, st.msg.Value) - - if st.evm.ChainConfig().IsCancun(st.evm.Context.BlockNumber, st.evm.Context.Time) { - if blobGas := st.blobGasUsed(); blobGas > 0 { - // Check that the user has enough funds to cover blobGasUsed * tx.BlobGasFeeCap - blobBalanceCheck := new(big.Int).SetUint64(blobGas) - blobBalanceCheck.Mul(blobBalanceCheck, st.msg.BlobGasFeeCap) - balanceCheck.Add(balanceCheck, blobBalanceCheck) - // Pay for blobGasUsed * actual blob fee - blobFee := new(big.Int).SetUint64(blobGas) - blobFee.Mul(blobFee, st.evm.Context.BlobBaseFee) - mgval.Add(mgval, blobFee) - } - } - balanceCheckU256, overflow := uint256.FromBig(balanceCheck) - if overflow { - return fmt.Errorf("%w: address %v required balance exceeds 256 bits", ErrInsufficientFunds, st.msg.From.Hex()) - } - if have, want := st.state.GetBalance(st.msg.From), balanceCheckU256; have.Cmp(want) < 0 { - return fmt.Errorf("%w: address %v have %v want %v", ErrInsufficientFunds, st.msg.From.Hex(), have, want) - } - if err := st.gp.SubGas(st.msg.GasLimit); err != nil { - return err - } - - if st.evm.Config.Tracer != nil && st.evm.Config.Tracer.OnGasChange != nil { - st.evm.Config.Tracer.OnGasChange(0, st.msg.GasLimit, tracing.GasChangeTxInitialBalance) - } - st.gasRemaining = st.msg.GasLimit - - st.initialGas = st.msg.GasLimit - mgvalU256, _ := uint256.FromBig(mgval) - st.state.SubBalance(st.msg.From, mgvalU256, tracing.BalanceDecreaseGasBuy) - return nil -} - func (st *StateTransition) preCheck() error { // Only check transactions that are not fake msg := st.msg @@ -306,31 +275,12 @@ func (st *StateTransition) preCheck() error { msg.From.Hex(), codeHash) } } - // Make sure that transaction gasFeeCap is greater than the baseFee (post london) - if st.evm.ChainConfig().IsLondon(st.evm.Context.BlockNumber) { - // Skip the checks if gas fields are zero and baseFee was explicitly disabled (eth_call) - skipCheck := st.evm.Config.NoBaseFee && msg.GasFeeCap.BitLen() == 0 && msg.GasTipCap.BitLen() == 0 - if !skipCheck { - if l := msg.GasFeeCap.BitLen(); l > 256 { - return fmt.Errorf("%w: address %v, maxFeePerGas bit length: %d", ErrFeeCapVeryHigh, - msg.From.Hex(), l) - } - if l := msg.GasTipCap.BitLen(); l > 256 { - return fmt.Errorf("%w: address %v, maxPriorityFeePerGas bit length: %d", ErrTipVeryHigh, - msg.From.Hex(), l) - } - if msg.GasFeeCap.Cmp(msg.GasTipCap) < 0 { - return fmt.Errorf("%w: address %v, maxPriorityFeePerGas: %s, maxFeePerGas: %s", ErrTipAboveFeeCap, - msg.From.Hex(), msg.GasTipCap, msg.GasFeeCap) - } - // This will panic if baseFee is nil, but basefee presence is verified - // as part of header validation. - if msg.GasFeeCap.Cmp(st.evm.Context.BaseFee) < 0 { - return fmt.Errorf("%w: address %v, maxFeePerGas: %s, baseFee: %s", ErrFeeCapTooLow, - msg.From.Hex(), msg.GasFeeCap, st.evm.Context.BaseFee) - } - } + + // [rollup-geth] + if preCheckGasErr := st.preCheckGas(); preCheckGasErr != nil { + return preCheckGasErr } + // Check the blob version validity if msg.BlobHashes != nil { // The to field of a blob tx type is mandatory, and a `BlobTx` transaction internally @@ -348,21 +298,6 @@ func (st *StateTransition) preCheck() error { } } } - // Check that the user is paying at least the current blob fee - if st.evm.ChainConfig().IsCancun(st.evm.Context.BlockNumber, st.evm.Context.Time) { - if st.blobGasUsed() > 0 { - // Skip the checks if gas fields are zero and blobBaseFee was explicitly disabled (eth_call) - skipCheck := st.evm.Config.NoBaseFee && msg.BlobGasFeeCap.BitLen() == 0 - if !skipCheck { - // This will panic if blobBaseFee is nil, but blobBaseFee presence - // is verified as part of header validation. - if msg.BlobGasFeeCap.Cmp(st.evm.Context.BlobBaseFee) < 0 { - return fmt.Errorf("%w: address %v blobGasFeeCap: %v, blobBaseFee: %v", ErrBlobFeeCapTooLow, - msg.From.Hex(), msg.BlobGasFeeCap, st.evm.Context.BlobBaseFee) - } - } - } - } return st.buyGas() } @@ -459,28 +394,12 @@ func (st *StateTransition) TransitionDb() (*ExecutionResult, error) { // After EIP-3529: refunds are capped to gasUsed / 5 gasRefund = st.refundGas(params.RefundQuotientEIP3529) } - effectiveTip := msg.GasPrice - if rules.IsLondon { - effectiveTip = cmath.BigMin(msg.GasTipCap, new(big.Int).Sub(msg.GasFeeCap, st.evm.Context.BaseFee)) - } - effectiveTipU256, _ := uint256.FromBig(effectiveTip) - 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) - - // add the coinbase to the witness iff the fee is greater than 0 - if rules.IsEIP4762 && fee.Sign() != 0 { - st.evm.AccessEvents.AddAccount(st.evm.Context.Coinbase, true) - } - } + // [rollup-geth] + st.payTheTip(rules, msg) return &ExecutionResult{ + // TODO: this should be vector [execution_gas, blobl_gas, calldata_gas] UsedGas: st.gasUsed(), RefundedGas: gasRefund, Err: vmerr, @@ -502,9 +421,8 @@ func (st *StateTransition) refundGas(refundQuotient uint64) uint64 { st.gasRemaining += refund // Return ETH for remaining gas, exchanged at the original rate. - remaining := uint256.NewInt(st.gasRemaining) - remaining.Mul(remaining, uint256.MustFromBig(st.msg.GasPrice)) - st.state.AddBalance(st.msg.From, remaining, tracing.BalanceIncreaseGasReturn) + // [rollup-geth] + st.refundGasToAddress() if st.evm.Config.Tracer != nil && st.evm.Config.Tracer.OnGasChange != nil && st.gasRemaining > 0 { st.evm.Config.Tracer.OnGasChange(st.gasRemaining, 0, tracing.GasChangeTxLeftOverReturned) diff --git a/core/state_transition_rollup.go b/core/state_transition_rollup.go new file mode 100644 index 000000000000..43159d332a7f --- /dev/null +++ b/core/state_transition_rollup.go @@ -0,0 +1,301 @@ +package core + +import ( + "fmt" + "math/big" + + // cmath "github.com/ethereum/go-ethereum/common/math" + "github.com/ethereum/go-ethereum/core/tracing" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/params" + "github.com/holiman/uint256" +) + +// TransactionToMessage converts a transaction into a Message post EIP-7706 +func TransactionToMessageEIP7706(tx *types.Transaction, s types.Signer, baseFees types.VectorFeeBigint) (*Message, error) { + msg := &Message{ + Nonce: tx.Nonce(), + GasLimit: tx.Gas(), + GasFeeCap: new(big.Int).Set(tx.GasFeeCap()), + GasTipCap: new(big.Int).Set(tx.GasTipCap()), + To: tx.To(), + Value: tx.Value(), + Data: tx.Data(), + AccessList: tx.AccessList(), + SkipNonceChecks: false, + SkipFromEOACheck: false, + BlobHashes: tx.BlobHashes(), + BlobGasFeeCap: tx.BlobGasFeeCap(), + + GasFeeCaps: tx.GasFeeCaps(), + GasTipCaps: tx.GasTipCaps(), + GasLimits: tx.GasLimits(), + EffectiveGasTips: tx.EffectiveGasTips(baseFees), + EffectiveGasPrices: tx.EffectiveGasPrices(baseFees), + } + + var err error + msg.From, err = types.Sender(s, tx) + return msg, err +} + +func (st *StateTransition) preCheckGas() error { + if st.evm.ChainConfig().IsR0() { + return st.preCheckGasEIP7706() + } + + return st.preCheckGasEIP4484() +} + +func (st *StateTransition) buyGas() error { + if !st.evm.ChainConfig().IsR0() { + return st.buyGasEIP7706() + } + + return st.buyGasEIP4844() +} + +func (st *StateTransition) refundGasToAddress() { + if st.evm.ChainConfig().IsR0() { + st.refundGasToAddressEIP7706() + } else { + st.refundGasToAddressEIP4844() + } +} + +func (st *StateTransition) payTheTip(rules params.Rules, msg *Message) { + if st.evm.ChainConfig().IsR0() { + st.payTheTipEIP7706(rules, msg) + } else { + st.payTheTipEIP4844(rules, msg) + } +} + +func (st *StateTransition) buyGasEIP4844() error { + mgval := new(big.Int).SetUint64(st.msg.GasLimit) + mgval.Mul(mgval, st.msg.GasPrice) + balanceCheck := new(big.Int).Set(mgval) + if st.msg.GasFeeCap != nil { + balanceCheck.SetUint64(st.msg.GasLimit) + balanceCheck = balanceCheck.Mul(balanceCheck, st.msg.GasFeeCap) + } + balanceCheck.Add(balanceCheck, st.msg.Value) + + if st.evm.ChainConfig().IsCancun(st.evm.Context.BlockNumber, st.evm.Context.Time) { + if blobGas := st.blobGasUsed(); blobGas > 0 { + // Check that the user has enough funds to cover blobGasUsed * tx.BlobGasFeeCap + blobBalanceCheck := new(big.Int).SetUint64(blobGas) + blobBalanceCheck.Mul(blobBalanceCheck, st.msg.BlobGasFeeCap) + balanceCheck.Add(balanceCheck, blobBalanceCheck) + // Pay for blobGasUsed * actual blob fee + blobFee := new(big.Int).SetUint64(blobGas) + blobFee.Mul(blobFee, st.evm.Context.BlobBaseFee) + mgval.Add(mgval, blobFee) + } + } + balanceCheckU256, overflow := uint256.FromBig(balanceCheck) + if overflow { + return fmt.Errorf("%w: address %v required balance exceeds 256 bits", ErrInsufficientFunds, st.msg.From.Hex()) + } + if have, want := st.state.GetBalance(st.msg.From), balanceCheckU256; have.Cmp(want) < 0 { + return fmt.Errorf("%w: address %v have %v want %v", ErrInsufficientFunds, st.msg.From.Hex(), have, want) + } + if err := st.gp.SubGas(st.msg.GasLimit); err != nil { + return err + } + + if st.evm.Config.Tracer != nil && st.evm.Config.Tracer.OnGasChange != nil { + st.evm.Config.Tracer.OnGasChange(0, st.msg.GasLimit, tracing.GasChangeTxInitialBalance) + } + st.gasRemaining = st.msg.GasLimit + + st.initialGas = st.msg.GasLimit + mgvalU256, _ := uint256.FromBig(mgval) + st.state.SubBalance(st.msg.From, mgvalU256, tracing.BalanceDecreaseGasBuy) + return nil +} + +func (st *StateTransition) buyGasEIP7706() error { + gasLimits := st.msg.GasLimits.ToVectorBigInt() + + // User should be able to cover GAS_LIMIT * MAX_FEE_PER_GAS + tx.value + maxGasFees := gasLimits.VectorMul(st.msg.GasFeeCaps) + balanceCheck := maxGasFees.Sum() + balanceCheck.Add(balanceCheck, st.msg.Value) + balanceCheckU256, overflow := uint256.FromBig(balanceCheck) + + if overflow { + return fmt.Errorf("%w: address %v required balance exceeds 256 bits", ErrInsufficientFunds, st.msg.From.Hex()) + } + if have, want := st.state.GetBalance(st.msg.From), balanceCheckU256; have.Cmp(want) < 0 { + return fmt.Errorf("%w: address %v have %v want %v", ErrInsufficientFunds, st.msg.From.Hex(), have, want) + } + + // NOTE: calculations below, which rely on msg.GasLimit, are still valid + // This is because per EIP-7706 will still have Gas(Limit) as TX field + // Which is in fact gas execution limit, so msg.GasLimits[0] == msg.GasLimit + if err := st.gp.SubGas(st.msg.GasLimit); err != nil { + return err + } + + if st.evm.Config.Tracer != nil && st.evm.Config.Tracer.OnGasChange != nil { + st.evm.Config.Tracer.OnGasChange(0, st.msg.GasLimit, tracing.GasChangeTxInitialBalance) + } + + st.gasRemaining = st.msg.GasLimit + st.initialGas = st.msg.GasLimit + + // GAS_LIMIT * ACTUAL_FEE_PER_GAS + totalGasFees := st.msg.EffectiveGasPrices.VectorMul(gasLimits).Sum() + totalGasFeesU256, _ := uint256.FromBig(totalGasFees) + + st.state.SubBalance(st.msg.From, totalGasFeesU256, tracing.BalanceDecreaseGasBuy) + return nil +} + +func (st *StateTransition) preCheckGasEIP4484() error { + msg := st.msg + // Make sure that transaction gasFeeCap is greater than the baseFee (post london) + if st.evm.ChainConfig().IsLondon(st.evm.Context.BlockNumber) { + // Skip the checks if gas fields are zero and baseFee was explicitly disabled (eth_call) + skipCheck := st.evm.Config.NoBaseFee && msg.GasFeeCap.BitLen() == 0 && msg.GasTipCap.BitLen() == 0 + if !skipCheck { + if l := msg.GasFeeCap.BitLen(); l > 256 { + return fmt.Errorf("%w: address %v, maxFeePerGas bit length: %d", ErrFeeCapVeryHigh, + msg.From.Hex(), l) + } + if l := msg.GasTipCap.BitLen(); l > 256 { + return fmt.Errorf("%w: address %v, maxPriorityFeePerGas bit length: %d", ErrTipVeryHigh, + msg.From.Hex(), l) + } + if msg.GasFeeCap.Cmp(msg.GasTipCap) < 0 { + return fmt.Errorf("%w: address %v, maxPriorityFeePerGas: %s, maxFeePerGas: %s", ErrTipAboveFeeCap, + msg.From.Hex(), msg.GasTipCap, msg.GasFeeCap) + } + // This will panic if baseFee is nil, but base fee presence is verified + // as part of header validation. + if msg.GasFeeCap.Cmp(st.evm.Context.BaseFee) < 0 { + return fmt.Errorf("%w: address %v, maxFeePerGas: %s, baseFee: %s", ErrFeeCapTooLow, + msg.From.Hex(), msg.GasFeeCap, st.evm.Context.BaseFee) + } + } + } + + // Check that the user is paying at least the current blob fee + if st.evm.ChainConfig().IsCancun(st.evm.Context.BlockNumber, st.evm.Context.Time) { + if st.blobGasUsed() > 0 { + // Skip the checks if gas fields are zero and blobBaseFee was explicitly disabled (eth_call) + skipCheck := st.evm.Config.NoBaseFee && msg.BlobGasFeeCap.BitLen() == 0 + if !skipCheck { + // This will panic if blobBaseFee is nil, but blobBaseFee presence + // is verified as part of header validation. + if msg.BlobGasFeeCap.Cmp(st.evm.Context.BlobBaseFee) < 0 { + return fmt.Errorf("%w: address %v blobGasFeeCap: %v, blobBaseFee: %v", ErrBlobFeeCapTooLow, + msg.From.Hex(), msg.BlobGasFeeCap, st.evm.Context.BlobBaseFee) + } + } + } + } + + return nil +} + +func (st *StateTransition) preCheckGasEIP7706() error { + if !st.evm.ChainConfig().IsR0() { + return nil + } + + msg := st.msg + // Skip the checks if gas fields are zero and baseFee was explicitly disabled (eth_call) + skipCheck := st.evm.Config.NoBaseFee && msg.GasFeeCaps.VecBitLenAllZero() && msg.GasTipCaps.VecBitLenAllZero() + if skipCheck { + return nil + } + + if !msg.GasFeeCaps.VecBitLenAllLessEqThan256() { + return fmt.Errorf("%w: address %v", ErrFeeCapVeryHigh, msg.From.Hex()) + } + if !msg.GasTipCaps.VecBitLenAllLessEqThan256() { + return fmt.Errorf("%w: address %v", ErrTipVeryHigh, msg.From.Hex()) + } + if !msg.GasTipCaps.VectorAllLessOrEqual(msg.GasFeeCaps) { + return fmt.Errorf("%w: address %v", ErrTipAboveFeeCap, msg.From.Hex()) + } + if !st.evm.Context.BaseFees.VectorAllLessOrEqual(msg.GasFeeCaps) { + return fmt.Errorf("%w: address %v", ErrFeeCapTooLow, msg.From.Hex()) + } + + return nil +} + +func (st *StateTransition) refundGasToAddressEIP4844() { + gasFeeToRefund := uint256.NewInt(st.gasRemaining) + gasFeeToRefund.Mul(gasFeeToRefund, uint256.MustFromBig(st.msg.GasPrice)) + st.state.AddBalance(st.msg.From, gasFeeToRefund, tracing.BalanceIncreaseGasReturn) +} + +func (st *StateTransition) refundGasToAddressEIP7706() { + gasToRefund := st.vectorGasRemaining().ToVectorBigInt() + gasFeeToRefund := gasToRefund.VectorMul(st.msg.EffectiveGasPrices).Sum() + + st.state.AddBalance(st.msg.From, uint256.MustFromBig(gasFeeToRefund), tracing.BalanceIncreaseGasReturn) +} + +func (st *StateTransition) payTheTipEIP4844(rules params.Rules, msg *Message) { + 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. + return + } + + effectiveTip := msg.EffectiveGasTip + effectiveTipU256, _ := uint256.FromBig(effectiveTip) + fee := new(uint256.Int).SetUint64(st.gasUsed()) + fee.Mul(fee, effectiveTipU256) + st.state.AddBalance(st.evm.Context.Coinbase, fee, tracing.BalanceIncreaseRewardTransactionFee) + + // add the coinbase to the witness iff the fee is greater than 0 + if rules.IsEIP4762 && fee.Sign() != 0 { + st.evm.AccessEvents.AddAccount(st.evm.Context.Coinbase, true) + } +} + +func (st *StateTransition) payTheTipEIP7706(rules params.Rules, msg *Message) { + if st.evm.Config.NoBaseFee && msg.GasTipCaps.VecBitLenAllZero() && msg.GasFeeCaps.VecBitLenAllZero() { + // 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. + return + } + + gasUsed := st.vectorGasUsed() + totalTip, _ := uint256.FromBig(msg.EffectiveGasTips.VectorMul(gasUsed.ToVectorBigInt()).Sum()) + st.state.AddBalance(st.evm.Context.Coinbase, totalTip, tracing.BalanceIncreaseRewardTransactionFee) + + // add the coinbase to the witness iff the fee is greater than 0 + if rules.IsEIP4762 && totalTip.Sign() != 0 { + st.evm.AccessEvents.AddAccount(st.evm.Context.Coinbase, true) + } +} + +func (st *StateTransition) vectorGasUsed() types.VectorGasLimit { + // TODO: think if this should be "precalculated", that is set where we update st.gasRemaining + // NOTE: Gas used by [execution, blob, calldata] + // Blob and calldata gas used is actually same as their gas limits (because it is precomputed from tx data and known upfront) + // Only execution gas is not known upfront and has to be determined while executing the transaction + gasUsed := st.msg.GasLimits + gasUsed[0] = st.gasUsed() + + return gasUsed +} + +func (st *StateTransition) vectorGasRemaining() types.VectorGasLimit { + //NOTE: Per EIP-7706: + //In practice, only the first term will be nonzero for now + //This is because gas used by blob and calldata can be calculated upfront so we don't have any remaining calldata/blob gas + + //NOTE: 2 msg.GasLimits[0] == msg.GasLimit == st.initialGas + // this is why this holds + return st.msg.GasLimits.VectorSubtract(st.vectorGasUsed()) +} diff --git a/core/types/transaction.go b/core/types/transaction.go index 6c8759ee69b6..917fe2d15df4 100644 --- a/core/types/transaction.go +++ b/core/types/transaction.go @@ -49,6 +49,8 @@ const ( AccessListTxType = 0x01 DynamicFeeTxType = 0x02 BlobTxType = 0x03 + //[rollup-geth] + VectorFeeTxType = 0x04 ) // Transaction is an Ethereum transaction. @@ -100,6 +102,11 @@ type TxData interface { encode(*bytes.Buffer) error decode([]byte) error + + //[rollup-geth] + gasTipCaps() VectorFeeBigint + gasFeeCaps() VectorFeeBigint + gasLimits() VectorGasLimit } // EncodeRLP implements rlp.Encoder @@ -206,6 +213,9 @@ func (tx *Transaction) decodeTyped(b []byte) (TxData, error) { inner = new(DynamicFeeTx) case BlobTxType: inner = new(BlobTx) + // [rollup-geth] + case VectorFeeTxType: + inner = new(VectorFeeTx) default: return nil, ErrTxTypeNotSupported } diff --git a/core/types/transaction_rollup.go b/core/types/transaction_rollup.go new file mode 100644 index 000000000000..f82a899a679c --- /dev/null +++ b/core/types/transaction_rollup.go @@ -0,0 +1,54 @@ +package types + +import ( + "math/big" + + "github.com/ethereum/go-ethereum/common/math" +) + +// Gas returns the gas limit of the transaction for each of [execution, blob, calldata] gas "type" respectively . +func (tx *Transaction) GasLimits() VectorGasLimit { return tx.inner.gasLimits() } + +// GasTipCaps returns the vector of tip caps per gas for each of [execution, blob, calldata] gas "types" respectively +func (tx *Transaction) GasTipCaps() VectorFeeBigint { return tx.inner.gasTipCaps() } + +// GasFeeCaps returns the vector of fee caps per gas for each of [execution, blob, calldata] gas "types" respectively +func (tx *Transaction) GasFeeCaps() VectorFeeBigint { return tx.inner.gasFeeCaps() } + +// EffectiveGasTip returns the effective miner gasTipCap for the given base fees, per gas for each of [execution, blob, calldata] gas "type" respectively . +func (tx *Transaction) EffectiveGasTips(baseFees VectorFeeBigint) VectorFeeBigint { + gasFeeCaps := tx.GasFeeCaps() + gasTipCaps := tx.GasTipCaps() + effectiveTips := NewVectorFeeBigInt() + for i, baseFee := range baseFees { + if baseFee == nil { + effectiveTips[i].Set(gasTipCaps[i]) + } else { + effectiveTips[i] = math.BigMin(gasTipCaps[i], effectiveTips[i].Sub(gasFeeCaps[i], baseFee)) + } + } + + return effectiveTips +} + +// EffectiveGasPrices returns the effective (actual) prices per gas for each of [execution, blob, calldata] gas "type" respectively . +func (tx *Transaction) EffectiveGasPrices(baseFees VectorFeeBigint) VectorFeeBigint { + gasFeeCaps := tx.GasFeeCaps() + gasTipCaps := tx.GasTipCaps() + effectiveFees := NewVectorFeeBigInt() + + for i, baseFee := range baseFees { + if baseFee == nil { + effectiveFees[i].Set(gasFeeCaps[i]) + } else { + effectiveFees[i] = math.BigMin(effectiveFees[i].Add(gasTipCaps[i], baseFee), gasFeeCaps[i]) + } + } + + return effectiveFees +} + +// EffectiveGasPrice returns the effective (actual) price per gas +func (tx *Transaction) EffectiveGasPrice(baseFee *big.Int) *big.Int { + return tx.inner.effectiveGasPrice(new(big.Int), baseFee) +} diff --git a/core/types/tx_blob.go b/core/types/tx_blob.go index ce1f287caaf8..e44d4f58972d 100644 --- a/core/types/tx_blob.go +++ b/core/types/tx_blob.go @@ -22,6 +22,7 @@ import ( "math/big" "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/common/math" "github.com/ethereum/go-ethereum/crypto/kzg4844" "github.com/ethereum/go-ethereum/params" "github.com/ethereum/go-ethereum/rlp" @@ -167,11 +168,9 @@ func (tx *BlobTx) effectiveGasPrice(dst *big.Int, baseFee *big.Int) *big.Int { if baseFee == nil { return dst.Set(tx.GasFeeCap.ToBig()) } - tip := dst.Sub(tx.GasFeeCap.ToBig(), baseFee) - if tip.Cmp(tx.GasTipCap.ToBig()) > 0 { - tip.Set(tx.GasTipCap.ToBig()) - } - return tip.Add(tip, baseFee) + + //[rollup-geth] + return dst.Set(math.BigMin(dst.Add(tx.GasTipCap.ToBig(), baseFee), tx.GasFeeCap.ToBig())) } func (tx *BlobTx) rawSignatureValues() (v, r, s *big.Int) { diff --git a/core/types/tx_dynamic_fee.go b/core/types/tx_dynamic_fee.go index 8b5b514fdec5..64eb6300abde 100644 --- a/core/types/tx_dynamic_fee.go +++ b/core/types/tx_dynamic_fee.go @@ -21,6 +21,7 @@ import ( "math/big" "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/common/math" "github.com/ethereum/go-ethereum/rlp" ) @@ -101,11 +102,9 @@ func (tx *DynamicFeeTx) effectiveGasPrice(dst *big.Int, baseFee *big.Int) *big.I if baseFee == nil { return dst.Set(tx.GasFeeCap) } - tip := dst.Sub(tx.GasFeeCap, baseFee) - if tip.Cmp(tx.GasTipCap) > 0 { - tip.Set(tx.GasTipCap) - } - return tip.Add(tip, baseFee) + + //[rollup-geth] + return dst.Set(math.BigMin(dst.Add(tx.GasTipCap, baseFee), tx.GasFeeCap)) } func (tx *DynamicFeeTx) rawSignatureValues() (v, r, s *big.Int) { diff --git a/core/types/tx_rollup.go b/core/types/tx_rollup.go new file mode 100644 index 000000000000..90f0c3d01967 --- /dev/null +++ b/core/types/tx_rollup.go @@ -0,0 +1,90 @@ +// Contains rollup-specific implementations for tx_legacy, tx_access_list, tx_dynamic_fee +// and tx_blob +package types + +import ( + "bytes" + "math/big" + + "github.com/ethereum/go-ethereum/params" +) + +func (tx *LegacyTx) calldataGas() uint64 { + zeroBytes := bytes.Count(tx.Data, []byte{0x00}) + nonZeroBytes := len(tx.Data) - zeroBytes + tokens := uint64(zeroBytes) + uint64(nonZeroBytes)*params.CalldataTokensPerNonZeroByte + + return tokens * params.CalldataGasPerToken +} + +func (tx *LegacyTx) gasLimits() VectorGasLimit { + return VectorGasLimit{tx.Gas, 0, tx.calldataGas()} +} + +func (tx *LegacyTx) gasTipCaps() VectorFeeBigint { + return VectorFeeBigint{tx.GasPrice, big.NewInt(0), tx.GasPrice} +} + +func (tx *LegacyTx) gasFeeCaps() VectorFeeBigint { + return VectorFeeBigint{tx.GasPrice, big.NewInt(0), tx.GasPrice} +} + +func (tx *AccessListTx) calldataGas() uint64 { + zeroBytes := bytes.Count(tx.Data, []byte{0x00}) + nonZeroBytes := len(tx.Data) - zeroBytes + tokens := uint64(zeroBytes) + uint64(nonZeroBytes)*params.CalldataTokensPerNonZeroByte + + return tokens * params.CalldataGasPerToken +} + +func (tx *AccessListTx) gasLimits() VectorGasLimit { + return VectorGasLimit{tx.Gas, 0, tx.calldataGas()} +} + +func (tx *AccessListTx) gasTipCaps() VectorFeeBigint { + return VectorFeeBigint{tx.GasPrice, big.NewInt(0), tx.GasPrice} +} + +func (tx *AccessListTx) gasFeeCaps() VectorFeeBigint { + return VectorFeeBigint{tx.GasPrice, big.NewInt(0), tx.GasPrice} +} + +func (tx *DynamicFeeTx) calldataGas() uint64 { + zeroBytes := bytes.Count(tx.Data, []byte{0x00}) + nonZeroBytes := len(tx.Data) - zeroBytes + tokens := uint64(zeroBytes) + uint64(nonZeroBytes)*params.CalldataTokensPerNonZeroByte + + return tokens * params.CalldataGasPerToken +} + +func (tx *DynamicFeeTx) gasLimits() VectorGasLimit { + return VectorGasLimit{tx.Gas, 0, tx.calldataGas()} +} + +func (tx *DynamicFeeTx) gasTipCaps() VectorFeeBigint { + return VectorFeeBigint{tx.GasTipCap, big.NewInt(0), tx.GasTipCap} +} + +func (tx *DynamicFeeTx) gasFeeCaps() VectorFeeBigint { + return VectorFeeBigint{tx.GasFeeCap, big.NewInt(0), tx.GasFeeCap} +} + +func (tx *BlobTx) calldataGas() uint64 { + zeroBytes := bytes.Count(tx.Data, []byte{0x00}) + nonZeroBytes := len(tx.Data) - zeroBytes + tokens := uint64(zeroBytes) + uint64(nonZeroBytes)*params.CalldataTokensPerNonZeroByte + + return tokens * params.CalldataGasPerToken +} + +func (tx *BlobTx) gasLimits() VectorGasLimit { + return VectorGasLimit{tx.Gas, tx.blobGas(), tx.calldataGas()} +} + +func (tx *BlobTx) gasTipCaps() VectorFeeBigint { + return VectorFeeBigint{tx.GasTipCap.ToBig(), big.NewInt(0), tx.GasTipCap.ToBig()} +} + +func (tx *BlobTx) gasFeeCaps() VectorFeeBigint { + return VectorFeeBigint{tx.GasFeeCap.ToBig(), tx.BlobFeeCap.ToBig(), tx.GasFeeCap.ToBig()} +} diff --git a/core/types/tx_vector_fee.go b/core/types/tx_vector_fee.go new file mode 100644 index 000000000000..fac9a26c43be --- /dev/null +++ b/core/types/tx_vector_fee.go @@ -0,0 +1,161 @@ +package types + +import ( + "bytes" + "math/big" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/crypto/kzg4844" + "github.com/ethereum/go-ethereum/params" + "github.com/ethereum/go-ethereum/rlp" + "github.com/holiman/uint256" +) + +type VectorFeeTx struct { + ChainID *uint256.Int + Nonce uint64 + Gas uint64 + To common.Address + Value *uint256.Int + Data []byte + AccessList AccessList + + GasTipCaps VectorFeeUint // a.k.a. maxPriorityFeePerGas for all 3 types: Execution, Blob and Calldata + GasFeeCaps VectorFeeUint // a.k.a. maxFeePerGas for all 3 types: Execution, Blob and Calldata + + BlobHashes []common.Hash + // A blob transaction can optionally contain blobs. This field must be set when BlobTx + // is used to create a transaction for signing. + Sidecar *BlobTxSidecar `rlp:"-"` + + // Signature values + V *uint256.Int `json:"v" gencodec:"required"` + R *uint256.Int `json:"r" gencodec:"required"` + S *uint256.Int `json:"s" gencodec:"required"` +} + +// accessors for innerTx. (satisfies TxData Interface) +func (tx *VectorFeeTx) txType() byte { return DynamicFeeTxType } +func (tx *VectorFeeTx) chainID() *big.Int { return tx.ChainID.ToBig() } +func (tx *VectorFeeTx) accessList() AccessList { return tx.AccessList } +func (tx *VectorFeeTx) data() []byte { return tx.Data } +func (tx *VectorFeeTx) gas() uint64 { return tx.Gas } + +func (tx *VectorFeeTx) gasLimits() VectorGasLimit { + return VectorGasLimit{tx.Gas, tx.blobGas(), tx.calldataGas()} +} + +func (tx *VectorFeeTx) gasTipCaps() VectorFeeBigint { + var feesAsBigInt VectorFeeBigint + for i, f := range tx.GasTipCaps { + feesAsBigInt[i] = f.ToBig() + } + + return feesAsBigInt +} + +func (tx *VectorFeeTx) gasFeeCaps() VectorFeeBigint { + var feesAsBigInt VectorFeeBigint + for i, f := range tx.GasFeeCaps { + feesAsBigInt[i] = f.ToBig() + } + + return feesAsBigInt +} + +func (tx *VectorFeeTx) value() *big.Int { return tx.Value.ToBig() } +func (tx *VectorFeeTx) nonce() uint64 { return tx.Nonce } +func (tx *VectorFeeTx) to() *common.Address { tmp := tx.To; return &tmp } + +func (tx *VectorFeeTx) rawSignatureValues() (v, r, s *big.Int) { + return tx.V.ToBig(), tx.R.ToBig(), tx.S.ToBig() +} + +func (tx *VectorFeeTx) setSignatureValues(chainID, v, r, s *big.Int) { + tx.ChainID.SetFromBig(chainID) + tx.V.SetFromBig(v) + tx.R.SetFromBig(r) + tx.S.SetFromBig(s) +} + +func (tx *VectorFeeTx) encode(b *bytes.Buffer) error { + return rlp.Encode(b, tx) +} + +func (tx *VectorFeeTx) decode(input []byte) error { + return rlp.DecodeBytes(input, tx) +} + +func (tx *VectorFeeTx) calldataGas() uint64 { + zeroBytes := bytes.Count(tx.Data, []byte{0x00}) + nonZeroBytes := len(tx.Data) - zeroBytes + tokens := uint64(zeroBytes) + uint64(nonZeroBytes)*params.CalldataTokensPerNonZeroByte + + return tokens * params.CalldataGasPerToken +} + +func (tx *VectorFeeTx) blobGas() uint64 { + return params.BlobTxBlobGasPerBlob * uint64(len(tx.BlobHashes)) +} + +// TODO: check if this is indeed proper implemenation +// NOTE: These methods are needed to satisfy TxData Interface +func (tx *VectorFeeTx) gasFeeCap() *big.Int { return nil } +func (tx *VectorFeeTx) gasTipCap() *big.Int { return nil } +func (tx *VectorFeeTx) gasPrice() *big.Int { return nil } +func (tx *VectorFeeTx) effectiveGasPrice(dst *big.Int, baseFee *big.Int) *big.Int { return nil } + +func (tx *VectorFeeTx) copy() TxData { + cpy := &VectorFeeTx{ + Nonce: tx.Nonce, + To: tx.To, + Data: common.CopyBytes(tx.Data), + Gas: tx.Gas, + // These are copied below. + AccessList: make(AccessList, len(tx.AccessList)), + BlobHashes: make([]common.Hash, len(tx.BlobHashes)), + Value: new(uint256.Int), + ChainID: new(uint256.Int), + GasTipCaps: VectorFeeUint{}, + GasFeeCaps: VectorFeeUint{}, + V: new(uint256.Int), + R: new(uint256.Int), + S: new(uint256.Int), + } + copy(cpy.AccessList, tx.AccessList) + copy(cpy.BlobHashes, tx.BlobHashes) + + if tx.Value != nil { + cpy.Value.Set(tx.Value) + } + if tx.ChainID != nil { + cpy.ChainID.Set(tx.ChainID) + } + + for i, v := range tx.GasTipCaps { + cpy.GasTipCaps[i].Set(v) + } + + for i, v := range tx.GasFeeCaps { + cpy.GasFeeCaps[i].Set(v) + } + + if tx.V != nil { + cpy.V.Set(tx.V) + } + if tx.R != nil { + cpy.R.Set(tx.R) + } + if tx.S != nil { + cpy.S.Set(tx.S) + } + if tx.Sidecar != nil { + cpy.Sidecar = &BlobTxSidecar{ + Blobs: append([]kzg4844.Blob(nil), tx.Sidecar.Blobs...), + Commitments: append([]kzg4844.Commitment(nil), tx.Sidecar.Commitments...), + Proofs: append([]kzg4844.Proof(nil), tx.Sidecar.Proofs...), + } + } + + return cpy +} diff --git a/core/types/vector_fee.go b/core/types/vector_fee.go new file mode 100644 index 000000000000..f235705d72fe --- /dev/null +++ b/core/types/vector_fee.go @@ -0,0 +1,120 @@ +package types + +import ( + "math/big" + + "github.com/holiman/uint256" +) + +type ( + VectorFeeUint [3]*uint256.Int + VectorFeeBigint [3]*big.Int + VectorGasLimit [3]uint64 +) + +func NewVectorFeeBigInt() VectorFeeBigint { + var result VectorFeeBigint + for i := range result { + result[i] = new(big.Int) + } + + return result +} + +// TODO: Add nil checks +func (vec VectorFeeBigint) Sum() *big.Int { + sum := big.NewInt(0) + for _, v := range vec { + sum = sum.Add(sum, v) + } + + return sum +} + +func (vec VectorFeeBigint) VectorAllLessOrEqual(other VectorFeeBigint) bool { + for i, v := range vec { + if v.Cmp(other[i]) > 0 { + return false + } + } + + return true +} + +func (vec VectorFeeBigint) VectorAdd(other VectorFeeBigint) VectorFeeBigint { + var result VectorFeeBigint + for i, v := range vec { + result[i] = new(big.Int).Add(v, other[i]) + } + + return result +} + +func (vec VectorFeeBigint) VectorMul(other VectorFeeBigint) VectorFeeBigint { + var result VectorFeeBigint + for i, v := range vec { + result[i] = new(big.Int).Mul(v, other[i]) + } + + return result +} + +func (vec VectorFeeBigint) VectorSubtract(other VectorFeeBigint) VectorFeeBigint { + var result VectorFeeBigint + for i, v := range vec { + result[i] = new(big.Int).Sub(v, other[i]) + } + + return result +} + +func (vec VectorFeeBigint) VectorSubtractClampAtZero(other VectorFeeBigint) VectorFeeBigint { + var result VectorFeeBigint + for i, v := range vec { + if subWontProducePositiveValue := v.Cmp(other[i]) <= 0; subWontProducePositiveValue { + result[i] = big.NewInt(0) + } else { + result[i] = new(big.Int).Sub(v, other[i]) + } + } + + return result +} + +func (vec VectorFeeBigint) VecBitLenAllZero() bool { + for _, v := range vec { + if v.BitLen() > 0 { + return false + } + } + + return true +} + +func (vec VectorFeeBigint) VecBitLenAllLessEqThan256() bool { + for _, v := range vec { + if v.BitLen() > 256 { + return false + } + } + + return true +} + +func (vec VectorGasLimit) ToVectorBigInt() VectorFeeBigint { + var result VectorFeeBigint + for i, v := range vec { + result[i] = new(big.Int).SetUint64(v) + } + + return result +} + +func (vec VectorGasLimit) VectorSubtract(other VectorGasLimit) VectorGasLimit { + var result VectorGasLimit + for i, v := range vec { + result[i] = v - other[i] + } + + return result +} diff --git a/core/vm/evm.go b/core/vm/evm.go index 616668d565cc..8ec5d81898e6 100644 --- a/core/vm/evm.go +++ b/core/vm/evm.go @@ -65,6 +65,10 @@ type BlockContext struct { BaseFee *big.Int // Provides information for BASEFEE (0 if vm runs with NoBaseFee flag and 0 gas price) BlobBaseFee *big.Int // Provides information for BLOBBASEFEE (0 if vm runs with NoBaseFee flag and 0 blob gas price) Random *common.Hash // Provides information for PREVRANDAO + + //[rollup-geth] + //TODO: where do we set this? + BaseFees types.VectorFeeBigint } // TxContext provides the EVM with information about a transaction. diff --git a/core/vm/interpreter.go b/core/vm/interpreter.go index 793f398367a7..ce40d1f43ada 100644 --- a/core/vm/interpreter.go +++ b/core/vm/interpreter.go @@ -29,7 +29,8 @@ import ( // Config are the configuration options for the Interpreter type Config struct { - Tracer *tracing.Hooks + Tracer *tracing.Hooks + // TODO: double-check if this needs to be vectorized as well? I guesss not since per comment flag is for 0 price calls 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 diff --git a/params/config.go b/params/config.go index 9ecf465bb67a..fa67e9db090e 100644 --- a/params/config.go +++ b/params/config.go @@ -561,6 +561,11 @@ func (c *ChainConfig) IsEIP4762(num *big.Int, time uint64) bool { return c.IsVerkle(num, time) } +// [rollup-geth] +func (c *ChainConfig) IsR0() bool { + return true +} + // CheckCompatible checks whether scheduled fork transitions have been imported // with a mismatching chain configuration. func (c *ChainConfig) CheckCompatible(newcfg *ChainConfig, height uint64, time uint64) *ConfigCompatError { @@ -894,6 +899,7 @@ type Rules struct { IsBerlin, IsLondon bool IsMerge, IsShanghai, IsCancun, IsPrague bool IsVerkle bool + IsR0 bool // [rollup-geth] } // Rules ensures c's ChainID is not nil. @@ -924,5 +930,6 @@ func (c *ChainConfig) Rules(num *big.Int, isMerge bool, timestamp uint64) Rules IsPrague: isMerge && c.IsPrague(num, timestamp), IsVerkle: isVerkle, IsEIP4762: isVerkle, + IsR0: true, // [rollup-geth] } } diff --git a/params/protocol_params.go b/params/protocol_params.go index 638f58a33992..f979011a6861 100644 --- a/params/protocol_params.go +++ b/params/protocol_params.go @@ -176,6 +176,9 @@ const ( MaxBlobGasPerBlock = 6 * BlobTxBlobGasPerBlob // Maximum consumable blob gas for data blobs per block HistoryServeWindow = 8192 // Number of blocks to serve historical block hashes for, EIP-2935. + + CalldataTokensPerNonZeroByte = 4 + CalldataGasPerToken = 4 ) // Gas discount table for BLS12-381 G1 and G2 multi exponentiation operations