From 28e87892b86723d52954455bb0260068b98619d6 Mon Sep 17 00:00:00 2001 From: Roberto Bayardo Date: Fri, 7 Oct 2022 09:18:00 -0700 Subject: [PATCH] handle data gas / data gas fees appropriately (#26) --- accounts/abi/bind/backends/simulated.go | 1 + consensus/misc/eip4844.go | 5 ++ core/blockchain_test.go | 2 + core/error.go | 4 ++ core/state_transition.go | 73 ++++++++++++++++++------- core/tx_pool.go | 2 +- core/types/data_blob.go | 2 +- core/types/transaction.go | 17 +++++- light/txpool.go | 3 +- params/protocol_params.go | 22 +++----- tests/kzg_bench_test.go | 6 +- 11 files changed, 94 insertions(+), 43 deletions(-) diff --git a/accounts/abi/bind/backends/simulated.go b/accounts/abi/bind/backends/simulated.go index 118c736c4886..5864cbc0df9a 100644 --- a/accounts/abi/bind/backends/simulated.go +++ b/accounts/abi/bind/backends/simulated.go @@ -819,6 +819,7 @@ func (m callMsg) To() *common.Address { return m.CallMsg.To } func (m callMsg) GasPrice() *big.Int { return m.CallMsg.GasPrice } func (m callMsg) GasFeeCap() *big.Int { return m.CallMsg.GasFeeCap } func (m callMsg) GasTipCap() *big.Int { return m.CallMsg.GasTipCap } +func (m callMsg) MaxFeePerDataGas() *big.Int { return m.CallMsg.MaxFeePerDataGas } func (m callMsg) Gas() uint64 { return m.CallMsg.Gas } func (m callMsg) Value() *big.Int { return m.CallMsg.Value } func (m callMsg) Data() []byte { return m.CallMsg.Data } diff --git a/consensus/misc/eip4844.go b/consensus/misc/eip4844.go index 0d101cef04b1..1bc27225738d 100644 --- a/consensus/misc/eip4844.go +++ b/consensus/misc/eip4844.go @@ -71,3 +71,8 @@ func VerifyEip4844Header(config *params.ChainConfig, parent, header *types.Heade } return nil } + +// GetDataGasPrice implements get_data_gas_price from EIP-4844 +func GetDataGasPrice(excessDataGas *big.Int) *big.Int { + return FakeExponential(big.NewInt(params.MinDataGasPrice), excessDataGas, big.NewInt(params.DataGasPriceUpdateFraction)) +} diff --git a/core/blockchain_test.go b/core/blockchain_test.go index 2b37c1309d98..0818b60267e5 100644 --- a/core/blockchain_test.go +++ b/core/blockchain_test.go @@ -3923,6 +3923,8 @@ func TestDataBlobTxs(t *testing.T) { msg.Nonce = view.Uint64View(0) msg.GasFeeCap.SetFromBig(newGwei(5)) msg.GasTipCap.SetFromBig(big.NewInt(2)) + msg.MaxFeePerDataGas.SetFromBig(big.NewInt(params.MinDataGasPrice)) + // TODO: Add test case for max data fee too low msg.BlobVersionedHashes = []common.Hash{one, two} txdata := &types.SignedBlobTx{Message: msg} diff --git a/core/error.go b/core/error.go index 51ebefc137bc..f06d661fdcde 100644 --- a/core/error.go +++ b/core/error.go @@ -82,6 +82,10 @@ var ( // transaction with a tip higher than the total fee cap. ErrTipAboveFeeCap = errors.New("max priority fee per gas higher than max fee per gas") + // ErrMaxFeePerDataGas is returned if the transaction specified a + // max_fee_per_data_gas that is below the current data gas price. + ErrMaxFeePerDataGas = errors.New("max data fee per gas too low") + // ErrTipVeryHigh is a sanity error to avoid extremely big numbers specified // in the tip field. ErrTipVeryHigh = errors.New("max priority fee per gas higher than 2^256-1") diff --git a/core/state_transition.go b/core/state_transition.go index 2e646abbb41c..c54facdb2f19 100644 --- a/core/state_transition.go +++ b/core/state_transition.go @@ -23,7 +23,7 @@ import ( "github.com/ethereum/go-ethereum/common" cmath "github.com/ethereum/go-ethereum/common/math" - // "github.com/ethereum/go-ethereum/consensus/misc" + "github.com/ethereum/go-ethereum/consensus/misc" "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/core/vm" "github.com/ethereum/go-ethereum/crypto" @@ -72,6 +72,7 @@ type Message interface { GasPrice() *big.Int GasFeeCap() *big.Int GasTipCap() *big.Int + MaxFeePerDataGas() *big.Int Gas() uint64 Value() *big.Int @@ -162,25 +163,22 @@ func IntrinsicGas(data []byte, accessList types.AccessList, isContractCreation b gas += uint64(len(accessList)) * params.TxAccessListAddressGas gas += uint64(accessList.StorageKeys()) * params.TxAccessListStorageKeyGas } - // TODO - //if rules.EIP4844 { - //gas += uint64(blobCount) * getBlobGas(blockExcessBlobs) - //} return gas, nil } // NewStateTransition initialises and returns a new state transition object. func NewStateTransition(evm *vm.EVM, msg Message, gp *GasPool) *StateTransition { return &StateTransition{ - gp: gp, - evm: evm, - msg: msg, - gasPrice: msg.GasPrice(), - gasFeeCap: msg.GasFeeCap(), - gasTipCap: msg.GasTipCap(), - value: msg.Value(), - data: msg.Data(), - state: evm.StateDB, + gp: gp, + evm: evm, + msg: msg, + gasPrice: msg.GasPrice(), + gasFeeCap: msg.GasFeeCap(), + gasTipCap: msg.GasTipCap(), + maxFeePerDataGas: msg.MaxFeePerDataGas(), + value: msg.Value(), + data: msg.Data(), + state: evm.StateDB, } } @@ -206,12 +204,25 @@ func (st *StateTransition) to() common.Address { func (st *StateTransition) buyGas() error { mgval := new(big.Int).SetUint64(st.msg.Gas()) mgval = mgval.Mul(mgval, st.gasPrice) - balanceCheck := mgval - if st.gasFeeCap != nil { - balanceCheck = new(big.Int).SetUint64(st.msg.Gas()) - balanceCheck = balanceCheck.Mul(balanceCheck, st.gasFeeCap) - balanceCheck.Add(balanceCheck, st.value) + + dgval := new(big.Int) + if st.evm.ChainConfig().IsSharding(st.evm.Context.BlockNumber) { + // add in fee for eip-4844 data blobs if any + dgval.Mul(misc.GetDataGasPrice(st.evm.Context.ExcessDataGas), st.dataGasUsed()) + } + + balanceCheck := new(big.Int) + if st.gasFeeCap == nil { + balanceCheck.Set(mgval) + } else { + balanceCheck.Add(st.value, dgval) + // EIP-1559 mandates that the sender has enough balance to cover not just actual fee but + // the max gas fee, so we compute this upper bound rather than use mgval here. + maxGasFee := new(big.Int).SetUint64(st.msg.Gas()) + maxGasFee.Mul(maxGasFee, st.gasFeeCap) + balanceCheck.Add(balanceCheck, maxGasFee) } + if have, want := st.state.GetBalance(st.msg.From()), balanceCheck; have.Cmp(want) < 0 { return fmt.Errorf("%w: address %v have %v want %v", ErrInsufficientFunds, st.msg.From().Hex(), have, want) } @@ -219,8 +230,9 @@ func (st *StateTransition) buyGas() error { return err } st.gas += st.msg.Gas() - st.initialGas = st.msg.Gas() + + mgval.Add(mgval, dgval) // both regular gas fee and data gas fee need to be deducted st.state.SubBalance(st.msg.From(), mgval) return nil } @@ -270,6 +282,13 @@ func (st *StateTransition) preCheck() error { } } } + if st.evm.ChainConfig().IsSharding(st.evm.Context.BlockNumber) { + dataGasPrice := misc.GetDataGasPrice(st.evm.Context.ExcessDataGas) + if dataGasPrice.Cmp(st.maxFeePerDataGas) > 0 { + return fmt.Errorf("%w: address %v, maxFeePerDataGas: %v dataGasPrice: %v", ErrMaxFeePerDataGas, + st.msg.From().Hex(), st.maxFeePerDataGas, dataGasPrice) + } + } return st.buyGas() } @@ -291,7 +310,9 @@ func (st *StateTransition) TransitionDb() (*ExecutionResult, error) { // applying the message. The rules include these clauses // // 1. the nonce of the message caller is correct - // 2. caller has enough balance to cover transaction fee(gaslimit * gasprice) + // 2. caller has enough balance to cover: + // Legacy tx: fee(gaslimit * gasprice) + // EIP-1559 tx: tx.value + max-fee(gaslimit * gascap + datagas * datagasprice) // 3. the amount of gas required is available in the block // 4. the purchased gas is enough to cover intrinsic usage // 5. there is no overflow when calculating intrinsic gas @@ -402,3 +423,13 @@ func (st *StateTransition) refundGas(refundQuotient uint64) { func (st *StateTransition) gasUsed() uint64 { return st.initialGas - st.gas } + +func (st *StateTransition) dataGasUsed() *big.Int { + dataGas := new(big.Int) + l := int64(len(st.msg.DataHashes())) + if l != 0 { + dataGas.SetInt64(l) + dataGas.Mul(dataGas, big.NewInt(params.DataGasPerBlob)) + } + return dataGas +} diff --git a/core/tx_pool.go b/core/tx_pool.go index 2d0e159524d6..19e4adacb744 100644 --- a/core/tx_pool.go +++ b/core/tx_pool.go @@ -54,7 +54,7 @@ const ( // txWrapDataMax is the maximum size for the additional wrapper data, // enough to encode a blob-transaction wrapper data (48 bytes for commitment, 4 for offset, 48 for a commitment) - txWrapDataMax = 4 + 4 + params.MaxBlobsPerTx*(params.FieldElementsPerBlob*32+48) + txWrapDataMax = 4 + 4 + params.MaxBlobsPerBlock*(params.FieldElementsPerBlob*32+48) ) var ( diff --git a/core/types/data_blob.go b/core/types/data_blob.go index 5892f5287304..adf71b27c9a8 100644 --- a/core/types/data_blob.go +++ b/core/types/data_blob.go @@ -481,7 +481,7 @@ func (b *BlobTxWrapData) verifyVersionedHash(inner TxData) error { if !ok { return fmt.Errorf("expected signed blob tx, got %T", inner) } - if a, b := len(blobTx.Message.BlobVersionedHashes), params.MaxBlobsPerTx; a > b { + if a, b := len(blobTx.Message.BlobVersionedHashes), params.MaxBlobsPerBlock; a > b { return fmt.Errorf("too many blobs in blob tx, got %d, expected no more than %d", a, b) } if a, b := len(b.BlobKzgs), len(b.Blobs); a != b { diff --git a/core/types/transaction.go b/core/types/transaction.go index 0341e058d599..388a20cecee1 100644 --- a/core/types/transaction.go +++ b/core/types/transaction.go @@ -30,6 +30,7 @@ import ( "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/math" "github.com/ethereum/go-ethereum/crypto" + "github.com/ethereum/go-ethereum/params" "github.com/ethereum/go-ethereum/rlp" ) @@ -403,13 +404,27 @@ func (tx *Transaction) To() *common.Address { return copyAddressPtr(tx.inner.to()) } -// Cost returns gas * gasPrice + value. +// Cost returns (gas * gasPrice) + (DataGas() * maxDataFeePerGas) + value. func (tx *Transaction) Cost() *big.Int { total := new(big.Int).Mul(tx.GasPrice(), new(big.Int).SetUint64(tx.Gas())) total.Add(total, tx.Value()) + dataGasFee := tx.DataGas() + dataGasFee.Mul(dataGasFee, tx.MaxFeePerDataGas()) + total.Add(total, dataGasFee) return total } +// DataGas implements get_total_data_gas from EIP-4844 +func (tx *Transaction) DataGas() *big.Int { + r := new(big.Int) + l := int64(len(tx.DataHashes())) + if l != 0 { + r.SetInt64(l) + r.Mul(r, big.NewInt(params.DataGasPerBlob)) + } + return r +} + // RawSignatureValues returns the V, R, S signature values of the transaction. // The return values should not be modified by the caller. func (tx *Transaction) RawSignatureValues() (v, r, s *big.Int) { diff --git a/light/txpool.go b/light/txpool.go index 3ade8f074479..469944df687d 100644 --- a/light/txpool.go +++ b/light/txpool.go @@ -378,7 +378,7 @@ func (pool *TxPool) validateTx(ctx context.Context, tx *types.Transaction) error } // Transactor should have enough funds to cover the costs - // cost == V + GP * GL + // cost == V + GP * GL + DGP * DG if b := currentState.GetBalance(from); b.Cmp(tx.Cost()) < 0 { return core.ErrInsufficientFunds } @@ -389,7 +389,6 @@ func (pool *TxPool) validateTx(ctx context.Context, tx *types.Transaction) error EIP2028: pool.istanbul, EIP4844: pool.eip4844, } - // TODO: Check DataGas gas, err := core.IntrinsicGas(tx.Data(), tx.AccessList(), tx.To() == nil, rules) if err != nil { return err diff --git a/params/protocol_params.go b/params/protocol_params.go index 6e0c0dfc93b2..6709b4215585 100644 --- a/params/protocol_params.go +++ b/params/protocol_params.go @@ -158,20 +158,14 @@ const ( RefundQuotient uint64 = 2 RefundQuotientEIP3529 uint64 = 5 - // Fixed cost for sending a data blob. - BlobGas uint64 = 120000 - - MaxDataGasPerBlock = 1 << 21 - DataGasPerBlob = 1 << 17 - MaxDataBlobsPerBlock = MaxDataGasPerBlock / DataGasPerBlob - - TargetDataGasPerBlock = 1 << 20 - - MaxBlobsPerTx = 2 - MaxBlobsPerBlock = 16 - TargetBlobsPerBlock = 8 - FieldElementsPerBlob = 4096 // each field element is 32 bytes - GasPriceUpdateFractionPerBlob = 64 + // stuff from EIP-4844 + FieldElementsPerBlob = 4096 // each field element is 32 bytes + MaxDataGasPerBlock = 1 << 21 + DataGasPerBlob = 1 << 17 + TargetDataGasPerBlock = 1 << 20 + MinDataGasPrice = 10e8 + DataGasPriceUpdateFraction = 8902606 + MaxBlobsPerBlock = MaxDataGasPerBlock / DataGasPerBlob BlobVerificationGas uint64 = 1800000 BlobCommitmentVersionKZG uint8 = 0x01 diff --git a/tests/kzg_bench_test.go b/tests/kzg_bench_test.go index 24ba6e991c90..3295d35fdc83 100644 --- a/tests/kzg_bench_test.go +++ b/tests/kzg_bench_test.go @@ -46,7 +46,7 @@ func BenchmarkVerifyBlobsWithoutKZGProof(b *testing.B) { } func BenchmarkVerifyBlobs(b *testing.B) { - blobs := make([]types.Blob, params.MaxBlobsPerTx) + blobs := make([]types.Blob, params.MaxBlobsPerBlock) var commitments []types.KZGCommitment var hashes []common.Hash for i := 0; i < len(blobs); i++ { @@ -139,7 +139,7 @@ func BenchmarkVerifyMultiple(b *testing.B) { var blobs []types.Blob var commitments []types.KZGCommitment var hashes []common.Hash - for i := 0; i < params.MaxBlobsPerTx; i++ { + for i := 0; i < params.MaxBlobsPerBlock; i++ { var blobElements types.Blob blob := randomBlob() for j := range blob { @@ -211,7 +211,7 @@ func BenchmarkBatchVerifyWithoutKZGProofs(b *testing.B) { for i := 0; i < siz; i++ { var blobs [][]bls.Fr var commitments []*bls.G1Point - for i := 0; i < params.MaxBlobsPerTx; i++ { + for i := 0; i < params.MaxBlobsPerBlock; i++ { blob := randomBlob() blobs = append(blobs, blob) commitments = append(commitments, kzg.BlobToKzg(blob))