diff --git a/consensus/misc/eip4844.go b/consensus/misc/eip4844.go index cfc95cc8b1b..768cc4b5b52 100644 --- a/consensus/misc/eip4844.go +++ b/consensus/misc/eip4844.go @@ -42,20 +42,6 @@ func CalcExcessDataGas(parentExcessDataGas *big.Int, newBlobs int) *big.Int { return new(big.Int).Set(excessDataGas.Sub(excessDataGas, targetGas)) } -// fakeExponential approximates factor * e ** (num / denom) using a taylor expansion -// as described in the EIP-4844 spec. -func fakeExponential(factor, num, denom *big.Int) *big.Int { - output := new(big.Int) - numAccum := new(big.Int).Mul(factor, denom) - for i := 1; numAccum.Sign() > 0; i++ { - output.Add(output, numAccum) - numAccum.Mul(numAccum, num) - iBig := big.NewInt(int64(i)) - numAccum.Div(numAccum, iBig.Mul(iBig, denom)) - } - return output.Div(output, denom) -} - // CountBlobs returns the number of blob transactions in txs func CountBlobs(txs []*types.Transaction) int { var count int @@ -101,11 +87,6 @@ func VerifyExcessDataGas(chainReader ChainReader, block *types.Block) error { 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)) -} - // ChainReader defines a small collection of methods needed to access the local // blockchain for EIP4844 block verifcation. type ChainReader interface { diff --git a/consensus/misc/eip4844_test.go b/consensus/misc/eip4844_test.go index 9004207b256..c4e2d23c3d7 100644 --- a/consensus/misc/eip4844_test.go +++ b/consensus/misc/eip4844_test.go @@ -23,40 +23,6 @@ import ( "github.com/ethereum/go-ethereum/params" ) -func TestFakeExponential(t *testing.T) { - var tests = []struct { - factor, num, denom int64 - want int64 - }{ - // When num==0 the return value should always equal the value of factor - {1, 0, 1, 1}, - {38493, 0, 1000, 38493}, - {0, 1234, 2345, 0}, // should be 0 - {1, 2, 1, 6}, // approximate 7.389 - {1, 4, 2, 6}, - {1, 3, 1, 16}, // approximate 20.09 - {1, 6, 2, 18}, - {1, 4, 1, 49}, // approximate 54.60 - {1, 8, 2, 50}, - {10, 8, 2, 542}, // approximate 540.598 - {11, 8, 2, 596}, // approximate 600.58 - {1, 5, 1, 136}, // approximate 148.4 - {1, 5, 2, 11}, // approximate 12.18 - {2, 5, 2, 23}, // approximate 24.36 - } - - for _, tt := range tests { - factor := big.NewInt(tt.factor) - num := big.NewInt(tt.num) - denom := big.NewInt(tt.denom) - result := fakeExponential(factor, num, denom) - //t.Logf("%v*e^(%v/%v): %v", factor, num, denom, result) - if tt.want != result.Int64() { - t.Errorf("got %v want %v", result, tt.want) - } - } -} - func TestCalcExcessDataGas(t *testing.T) { var tests = []struct { parentExcessDataGas int64 diff --git a/core/blockchain.go b/core/blockchain.go index d0eb82869db..39072dc1637 100644 --- a/core/blockchain.go +++ b/core/blockchain.go @@ -2050,7 +2050,7 @@ func (bc *BlockChain) recoverAncestors(block *types.Block) (common.Hash, error) // the processing of a block. These logs are later announced as deleted or reborn. func (bc *BlockChain) collectLogs(b *types.Block, removed bool) []*types.Log { receipts := rawdb.ReadRawReceipts(bc.db, b.Hash(), b.NumberU64()) - receipts.DeriveFields(bc.chainConfig, b.Hash(), b.NumberU64(), b.Time(), b.BaseFee(), b.Transactions()) + receipts.DeriveLogFields(b.Hash(), b.NumberU64(), b.Transactions()) var logs []*types.Log for _, receipt := range receipts { diff --git a/core/rawdb/accessors_chain.go b/core/rawdb/accessors_chain.go index 97e9ca0a903..fa788fd0061 100644 --- a/core/rawdb/accessors_chain.go +++ b/core/rawdb/accessors_chain.go @@ -638,12 +638,17 @@ func ReadReceipts(db ethdb.Reader, hash common.Hash, number uint64, config *para } header := ReadHeader(db, hash, number) var baseFee *big.Int + var parentExcessDataGas *big.Int if header == nil { baseFee = big.NewInt(0) } else { baseFee = header.BaseFee + parentHeader := ReadHeader(db, header.ParentHash, number-1) + if parentHeader != nil { + parentExcessDataGas = parentHeader.ExcessDataGas + } } - if err := receipts.DeriveFields(config, hash, number, header.Time, baseFee, body.Transactions); err != nil { + if err := receipts.DeriveFields(config, hash, number, header.Time, baseFee, parentExcessDataGas, body.Transactions); err != nil { log.Error("Failed to derive block receipts fields", "hash", hash, "number", number, "err", err) return nil } diff --git a/core/state_transition.go b/core/state_transition.go index ebbc513fb7c..8424be7d876 100644 --- a/core/state_transition.go +++ b/core/state_transition.go @@ -23,7 +23,6 @@ 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/core/types" "github.com/ethereum/go-ethereum/core/vm" "github.com/ethereum/go-ethereum/params" @@ -242,7 +241,7 @@ func (st *StateTransition) buyGas() error { if st.evm.Context.ExcessDataGas == nil { return fmt.Errorf("%w: sharding is active but ExcessDataGas is nil. Time: %v", ErrInternalFailure, st.evm.Context.Time) } - dgval.Mul(misc.GetDataGasPrice(st.evm.Context.ExcessDataGas), new(big.Int).SetUint64(dataGasUsed)) + dgval.Mul(types.GetDataGasPrice(st.evm.Context.ExcessDataGas), new(big.Int).SetUint64(dataGasUsed)) } // perform the required user balance checks @@ -325,7 +324,7 @@ func (st *StateTransition) preCheck() error { } } if st.dataGasUsed() > 0 && st.evm.ChainConfig().IsSharding(st.evm.Context.Time) { - dataGasPrice := misc.GetDataGasPrice(st.evm.Context.ExcessDataGas) + dataGasPrice := types.GetDataGasPrice(st.evm.Context.ExcessDataGas) if dataGasPrice.Cmp(st.msg.MaxFeePerDataGas) > 0 { return fmt.Errorf("%w: address %v, maxFeePerDataGas: %v dataGasPrice: %v, excessDataGas: %v", ErrMaxFeePerDataGas, @@ -466,5 +465,5 @@ func (st *StateTransition) gasUsed() uint64 { } func (st *StateTransition) dataGasUsed() uint64 { - return uint64(len(st.msg.DataHashes)) * params.DataGasPerBlob + return types.GetDataGasUsed(len(st.msg.DataHashes)) } diff --git a/core/types/data_blob_tx.go b/core/types/data_blob_tx.go index 19a7bc32164..a35fa981aba 100644 --- a/core/types/data_blob_tx.go +++ b/core/types/data_blob_tx.go @@ -6,11 +6,13 @@ import ( "fmt" "math/big" - "github.com/ethereum/go-ethereum/common" "github.com/holiman/uint256" "github.com/protolambda/ztyp/codec" "github.com/protolambda/ztyp/conv" . "github.com/protolambda/ztyp/view" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/params" ) type ECDSASignature struct { @@ -398,3 +400,31 @@ func (tx *SignedBlobTx) effectiveGasPrice(dst *big.Int, baseFee *big.Int) *big.I } return tip.Add(tip, baseFee) } + +// fakeExponential approximates factor * e ** (num / denom) using a taylor expansion +// as described in the EIP-4844 spec. +func fakeExponential(factor, num, denom *big.Int) *big.Int { + output := new(big.Int) + numAccum := new(big.Int).Mul(factor, denom) + for i := 1; numAccum.Sign() > 0; i++ { + output.Add(output, numAccum) + numAccum.Mul(numAccum, num) + iBig := big.NewInt(int64(i)) + numAccum.Div(numAccum, iBig.Mul(iBig, denom)) + } + return output.Div(output, denom) +} + +// GetDataGasPrice implements get_data_gas_price from EIP-4844 +func GetDataGasPrice(excessDataGas *big.Int) *big.Int { + if excessDataGas == nil { + return nil + } + return fakeExponential(big.NewInt(params.MinDataGasPrice), excessDataGas, big.NewInt(params.DataGasPriceUpdateFraction)) +} + +// GetDataGasUsed returns the amount of datagas consumed by a transaction with the specified number +// of blobs +func GetDataGasUsed(blobs int) uint64 { + return uint64(blobs) * params.DataGasPerBlob +} diff --git a/core/types/data_blob_tx_test.go b/core/types/data_blob_tx_test.go new file mode 100644 index 00000000000..a98322b9ff7 --- /dev/null +++ b/core/types/data_blob_tx_test.go @@ -0,0 +1,40 @@ +package types + +import ( + "math/big" + "testing" +) + +func TestFakeExponential(t *testing.T) { + var tests = []struct { + factor, num, denom int64 + want int64 + }{ + // When num==0 the return value should always equal the value of factor + {1, 0, 1, 1}, + {38493, 0, 1000, 38493}, + {0, 1234, 2345, 0}, // should be 0 + {1, 2, 1, 6}, // approximate 7.389 + {1, 4, 2, 6}, + {1, 3, 1, 16}, // approximate 20.09 + {1, 6, 2, 18}, + {1, 4, 1, 49}, // approximate 54.60 + {1, 8, 2, 50}, + {10, 8, 2, 542}, // approximate 540.598 + {11, 8, 2, 596}, // approximate 600.58 + {1, 5, 1, 136}, // approximate 148.4 + {1, 5, 2, 11}, // approximate 12.18 + {2, 5, 2, 23}, // approximate 24.36 + } + + for _, tt := range tests { + factor := big.NewInt(tt.factor) + num := big.NewInt(tt.num) + denom := big.NewInt(tt.denom) + result := fakeExponential(factor, num, denom) + //t.Logf("%v*e^(%v/%v): %v", factor, num, denom, result) + if tt.want != result.Int64() { + t.Errorf("got %v want %v", result, tt.want) + } + } +} diff --git a/core/types/gen_receipt_json.go b/core/types/gen_receipt_json.go index 8d85dd5b9c1..83133c8d13b 100644 --- a/core/types/gen_receipt_json.go +++ b/core/types/gen_receipt_json.go @@ -25,7 +25,9 @@ func (r Receipt) MarshalJSON() ([]byte, error) { TxHash common.Hash `json:"transactionHash" gencodec:"required"` ContractAddress common.Address `json:"contractAddress"` GasUsed hexutil.Uint64 `json:"gasUsed" gencodec:"required"` - EffectiveGasPrice *hexutil.Big `json:"effectiveGasPrice,omitempty"` + EffectiveGasPrice *big.Int `json:"effectiveGasPrice,omitempty"` + DataGasUsed uint64 `json:"dataGasUsed,omitempty"` + DataGasPrice *big.Int `json:"dataGasPrice,omitempty"` BlockHash common.Hash `json:"blockHash,omitempty"` BlockNumber *hexutil.Big `json:"blockNumber,omitempty"` TransactionIndex hexutil.Uint `json:"transactionIndex"` @@ -40,7 +42,9 @@ func (r Receipt) MarshalJSON() ([]byte, error) { enc.TxHash = r.TxHash enc.ContractAddress = r.ContractAddress enc.GasUsed = hexutil.Uint64(r.GasUsed) - enc.EffectiveGasPrice = (*hexutil.Big)(r.EffectiveGasPrice) + enc.EffectiveGasPrice = r.EffectiveGasPrice + enc.DataGasUsed = r.DataGasUsed + enc.DataGasPrice = r.DataGasPrice enc.BlockHash = r.BlockHash enc.BlockNumber = (*hexutil.Big)(r.BlockNumber) enc.TransactionIndex = hexutil.Uint(r.TransactionIndex) @@ -59,7 +63,9 @@ func (r *Receipt) UnmarshalJSON(input []byte) error { TxHash *common.Hash `json:"transactionHash" gencodec:"required"` ContractAddress *common.Address `json:"contractAddress"` GasUsed *hexutil.Uint64 `json:"gasUsed" gencodec:"required"` - EffectiveGasPrice *hexutil.Big `json:"effectiveGasPrice,omitempty"` + EffectiveGasPrice *big.Int `json:"effectiveGasPrice,omitempty"` + DataGasUsed *uint64 `json:"dataGasUsed,omitempty"` + DataGasPrice *big.Int `json:"dataGasPrice,omitempty"` BlockHash *common.Hash `json:"blockHash,omitempty"` BlockNumber *hexutil.Big `json:"blockNumber,omitempty"` TransactionIndex *hexutil.Uint `json:"transactionIndex"` @@ -101,7 +107,13 @@ func (r *Receipt) UnmarshalJSON(input []byte) error { } r.GasUsed = uint64(*dec.GasUsed) if dec.EffectiveGasPrice != nil { - r.EffectiveGasPrice = (*big.Int)(dec.EffectiveGasPrice) + r.EffectiveGasPrice = dec.EffectiveGasPrice + } + if dec.DataGasUsed != nil { + r.DataGasUsed = *dec.DataGasUsed + } + if dec.DataGasPrice != nil { + r.DataGasPrice = dec.DataGasPrice } if dec.BlockHash != nil { r.BlockHash = *dec.BlockHash diff --git a/core/types/receipt.go b/core/types/receipt.go index f18309b93c5..8c51bb4678b 100644 --- a/core/types/receipt.go +++ b/core/types/receipt.go @@ -59,10 +59,15 @@ type Receipt struct { Logs []*Log `json:"logs" gencodec:"required"` // Implementation fields: These fields are added by geth when processing a transaction. - TxHash common.Hash `json:"transactionHash" gencodec:"required"` - ContractAddress common.Address `json:"contractAddress"` - GasUsed uint64 `json:"gasUsed" gencodec:"required"` - EffectiveGasPrice *big.Int `json:"effectiveGasPrice"` + TxHash common.Hash `json:"transactionHash" gencodec:"required"` + ContractAddress common.Address `json:"contractAddress"` + GasUsed uint64 `json:"gasUsed" gencodec:"required"` + // TODO: EffectiveGasPrice should be a required field: + // https://github.com/ethereum/execution-apis/blob/af82a989bead35e2325ecc49a9023df39c548756/src/schemas/receipt.yaml#L49 + // Changing it to required is currently breaking cmd/evm/t8n_test.go, so leaving as omitempty for now. + EffectiveGasPrice *big.Int `json:"effectiveGasPrice,omitempty"` + DataGasUsed uint64 `json:"dataGasUsed,omitempty"` + DataGasPrice *big.Int `json:"dataGasPrice,omitempty"` // Inclusion information: These fields provide information about the inclusion of the // transaction corresponding to this receipt. @@ -318,10 +323,9 @@ func (rs Receipts) EncodeIndex(i int, w *bytes.Buffer) { // DeriveFields fills the receipts with their computed fields based on consensus // data and contextual infos like containing block and transactions. -func (rs Receipts) DeriveFields(config *params.ChainConfig, hash common.Hash, number uint64, time uint64, baseFee *big.Int, txs []*Transaction) error { +func (rs Receipts) DeriveFields(config *params.ChainConfig, hash common.Hash, number uint64, time uint64, baseFee *big.Int, parentExcessDataGas *big.Int, txs []*Transaction) error { signer := MakeSigner(config, new(big.Int).SetUint64(number), time) - logIndex := uint(0) if len(txs) != len(rs) { return errors.New("transaction and receipt count mismatch") } @@ -353,13 +357,30 @@ func (rs Receipts) DeriveFields(config *params.ChainConfig, hash common.Hash, nu rs[i].GasUsed = rs[i].CumulativeGasUsed - rs[i-1].CumulativeGasUsed } + // Set data gas fields for blob-containing txs + if len(txs[i].DataHashes()) > 0 { + rs[i].DataGasUsed = GetDataGasUsed(len(txs[i].DataHashes())) + rs[i].DataGasPrice = GetDataGasPrice(parentExcessDataGas) + } + } + return rs.DeriveLogFields(hash, number, txs) +} + +// DeriveLogFields fills the receipt logs with their computed fields based on consensus data and +// contextual infos like containing block and transactions. +func (rs Receipts) DeriveLogFields(hash common.Hash, number uint64, txs []*Transaction) error { + if len(txs) != len(rs) { + return errors.New("transaction and receipt count mismatch") + } + logIndex := uint(0) + for i, r := range rs { // The derived log fields can simply be set from the block and transaction - for j := 0; j < len(rs[i].Logs); j++ { - rs[i].Logs[j].BlockNumber = number - rs[i].Logs[j].BlockHash = hash - rs[i].Logs[j].TxHash = rs[i].TxHash - rs[i].Logs[j].TxIndex = uint(i) - rs[i].Logs[j].Index = logIndex + for _, l := range r.Logs { + l.BlockNumber = number + l.BlockHash = hash + l.TxHash = txs[i].Hash() + l.TxIndex = uint(i) + l.Index = logIndex logIndex++ } } diff --git a/core/types/receipt_test.go b/core/types/receipt_test.go index 03afbcb0c22..15a8c2ea9c1 100644 --- a/core/types/receipt_test.go +++ b/core/types/receipt_test.go @@ -24,6 +24,9 @@ import ( "reflect" "testing" + "github.com/holiman/uint256" + "github.com/protolambda/ztyp/view" + "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/params" "github.com/ethereum/go-ethereum/rlp" @@ -99,6 +102,20 @@ func TestDeriveFields(t *testing.T) { to3 := common.HexToAddress("0x3") to4 := common.HexToAddress("0x4") to5 := common.HexToAddress("0x5") + to6 := common.HexToAddress("0x6") + + blobTx := &SignedBlobTx{ + Message: BlobTxMessage{ + To: AddressOptionalSSZ{Address: (*AddressSSZ)(&to6)}, + Nonce: view.Uint64View(6), + Value: view.Uint256View(*uint256.NewInt(6)), + Gas: view.Uint64View(6), + GasTipCap: view.Uint256View(*uint256.NewInt(66)), + GasFeeCap: view.Uint256View(*uint256.NewInt(1066)), + }, + } + _, blobTx.Message.BlobVersionedHashes = oneEmptyBlobWrapData() + txs := Transactions{ NewTx(&LegacyTx{ Nonce: 1, @@ -134,9 +151,10 @@ func TestDeriveFields(t *testing.T) { Nonce: 5, Value: big.NewInt(5), Gas: 5, - GasTipCap: big.NewInt(56), + GasTipCap: big.NewInt(55), GasFeeCap: big.NewInt(1055), }), + NewTx(blobTx), } blockNumber := big.NewInt(1) @@ -246,12 +264,28 @@ func TestDeriveFields(t *testing.T) { BlockNumber: blockNumber, TransactionIndex: 4, }, + &Receipt{ + Type: BlobTxType, + PostState: common.Hash{6}.Bytes(), + CumulativeGasUsed: 21, + Logs: []*Log{}, + // derived fields: + TxHash: txs[5].Hash(), + GasUsed: 6, + DataGasUsed: 131072, + EffectiveGasPrice: big.NewInt(1066), + DataGasPrice: big.NewInt(1), + BlockHash: blockHash, + BlockNumber: blockNumber, + TransactionIndex: 5, + }, } // Re-derive receipts. basefee := big.NewInt(1000) derivedReceipts := clearComputedFieldsOnReceipts(receipts) - err := Receipts(derivedReceipts).DeriveFields(params.TestChainConfig, blockHash, blockNumber.Uint64(), 0, basefee, txs) + excessDataGas := new(big.Int) + err := Receipts(derivedReceipts).DeriveFields(params.TestChainConfig, blockHash, blockNumber.Uint64(), 0, basefee, excessDataGas, txs) if err != nil { t.Fatalf("DeriveFields(...) = %v, want ", err) } diff --git a/internal/ethapi/api.go b/internal/ethapi/api.go index 1783f9d7812..c09e12316c8 100644 --- a/internal/ethapi/api.go +++ b/internal/ethapi/api.go @@ -1674,6 +1674,10 @@ func (s *TransactionAPI) GetTransactionReceipt(ctx context.Context, hash common. "type": hexutil.Uint(tx.Type()), "effectiveGasPrice": (*hexutil.Big)(receipt.EffectiveGasPrice), } + if receipt.DataGasUsed > 0 { + fields["dataGasUsed"] = hexutil.Uint64(receipt.DataGasUsed) + fields["dataGasPrice"] = (*hexutil.Big)(receipt.DataGasPrice) + } // Assign receipt status or post state. if len(receipt.PostState) > 0 { diff --git a/light/odr_util.go b/light/odr_util.go index 37a1b7a633f..1325beae31a 100644 --- a/light/odr_util.go +++ b/light/odr_util.go @@ -175,7 +175,15 @@ func GetBlockReceipts(ctx context.Context, odr OdrBackend, hash common.Hash, num genesis := rawdb.ReadCanonicalHash(odr.Database(), 0) config := rawdb.ReadChainConfig(odr.Database(), genesis) - if err := receipts.DeriveFields(config, block.Hash(), block.NumberU64(), block.Time(), block.BaseFee(), block.Transactions()); err != nil { + var excessDataGas *big.Int + if number > 0 { + parentHeader, err := GetHeaderByNumber(ctx, odr, number-1) + if err != nil { + return nil, err + } + excessDataGas = parentHeader.ExcessDataGas + } + if err := receipts.DeriveFields(config, block.Hash(), block.NumberU64(), block.Time(), block.BaseFee(), excessDataGas, block.Transactions()); err != nil { return nil, err } rawdb.WriteReceipts(odr.Database(), hash, number, receipts)