core, tests: EIP-4844 transaction processing logic (#27721)

This updates the reference tests to the latest version and also adds logic
to process EIP-4844 blob transactions into the state transition. We are now
passing most Cancun fork tests.

Co-authored-by: Marius van der Wijden <m.vanderwijden@live.de>
Co-authored-by: Felix Lange <fjl@twurst.com>
This commit is contained in:
Martin Holst Swende 2023-07-15 23:27:36 +02:00 committed by GitHub
parent 99e000cb13
commit b058cf454b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
14 changed files with 268 additions and 185 deletions

View File

@ -117,6 +117,7 @@ func runStateTest(fname string, cfg vm.Config, jsonOut, dump bool) error {
// Test failed, mark as so and dump any state to aid debugging // Test failed, mark as so and dump any state to aid debugging
result.Pass, result.Error = false, err.Error() result.Pass, result.Error = false, err.Error()
if dump && s != nil { if dump && s != nil {
s, _ = state.New(*result.Root, s.Database(), nil)
dump := s.RawDump(nil) dump := s.RawDump(nil)
result.State = &dump result.State = &dump
} }

View File

@ -86,18 +86,8 @@ func (v *BlockValidator) ValidateBody(block *types.Block) error {
for _, tx := range block.Transactions() { for _, tx := range block.Transactions() {
// Count the number of blobs to validate against the header's dataGasUsed // Count the number of blobs to validate against the header's dataGasUsed
blobs += len(tx.BlobHashes()) blobs += len(tx.BlobHashes())
// The individual checks for blob validity (version-check + not empty)
// Validate the data blobs individually too // happens in the state_transition check.
if tx.Type() == types.BlobTxType {
if len(tx.BlobHashes()) == 0 {
return errors.New("no-blob blob transaction present in block body")
}
for _, hash := range tx.BlobHashes() {
if hash[0] != params.BlobTxHashVersion {
return fmt.Errorf("blob hash version mismatch (have %d, supported %d)", hash[0], params.BlobTxHashVersion)
}
}
}
} }
if header.DataGasUsed != nil { if header.DataGasUsed != nil {
if want := *header.DataGasUsed / params.BlobTxDataGasPerBlob; uint64(blobs) != want { // div because the header is surely good vs the body might be bloated if want := *header.DataGasUsed / params.BlobTxDataGasPerBlob; uint64(blobs) != want { // div because the header is surely good vs the body might be bloated

View File

@ -100,4 +100,8 @@ var (
// ErrSenderNoEOA is returned if the sender of a transaction is a contract. // ErrSenderNoEOA is returned if the sender of a transaction is a contract.
ErrSenderNoEOA = errors.New("sender not an eoa") ErrSenderNoEOA = errors.New("sender not an eoa")
// ErrBlobFeeCapTooLow is returned if the transaction fee cap is less than the
// data gas fee of the block.
ErrBlobFeeCapTooLow = errors.New("max fee per data gas less than block data gas fee")
) )

View File

@ -66,6 +66,7 @@ func NewEVMBlockContext(header *types.Header, chain ChainContext, author *common
BaseFee: baseFee, BaseFee: baseFee,
GasLimit: header.GasLimit, GasLimit: header.GasLimit,
Random: random, Random: random,
ExcessDataGas: header.ExcessDataGas,
} }
} }

View File

@ -31,6 +31,8 @@ func (g Genesis) MarshalJSON() ([]byte, error) {
GasUsed math.HexOrDecimal64 `json:"gasUsed"` GasUsed math.HexOrDecimal64 `json:"gasUsed"`
ParentHash common.Hash `json:"parentHash"` ParentHash common.Hash `json:"parentHash"`
BaseFee *math.HexOrDecimal256 `json:"baseFeePerGas"` BaseFee *math.HexOrDecimal256 `json:"baseFeePerGas"`
ExcessDataGas *math.HexOrDecimal64 `json:"excessDataGas"`
DataGasUsed *math.HexOrDecimal64 `json:"dataGasUsed"`
} }
var enc Genesis var enc Genesis
enc.Config = g.Config enc.Config = g.Config
@ -51,6 +53,8 @@ func (g Genesis) MarshalJSON() ([]byte, error) {
enc.GasUsed = math.HexOrDecimal64(g.GasUsed) enc.GasUsed = math.HexOrDecimal64(g.GasUsed)
enc.ParentHash = g.ParentHash enc.ParentHash = g.ParentHash
enc.BaseFee = (*math.HexOrDecimal256)(g.BaseFee) enc.BaseFee = (*math.HexOrDecimal256)(g.BaseFee)
enc.ExcessDataGas = (*math.HexOrDecimal64)(g.ExcessDataGas)
enc.DataGasUsed = (*math.HexOrDecimal64)(g.DataGasUsed)
return json.Marshal(&enc) return json.Marshal(&enc)
} }
@ -70,6 +74,8 @@ func (g *Genesis) UnmarshalJSON(input []byte) error {
GasUsed *math.HexOrDecimal64 `json:"gasUsed"` GasUsed *math.HexOrDecimal64 `json:"gasUsed"`
ParentHash *common.Hash `json:"parentHash"` ParentHash *common.Hash `json:"parentHash"`
BaseFee *math.HexOrDecimal256 `json:"baseFeePerGas"` BaseFee *math.HexOrDecimal256 `json:"baseFeePerGas"`
ExcessDataGas *math.HexOrDecimal64 `json:"excessDataGas"`
DataGasUsed *math.HexOrDecimal64 `json:"dataGasUsed"`
} }
var dec Genesis var dec Genesis
if err := json.Unmarshal(input, &dec); err != nil { if err := json.Unmarshal(input, &dec); err != nil {
@ -120,5 +126,11 @@ func (g *Genesis) UnmarshalJSON(input []byte) error {
if dec.BaseFee != nil { if dec.BaseFee != nil {
g.BaseFee = (*big.Int)(dec.BaseFee) g.BaseFee = (*big.Int)(dec.BaseFee)
} }
if dec.ExcessDataGas != nil {
g.ExcessDataGas = (*uint64)(dec.ExcessDataGas)
}
if dec.DataGasUsed != nil {
g.DataGasUsed = (*uint64)(dec.DataGasUsed)
}
return nil return nil
} }

View File

@ -62,7 +62,9 @@ type Genesis struct {
Number uint64 `json:"number"` Number uint64 `json:"number"`
GasUsed uint64 `json:"gasUsed"` GasUsed uint64 `json:"gasUsed"`
ParentHash common.Hash `json:"parentHash"` ParentHash common.Hash `json:"parentHash"`
BaseFee *big.Int `json:"baseFeePerGas"` BaseFee *big.Int `json:"baseFeePerGas"` // EIP-1559
ExcessDataGas *uint64 `json:"excessDataGas"` // EIP-4844
DataGasUsed *uint64 `json:"dataGasUsed"` // EIP-4844
} }
func ReadGenesis(db ethdb.Database) (*Genesis, error) { func ReadGenesis(db ethdb.Database) (*Genesis, error) {
@ -96,6 +98,9 @@ func ReadGenesis(db ethdb.Database) (*Genesis, error) {
genesis.Difficulty = genesisHeader.Difficulty genesis.Difficulty = genesisHeader.Difficulty
genesis.Mixhash = genesisHeader.MixDigest genesis.Mixhash = genesisHeader.MixDigest
genesis.Coinbase = genesisHeader.Coinbase genesis.Coinbase = genesisHeader.Coinbase
genesis.BaseFee = genesisHeader.BaseFee
genesis.ExcessDataGas = genesisHeader.ExcessDataGas
genesis.DataGasUsed = genesisHeader.DataGasUsed
return &genesis, nil return &genesis, nil
} }
@ -221,8 +226,10 @@ type genesisSpecMarshaling struct {
GasUsed math.HexOrDecimal64 GasUsed math.HexOrDecimal64
Number math.HexOrDecimal64 Number math.HexOrDecimal64
Difficulty *math.HexOrDecimal256 Difficulty *math.HexOrDecimal256
BaseFee *math.HexOrDecimal256
Alloc map[common.UnprefixedAddress]GenesisAccount Alloc map[common.UnprefixedAddress]GenesisAccount
BaseFee *math.HexOrDecimal256
ExcessDataGas *math.HexOrDecimal64
DataGasUsed *math.HexOrDecimal64
} }
type genesisAccountMarshaling struct { type genesisAccountMarshaling struct {
@ -463,10 +470,23 @@ func (g *Genesis) ToBlock() *types.Block {
} }
} }
var withdrawals []*types.Withdrawal var withdrawals []*types.Withdrawal
if g.Config != nil && g.Config.IsShanghai(big.NewInt(int64(g.Number)), g.Timestamp) { if conf := g.Config; conf != nil {
num := big.NewInt(int64(g.Number))
if conf.IsShanghai(num, g.Timestamp) {
head.WithdrawalsHash = &types.EmptyWithdrawalsHash head.WithdrawalsHash = &types.EmptyWithdrawalsHash
withdrawals = make([]*types.Withdrawal, 0) withdrawals = make([]*types.Withdrawal, 0)
} }
if conf.IsCancun(num, g.Timestamp) {
head.ExcessDataGas = g.ExcessDataGas
head.DataGasUsed = g.DataGasUsed
if head.ExcessDataGas == nil {
head.ExcessDataGas = new(uint64)
}
if head.DataGasUsed == nil {
head.DataGasUsed = new(uint64)
}
}
}
return types.NewBlock(head, nil, nil, nil, trie.NewStackTrie(nil)).WithWithdrawals(withdrawals) return types.NewBlock(head, nil, nil, nil, trie.NewStackTrie(nil)).WithWithdrawals(withdrawals)
} }

View File

@ -157,6 +157,6 @@ func ApplyTransaction(config *params.ChainConfig, bc ChainContext, author *commo
} }
// Create a new context to be used in the EVM environment // Create a new context to be used in the EVM environment
blockContext := NewEVMBlockContext(header, bc, author) blockContext := NewEVMBlockContext(header, bc, author)
vmenv := vm.NewEVM(blockContext, vm.TxContext{}, statedb, config, cfg) vmenv := vm.NewEVM(blockContext, vm.TxContext{BlobHashes: tx.BlobHashes()}, statedb, config, cfg)
return applyTransaction(msg, config, gp, statedb, header.Number, header.Hash(), tx, usedGas, vmenv) return applyTransaction(msg, config, gp, statedb, header.Number, header.Hash(), tx, usedGas, vmenv)
} }

View File

@ -33,6 +33,7 @@ import (
"github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/params" "github.com/ethereum/go-ethereum/params"
"github.com/ethereum/go-ethereum/trie" "github.com/ethereum/go-ethereum/trie"
"github.com/holiman/uint256"
"golang.org/x/crypto/sha3" "golang.org/x/crypto/sha3"
) )
@ -58,6 +59,10 @@ func TestStateProcessorErrors(t *testing.T) {
BerlinBlock: big.NewInt(0), BerlinBlock: big.NewInt(0),
LondonBlock: big.NewInt(0), LondonBlock: big.NewInt(0),
Ethash: new(params.EthashConfig), Ethash: new(params.EthashConfig),
TerminalTotalDifficulty: big.NewInt(0),
TerminalTotalDifficultyPassed: true,
ShanghaiTime: new(uint64),
CancunTime: new(uint64),
} }
signer = types.LatestSigner(config) signer = types.LatestSigner(config)
key1, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291") key1, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291")
@ -89,6 +94,22 @@ func TestStateProcessorErrors(t *testing.T) {
}), signer, key1) }), signer, key1)
return tx return tx
} }
var mkBlobTx = func(nonce uint64, to common.Address, gasLimit uint64, gasTipCap, gasFeeCap *big.Int, hashes []common.Hash) *types.Transaction {
tx, err := types.SignTx(types.NewTx(&types.BlobTx{
Nonce: nonce,
GasTipCap: uint256.MustFromBig(gasTipCap),
GasFeeCap: uint256.MustFromBig(gasFeeCap),
Gas: gasLimit,
To: to,
BlobHashes: hashes,
Value: new(uint256.Int),
}), signer, key1)
if err != nil {
t.Fatal(err)
}
return tx
}
{ // Tests against a 'recent' chain definition { // Tests against a 'recent' chain definition
var ( var (
db = rawdb.NewMemoryDatabase() db = rawdb.NewMemoryDatabase()
@ -105,8 +126,10 @@ func TestStateProcessorErrors(t *testing.T) {
}, },
}, },
} }
blockchain, _ = NewBlockChain(db, nil, gspec, nil, ethash.NewFaker(), vm.Config{}, nil, nil) blockchain, _ = NewBlockChain(db, nil, gspec, nil, beacon.New(ethash.NewFaker()), vm.Config{}, nil, nil)
tooBigInitCode = [params.MaxInitCodeSize + 1]byte{}
) )
defer blockchain.Stop() defer blockchain.Stop()
bigNumber := new(big.Int).SetBytes(common.FromHex("0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff")) bigNumber := new(big.Int).SetBytes(common.FromHex("0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"))
tooBigNumber := new(big.Int).Set(bigNumber) tooBigNumber := new(big.Int).Set(bigNumber)
@ -209,8 +232,26 @@ func TestStateProcessorErrors(t *testing.T) {
}, },
want: "could not apply tx 0 [0xd82a0c2519acfeac9a948258c47e784acd20651d9d80f9a1c67b4137651c3a24]: insufficient funds for gas * price + value: address 0x71562b71999873DB5b286dF957af199Ec94617F7 have 1000000000000000000 want 2431633873983640103894990685182446064918669677978451844828609264166175722438635000", want: "could not apply tx 0 [0xd82a0c2519acfeac9a948258c47e784acd20651d9d80f9a1c67b4137651c3a24]: insufficient funds for gas * price + value: address 0x71562b71999873DB5b286dF957af199Ec94617F7 have 1000000000000000000 want 2431633873983640103894990685182446064918669677978451844828609264166175722438635000",
}, },
{ // ErrMaxInitCodeSizeExceeded
txs: []*types.Transaction{
mkDynamicCreationTx(0, 500000, common.Big0, big.NewInt(params.InitialBaseFee), tooBigInitCode[:]),
},
want: "could not apply tx 0 [0xd491405f06c92d118dd3208376fcee18a57c54bc52063ee4a26b1cf296857c25]: max initcode size exceeded: code size 49153 limit 49152",
},
{ // ErrIntrinsicGas: Not enough gas to cover init code
txs: []*types.Transaction{
mkDynamicCreationTx(0, 54299, common.Big0, big.NewInt(params.InitialBaseFee), make([]byte, 320)),
},
want: "could not apply tx 0 [0xfd49536a9b323769d8472fcb3ebb3689b707a349379baee3e2ee3fe7baae06a1]: intrinsic gas too low: have 54299, want 54300",
},
{ // ErrBlobFeeCapTooLow
txs: []*types.Transaction{
mkBlobTx(0, common.Address{}, params.TxGas, big.NewInt(1), big.NewInt(1), []common.Hash{(common.Hash{1})}),
},
want: "could not apply tx 0 [0x6c11015985ce82db691d7b2d017acda296db88b811c3c60dc71449c76256c716]: max fee per gas less than block base fee: address 0x71562b71999873DB5b286dF957af199Ec94617F7, maxFeePerGas: 1 baseFee: 875000000",
},
} { } {
block := GenerateBadBlock(gspec.ToBlock(), ethash.NewFaker(), tt.txs, gspec.Config) block := GenerateBadBlock(gspec.ToBlock(), beacon.New(ethash.NewFaker()), tt.txs, gspec.Config)
_, err := blockchain.InsertChain(types.Blocks{block}) _, err := blockchain.InsertChain(types.Blocks{block})
if err == nil { if err == nil {
t.Fatal("block imported without errors") t.Fatal("block imported without errors")
@ -284,7 +325,7 @@ func TestStateProcessorErrors(t *testing.T) {
}, },
}, },
} }
blockchain, _ = NewBlockChain(db, nil, gspec, nil, ethash.NewFaker(), vm.Config{}, nil, nil) blockchain, _ = NewBlockChain(db, nil, gspec, nil, beacon.New(ethash.NewFaker()), vm.Config{}, nil, nil)
) )
defer blockchain.Stop() defer blockchain.Stop()
for i, tt := range []struct { for i, tt := range []struct {
@ -298,73 +339,7 @@ func TestStateProcessorErrors(t *testing.T) {
want: "could not apply tx 0 [0x88626ac0d53cb65308f2416103c62bb1f18b805573d4f96a3640bbbfff13c14f]: sender not an eoa: address 0x71562b71999873DB5b286dF957af199Ec94617F7, codehash: 0x9280914443471259d4570a8661015ae4a5b80186dbc619658fb494bebc3da3d1", want: "could not apply tx 0 [0x88626ac0d53cb65308f2416103c62bb1f18b805573d4f96a3640bbbfff13c14f]: sender not an eoa: address 0x71562b71999873DB5b286dF957af199Ec94617F7, codehash: 0x9280914443471259d4570a8661015ae4a5b80186dbc619658fb494bebc3da3d1",
}, },
} { } {
block := GenerateBadBlock(gspec.ToBlock(), ethash.NewFaker(), tt.txs, gspec.Config) block := GenerateBadBlock(gspec.ToBlock(), beacon.New(ethash.NewFaker()), tt.txs, gspec.Config)
_, err := blockchain.InsertChain(types.Blocks{block})
if err == nil {
t.Fatal("block imported without errors")
}
if have, want := err.Error(), tt.want; have != want {
t.Errorf("test %d:\nhave \"%v\"\nwant \"%v\"\n", i, have, want)
}
}
}
// ErrMaxInitCodeSizeExceeded, for this we need extra Shanghai (EIP-3860) enabled.
{
var (
db = rawdb.NewMemoryDatabase()
gspec = &Genesis{
Config: &params.ChainConfig{
ChainID: big.NewInt(1),
HomesteadBlock: big.NewInt(0),
EIP150Block: big.NewInt(0),
EIP155Block: big.NewInt(0),
EIP158Block: big.NewInt(0),
ByzantiumBlock: big.NewInt(0),
ConstantinopleBlock: big.NewInt(0),
PetersburgBlock: big.NewInt(0),
IstanbulBlock: big.NewInt(0),
MuirGlacierBlock: big.NewInt(0),
BerlinBlock: big.NewInt(0),
LondonBlock: big.NewInt(0),
ArrowGlacierBlock: big.NewInt(0),
GrayGlacierBlock: big.NewInt(0),
MergeNetsplitBlock: big.NewInt(0),
TerminalTotalDifficulty: big.NewInt(0),
TerminalTotalDifficultyPassed: true,
ShanghaiTime: u64(0),
},
Alloc: GenesisAlloc{
common.HexToAddress("0x71562b71999873DB5b286dF957af199Ec94617F7"): GenesisAccount{
Balance: big.NewInt(1000000000000000000), // 1 ether
Nonce: 0,
},
},
}
genesis = gspec.MustCommit(db)
blockchain, _ = NewBlockChain(db, nil, gspec, nil, beacon.New(ethash.NewFaker()), vm.Config{}, nil, nil)
tooBigInitCode = [params.MaxInitCodeSize + 1]byte{}
smallInitCode = [320]byte{}
)
defer blockchain.Stop()
for i, tt := range []struct {
txs []*types.Transaction
want string
}{
{ // ErrMaxInitCodeSizeExceeded
txs: []*types.Transaction{
mkDynamicCreationTx(0, 500000, common.Big0, misc.CalcBaseFee(config, genesis.Header()), tooBigInitCode[:]),
},
want: "could not apply tx 0 [0x832b54a6c3359474a9f504b1003b2cc1b6fcaa18e4ef369eb45b5d40dad6378f]: max initcode size exceeded: code size 49153 limit 49152",
},
{ // ErrIntrinsicGas: Not enough gas to cover init code
txs: []*types.Transaction{
mkDynamicCreationTx(0, 54299, common.Big0, misc.CalcBaseFee(config, genesis.Header()), smallInitCode[:]),
},
want: "could not apply tx 0 [0x39b7436cb432d3662a25626474282c5c4c1a213326fd87e4e18a91477bae98b2]: intrinsic gas too low: have 54299, want 54300",
},
} {
block := GenerateBadBlock(genesis, beacon.New(ethash.NewFaker()), tt.txs, gspec.Config)
_, err := blockchain.InsertChain(types.Blocks{block}) _, err := blockchain.InsertChain(types.Blocks{block})
if err == nil { if err == nil {
t.Fatal("block imported without errors") t.Fatal("block imported without errors")
@ -412,6 +387,7 @@ func GenerateBadBlock(parent *types.Block, engine consensus.Engine, txs types.Tr
hasher := sha3.NewLegacyKeccak256() hasher := sha3.NewLegacyKeccak256()
hasher.Write(header.Number.Bytes()) hasher.Write(header.Number.Bytes())
var cumulativeGas uint64 var cumulativeGas uint64
var nBlobs int
for _, tx := range txs { for _, tx := range txs {
txh := tx.Hash() txh := tx.Hash()
hasher.Write(txh[:]) hasher.Write(txh[:])
@ -420,8 +396,20 @@ func GenerateBadBlock(parent *types.Block, engine consensus.Engine, txs types.Tr
receipt.GasUsed = tx.Gas() receipt.GasUsed = tx.Gas()
receipts = append(receipts, receipt) receipts = append(receipts, receipt)
cumulativeGas += tx.Gas() cumulativeGas += tx.Gas()
nBlobs += len(tx.BlobHashes())
} }
header.Root = common.BytesToHash(hasher.Sum(nil)) header.Root = common.BytesToHash(hasher.Sum(nil))
if config.IsCancun(header.Number, header.Time) {
var pExcess, pUsed = uint64(0), uint64(0)
if parent.ExcessDataGas() != nil {
pExcess = *parent.ExcessDataGas()
pUsed = *parent.DataGasUsed()
}
excess := misc.CalcExcessDataGas(pExcess, pUsed)
used := uint64(nBlobs * params.BlobTxDataGasPerBlob)
header.ExcessDataGas = &excess
header.DataGasUsed = &used
}
// Assemble and return the final block for sealing // Assemble and return the final block for sealing
if config.IsShanghai(header.Number, header.Time) { if config.IsShanghai(header.Number, header.Time) {
return types.NewBlockWithWithdrawals(header, txs, nil, receipts, []*types.Withdrawal{}, trie.NewStackTrie(nil)) return types.NewBlockWithWithdrawals(header, txs, nil, receipts, []*types.Withdrawal{}, trie.NewStackTrie(nil))

View File

@ -17,12 +17,14 @@
package core package core
import ( import (
"errors"
"fmt" "fmt"
"math" "math"
"math/big" "math/big"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
cmath "github.com/ethereum/go-ethereum/common/math" 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/types"
"github.com/ethereum/go-ethereum/core/vm" "github.com/ethereum/go-ethereum/core/vm"
"github.com/ethereum/go-ethereum/params" "github.com/ethereum/go-ethereum/params"
@ -135,6 +137,7 @@ type Message struct {
GasTipCap *big.Int GasTipCap *big.Int
Data []byte Data []byte
AccessList types.AccessList AccessList types.AccessList
BlobGasFeeCap *big.Int
BlobHashes []common.Hash BlobHashes []common.Hash
// When SkipAccountChecks is true, the message nonce is not checked against the // When SkipAccountChecks is true, the message nonce is not checked against the
@ -157,6 +160,7 @@ func TransactionToMessage(tx *types.Transaction, s types.Signer, baseFee *big.In
AccessList: tx.AccessList(), AccessList: tx.AccessList(),
SkipAccountChecks: false, SkipAccountChecks: false,
BlobHashes: tx.BlobHashes(), BlobHashes: tx.BlobHashes(),
BlobGasFeeCap: tx.BlobGasFeeCap(),
} }
// If baseFee provided, set gasPrice to effectiveGasPrice. // If baseFee provided, set gasPrice to effectiveGasPrice.
if baseFee != nil { if baseFee != nil {
@ -230,12 +234,28 @@ func (st *StateTransition) to() common.Address {
func (st *StateTransition) buyGas() error { func (st *StateTransition) buyGas() error {
mgval := new(big.Int).SetUint64(st.msg.GasLimit) mgval := new(big.Int).SetUint64(st.msg.GasLimit)
mgval = mgval.Mul(mgval, st.msg.GasPrice) mgval = mgval.Mul(mgval, st.msg.GasPrice)
balanceCheck := mgval balanceCheck := new(big.Int).Set(mgval)
if st.msg.GasFeeCap != nil { if st.msg.GasFeeCap != nil {
balanceCheck = new(big.Int).SetUint64(st.msg.GasLimit) balanceCheck.SetUint64(st.msg.GasLimit)
balanceCheck = balanceCheck.Mul(balanceCheck, st.msg.GasFeeCap) balanceCheck = balanceCheck.Mul(balanceCheck, st.msg.GasFeeCap)
balanceCheck.Add(balanceCheck, st.msg.Value) balanceCheck.Add(balanceCheck, st.msg.Value)
} }
if st.evm.ChainConfig().IsCancun(st.evm.Context.BlockNumber, st.evm.Context.Time) {
if dataGas := st.dataGasUsed(); dataGas > 0 {
if st.evm.Context.ExcessDataGas == nil {
// programming error
panic("missing field excess data gas")
}
// Check that the user has enough funds to cover dataGasUsed * tx.BlobGasFeeCap
blobBalanceCheck := new(big.Int).SetUint64(dataGas)
blobBalanceCheck.Mul(blobBalanceCheck, st.msg.BlobGasFeeCap)
balanceCheck.Add(balanceCheck, blobBalanceCheck)
// Pay for dataGasUsed * actual blob fee
blobFee := new(big.Int).SetUint64(dataGas)
blobFee.Mul(blobFee, misc.CalcBlobFee(*st.evm.Context.ExcessDataGas))
mgval.Add(mgval, blobFee)
}
}
if have, want := st.state.GetBalance(st.msg.From), balanceCheck; have.Cmp(want) < 0 { 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) return fmt.Errorf("%w: address %v have %v want %v", ErrInsufficientFunds, st.msg.From.Hex(), have, want)
} }
@ -297,6 +317,28 @@ func (st *StateTransition) preCheck() error {
} }
} }
} }
// Check the blob version validity
if msg.BlobHashes != nil {
if len(msg.BlobHashes) == 0 {
return errors.New("blob transaction missing blob hashes")
}
for i, hash := range msg.BlobHashes {
if hash[0] != params.BlobTxHashVersion {
return fmt.Errorf("blob %d hash version mismatch (have %d, supported %d)",
i, hash[0], params.BlobTxHashVersion)
}
}
}
if st.evm.ChainConfig().IsCancun(st.evm.Context.BlockNumber, st.evm.Context.Time) {
if st.dataGasUsed() > 0 {
// Check that the user is paying at least the current blob fee
if have, want := st.msg.BlobGasFeeCap, misc.CalcBlobFee(*st.evm.Context.ExcessDataGas); have.Cmp(want) < 0 {
return fmt.Errorf("%w: address %v have %v want %v", ErrBlobFeeCapTooLow, st.msg.From.Hex(), have, want)
}
}
}
return st.buyGas() return st.buyGas()
} }
@ -427,3 +469,8 @@ func (st *StateTransition) refundGas(refundQuotient uint64) {
func (st *StateTransition) gasUsed() uint64 { func (st *StateTransition) gasUsed() uint64 {
return st.initialGas - st.gasRemaining return st.initialGas - st.gasRemaining
} }
// dataGasUsed returns the amount of data gas used by the message.
func (st *StateTransition) dataGasUsed() uint64 {
return uint64(len(st.msg.BlobHashes) * params.BlobTxDataGasPerBlob)
}

View File

@ -74,6 +74,7 @@ type BlockContext struct {
Difficulty *big.Int // Provides information for DIFFICULTY Difficulty *big.Int // Provides information for DIFFICULTY
BaseFee *big.Int // Provides information for BASEFEE BaseFee *big.Int // Provides information for BASEFEE
Random *common.Hash // Provides information for PREVRANDAO Random *common.Hash // Provides information for PREVRANDAO
ExcessDataGas *uint64 // ExcessDataGas field in the header, needed to compute the data
} }
// TxContext provides the EVM with information about a transaction. // TxContext provides the EVM with information about a transaction.

View File

@ -6,6 +6,7 @@ import (
"encoding/json" "encoding/json"
"math/big" "math/big"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil" "github.com/ethereum/go-ethereum/common/hexutil"
"github.com/ethereum/go-ethereum/common/math" "github.com/ethereum/go-ethereum/common/math"
"github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/core/types"
@ -26,6 +27,8 @@ func (s stTransaction) MarshalJSON() ([]byte, error) {
GasLimit []math.HexOrDecimal64 `json:"gasLimit"` GasLimit []math.HexOrDecimal64 `json:"gasLimit"`
Value []string `json:"value"` Value []string `json:"value"`
PrivateKey hexutil.Bytes `json:"secretKey"` PrivateKey hexutil.Bytes `json:"secretKey"`
BlobVersionedHashes []common.Hash `json:"blobVersionedHashes,omitempty"`
BlobGasFeeCap *math.HexOrDecimal256 `json:"maxFeePerDataGas,omitempty"`
} }
var enc stTransaction var enc stTransaction
enc.GasPrice = (*math.HexOrDecimal256)(s.GasPrice) enc.GasPrice = (*math.HexOrDecimal256)(s.GasPrice)
@ -43,6 +46,8 @@ func (s stTransaction) MarshalJSON() ([]byte, error) {
} }
enc.Value = s.Value enc.Value = s.Value
enc.PrivateKey = s.PrivateKey enc.PrivateKey = s.PrivateKey
enc.BlobVersionedHashes = s.BlobVersionedHashes
enc.BlobGasFeeCap = (*math.HexOrDecimal256)(s.BlobGasFeeCap)
return json.Marshal(&enc) return json.Marshal(&enc)
} }
@ -59,6 +64,8 @@ func (s *stTransaction) UnmarshalJSON(input []byte) error {
GasLimit []math.HexOrDecimal64 `json:"gasLimit"` GasLimit []math.HexOrDecimal64 `json:"gasLimit"`
Value []string `json:"value"` Value []string `json:"value"`
PrivateKey *hexutil.Bytes `json:"secretKey"` PrivateKey *hexutil.Bytes `json:"secretKey"`
BlobVersionedHashes []common.Hash `json:"blobVersionedHashes,omitempty"`
BlobGasFeeCap *math.HexOrDecimal256 `json:"maxFeePerDataGas,omitempty"`
} }
var dec stTransaction var dec stTransaction
if err := json.Unmarshal(input, &dec); err != nil { if err := json.Unmarshal(input, &dec); err != nil {
@ -97,5 +104,11 @@ func (s *stTransaction) UnmarshalJSON(input []byte) error {
if dec.PrivateKey != nil { if dec.PrivateKey != nil {
s.PrivateKey = *dec.PrivateKey s.PrivateKey = *dec.PrivateKey
} }
if dec.BlobVersionedHashes != nil {
s.BlobVersionedHashes = dec.BlobVersionedHashes
}
if dec.BlobGasFeeCap != nil {
s.BlobGasFeeCap = (*big.Int)(dec.BlobGasFeeCap)
}
return nil return nil
} }

View File

@ -48,23 +48,24 @@ func TestState(t *testing.T) {
st.slow(`^stStaticCall/static_Return50000`) st.slow(`^stStaticCall/static_Return50000`)
st.slow(`^stSystemOperationsTest/CallRecursiveBomb`) st.slow(`^stSystemOperationsTest/CallRecursiveBomb`)
st.slow(`^stTransactionTest/Opcodes_TransactionInit`) st.slow(`^stTransactionTest/Opcodes_TransactionInit`)
// Very time consuming // Very time consuming
st.skipLoad(`^stTimeConsuming/`) st.skipLoad(`^stTimeConsuming/`)
st.skipLoad(`.*vmPerformance/loop.*`) st.skipLoad(`.*vmPerformance/loop.*`)
// Uses 1GB RAM per tested fork // Uses 1GB RAM per tested fork
st.skipLoad(`^stStaticCall/static_Call1MB`) st.skipLoad(`^stStaticCall/static_Call1MB`)
// Broken tests: // Broken tests:
// // EOF is not part of cancun
// The stEOF tests are generated with EOF as part of Shanghai, which st.skipLoad(`^stEOF/`)
// is erroneous. Therefore, these tests are skipped.
st.skipLoad(`^EIPTests/stEOF/`)
// Expected failures: // Expected failures:
// These EIP-4844 tests need to be regenerated.
st.fails(`stEIP4844-blobtransactions/opcodeBlobhashOutOfRange.json`, "test has incorrect state root")
st.fails(`stEIP4844-blobtransactions/opcodeBlobhBounds.json`, "test has incorrect state root")
// For Istanbul, older tests were moved into LegacyTests // For Istanbul, older tests were moved into LegacyTests
for _, dir := range []string{ for _, dir := range []string{
filepath.Join(baseDir, "EIPTests", "StateTests"),
stateTestDir, stateTestDir,
legacyStateTestDir, legacyStateTestDir,
benchmarksDir, benchmarksDir,

View File

@ -113,6 +113,8 @@ type stTransaction struct {
GasLimit []uint64 `json:"gasLimit"` GasLimit []uint64 `json:"gasLimit"`
Value []string `json:"value"` Value []string `json:"value"`
PrivateKey []byte `json:"secretKey"` PrivateKey []byte `json:"secretKey"`
BlobVersionedHashes []common.Hash `json:"blobVersionedHashes,omitempty"`
BlobGasFeeCap *big.Int `json:"maxFeePerDataGas,omitempty"`
} }
type stTransactionMarshaling struct { type stTransactionMarshaling struct {
@ -122,6 +124,7 @@ type stTransactionMarshaling struct {
Nonce math.HexOrDecimal64 Nonce math.HexOrDecimal64
GasLimit []math.HexOrDecimal64 GasLimit []math.HexOrDecimal64
PrivateKey hexutil.Bytes PrivateKey hexutil.Bytes
BlobGasFeeCap *math.HexOrDecimal256
} }
// GetChainConfig takes a fork definition and returns a chain config. // GetChainConfig takes a fork definition and returns a chain config.
@ -413,6 +416,8 @@ func (tx *stTransaction) toMessage(ps stPostState, baseFee *big.Int) (*core.Mess
GasTipCap: tx.MaxPriorityFeePerGas, GasTipCap: tx.MaxPriorityFeePerGas,
Data: data, Data: data,
AccessList: accessList, AccessList: accessList,
BlobHashes: tx.BlobVersionedHashes,
BlobGasFeeCap: tx.BlobGasFeeCap,
} }
return msg, nil return msg, nil
} }

@ -1 +1 @@
Subproject commit bac70c50a579197af68af5fc6d8c7b6163b92c52 Subproject commit ee3fa4c86d05f99f2717f83a6ad08008490ddf07