From ec2ec2d08e28571dc189903f743cc3931da254a9 Mon Sep 17 00:00:00 2001 From: Marius van der Wijden Date: Tue, 22 Nov 2022 22:39:52 +0100 Subject: [PATCH] core: implement EIP-3651, warm coinbase (#25819) Implements EIP-3651, "Warm Coinbase", for Shanghai hardfork. Specification: https://eips.ethereum.org/EIPS/eip-3651. --- core/blockchain_test.go | 108 +++++++++++++++++++++++++++++++++++++ core/state/statedb.go | 22 +++++--- core/state_transition.go | 2 +- core/vm/interface.go | 2 +- core/vm/runtime/runtime.go | 6 +-- tests/state_test.go | 2 +- 6 files changed, 128 insertions(+), 14 deletions(-) diff --git a/core/blockchain_test.go b/core/blockchain_test.go index c476d4596..faa4b383f 100644 --- a/core/blockchain_test.go +++ b/core/blockchain_test.go @@ -4187,3 +4187,111 @@ func TestTransientStorageReset(t *testing.T) { t.Fatalf("Unexpected dirty storage slot") } } + +func TestEIP3651(t *testing.T) { + var ( + aa = common.HexToAddress("0x000000000000000000000000000000000000aaaa") + bb = common.HexToAddress("0x000000000000000000000000000000000000bbbb") + engine = ethash.NewFaker() + + // A sender who makes transactions, has some funds + key1, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291") + key2, _ = crypto.HexToECDSA("8a1f9a8f95be41cd7ccb6168179afb4504aefe388d1e14474d32c45c72ce7b7a") + addr1 = crypto.PubkeyToAddress(key1.PublicKey) + addr2 = crypto.PubkeyToAddress(key2.PublicKey) + funds = new(big.Int).Mul(common.Big1, big.NewInt(params.Ether)) + gspec = &Genesis{ + Config: params.AllEthashProtocolChanges, + Alloc: GenesisAlloc{ + addr1: {Balance: funds}, + addr2: {Balance: funds}, + // The address 0xAAAA sloads 0x00 and 0x01 + aa: { + Code: []byte{ + byte(vm.PC), + byte(vm.PC), + byte(vm.SLOAD), + byte(vm.SLOAD), + }, + Nonce: 0, + Balance: big.NewInt(0), + }, + // The address 0xBBBB calls 0xAAAA + bb: { + Code: []byte{ + byte(vm.PUSH1), 0, // out size + byte(vm.DUP1), // out offset + byte(vm.DUP1), // out insize + byte(vm.DUP1), // in offset + byte(vm.PUSH2), // address + byte(0xaa), + byte(0xaa), + byte(vm.GAS), // gas + byte(vm.DELEGATECALL), + }, + Nonce: 0, + Balance: big.NewInt(0), + }, + }, + } + ) + + gspec.Config.BerlinBlock = common.Big0 + gspec.Config.LondonBlock = common.Big0 + gspec.Config.ShanghaiBlock = common.Big0 + signer := types.LatestSigner(gspec.Config) + + _, blocks, _ := GenerateChainWithGenesis(gspec, engine, 1, func(i int, b *BlockGen) { + b.SetCoinbase(aa) + // One transaction to Coinbase + txdata := &types.DynamicFeeTx{ + ChainID: gspec.Config.ChainID, + Nonce: 0, + To: &bb, + Gas: 500000, + GasFeeCap: newGwei(5), + GasTipCap: big.NewInt(2), + AccessList: nil, + Data: []byte{}, + } + tx := types.NewTx(txdata) + tx, _ = types.SignTx(tx, signer, key1) + + b.AddTx(tx) + }) + chain, err := NewBlockChain(rawdb.NewMemoryDatabase(), nil, gspec, nil, engine, vm.Config{Tracer: logger.NewMarkdownLogger(&logger.Config{}, os.Stderr)}, nil, nil) + if err != nil { + t.Fatalf("failed to create tester chain: %v", err) + } + if n, err := chain.InsertChain(blocks); err != nil { + t.Fatalf("block %d: failed to insert into chain: %v", n, err) + } + + block := chain.GetBlockByNumber(1) + + // 1+2: Ensure EIP-1559 access lists are accounted for via gas usage. + innerGas := vm.GasQuickStep*2 + params.ColdSloadCostEIP2929*2 + expectedGas := params.TxGas + 5*vm.GasFastestStep + vm.GasQuickStep + 100 + innerGas // 100 because 0xaaaa is in access list + if block.GasUsed() != expectedGas { + t.Fatalf("incorrect amount of gas spent: expected %d, got %d", expectedGas, block.GasUsed()) + } + + state, _ := chain.State() + + // 3: Ensure that miner received only the tx's tip. + actual := state.GetBalance(block.Coinbase()) + expected := new(big.Int).Add( + new(big.Int).SetUint64(block.GasUsed()*block.Transactions()[0].GasTipCap().Uint64()), + ethash.ConstantinopleBlockReward, + ) + if actual.Cmp(expected) != 0 { + t.Fatalf("miner balance incorrect: expected %d, got %d", expected, actual) + } + + // 4: Ensure the tx sender paid for the gasUsed * (tip + block baseFee). + actual = new(big.Int).Sub(funds, state.GetBalance(addr1)) + expected = new(big.Int).SetUint64(block.GasUsed() * (block.Transactions()[0].GasTipCap().Uint64() + block.BaseFee().Uint64())) + if actual.Cmp(expected) != 0 { + t.Fatalf("sender balance incorrect: expected %d, got %d", expected, actual) + } +} diff --git a/core/state/statedb.go b/core/state/statedb.go index 559f7fa51..b85c94850 100644 --- a/core/state/statedb.go +++ b/core/state/statedb.go @@ -1067,26 +1067,32 @@ func (s *StateDB) Commit(deleteEmptyObjects bool) (common.Hash, error) { // - Add the contents of the optional tx access list (2930) // // Potential EIPs: -// - Reset transient storage(1153) -func (s *StateDB) Prepare(rules params.Rules, sender common.Address, dst *common.Address, precompiles []common.Address, list types.AccessList) { +// - Reset access list (Berlin) +// - Add coinbase to access list (EIP-3651) +// - Reset transient storage (EIP-1153) +func (s *StateDB) Prepare(rules params.Rules, sender, coinbase common.Address, dst *common.Address, precompiles []common.Address, list types.AccessList) { if rules.IsBerlin { // Clear out any leftover from previous executions - s.accessList = newAccessList() + al := newAccessList() + s.accessList = al - s.AddAddressToAccessList(sender) + al.AddAddress(sender) if dst != nil { - s.AddAddressToAccessList(*dst) + al.AddAddress(*dst) // If it's a create-tx, the destination will be added inside evm.create } for _, addr := range precompiles { - s.AddAddressToAccessList(addr) + al.AddAddress(addr) } for _, el := range list { - s.AddAddressToAccessList(el.Address) + al.AddAddress(el.Address) for _, key := range el.StorageKeys { - s.AddSlotToAccessList(el.Address, key) + al.AddSlot(el.Address, key) } } + if rules.IsShanghai { // EIP-3651: warm coinbase + al.AddAddress(coinbase) + } } // Reset transient storage at the beginning of transaction execution s.transientStorage = newTransientStorage() diff --git a/core/state_transition.go b/core/state_transition.go index 7387acf35..a6cf3f7c3 100644 --- a/core/state_transition.go +++ b/core/state_transition.go @@ -322,7 +322,7 @@ func (st *StateTransition) TransitionDb() (*ExecutionResult, error) { // Execute the preparatory steps for state transition which includes: // - prepare accessList(post-berlin) // - reset transient storage(eip 1153) - st.state.Prepare(rules, msg.From(), msg.To(), vm.ActivePrecompiles(rules), msg.AccessList()) + st.state.Prepare(rules, msg.From(), st.evm.Context.Coinbase, msg.To(), vm.ActivePrecompiles(rules), msg.AccessList()) var ( ret []byte diff --git a/core/vm/interface.go b/core/vm/interface.go index 624272dbf..0ee32b1dd 100644 --- a/core/vm/interface.go +++ b/core/vm/interface.go @@ -69,7 +69,7 @@ type StateDB interface { // AddSlotToAccessList adds the given (address,slot) to the access list. This operation is safe to perform // even if the feature/fork is not active yet AddSlotToAccessList(addr common.Address, slot common.Hash) - Prepare(rules params.Rules, sender common.Address, dest *common.Address, precompiles []common.Address, txAccesses types.AccessList) + Prepare(rules params.Rules, sender, coinbase common.Address, dest *common.Address, precompiles []common.Address, txAccesses types.AccessList) RevertToSnapshot(int) Snapshot() int diff --git a/core/vm/runtime/runtime.go b/core/vm/runtime/runtime.go index b185211ff..6b355deeb 100644 --- a/core/vm/runtime/runtime.go +++ b/core/vm/runtime/runtime.go @@ -122,7 +122,7 @@ func Execute(code, input []byte, cfg *Config) ([]byte, *state.StateDB, error) { // Execute the preparatory steps for state transition which includes: // - prepare accessList(post-berlin) // - reset transient storage(eip 1153) - cfg.State.Prepare(rules, cfg.Origin, &address, vm.ActivePrecompiles(rules), nil) + cfg.State.Prepare(rules, cfg.Origin, cfg.Coinbase, &address, vm.ActivePrecompiles(rules), nil) cfg.State.CreateAccount(address) // set the receiver's (the executing contract) code for execution. @@ -156,7 +156,7 @@ func Create(input []byte, cfg *Config) ([]byte, common.Address, uint64, error) { // Execute the preparatory steps for state transition which includes: // - prepare accessList(post-berlin) // - reset transient storage(eip 1153) - cfg.State.Prepare(rules, cfg.Origin, nil, vm.ActivePrecompiles(rules), nil) + cfg.State.Prepare(rules, cfg.Origin, cfg.Coinbase, nil, vm.ActivePrecompiles(rules), nil) // Call the code with the given configuration. code, address, leftOverGas, err := vmenv.Create( @@ -185,7 +185,7 @@ func Call(address common.Address, input []byte, cfg *Config) ([]byte, uint64, er // Execute the preparatory steps for state transition which includes: // - prepare accessList(post-berlin) // - reset transient storage(eip 1153) - statedb.Prepare(rules, cfg.Origin, &address, vm.ActivePrecompiles(rules), nil) + statedb.Prepare(rules, cfg.Origin, cfg.Coinbase, &address, vm.ActivePrecompiles(rules), nil) // Call the code with the given configuration. ret, leftOverGas, err := vmenv.Call( diff --git a/tests/state_test.go b/tests/state_test.go index 86b1a0e3c..cb7f76521 100644 --- a/tests/state_test.go +++ b/tests/state_test.go @@ -241,7 +241,7 @@ func runBenchmark(b *testing.B, t *StateTest) { b.ResetTimer() for n := 0; n < b.N; n++ { snapshot := statedb.Snapshot() - statedb.Prepare(rules, msg.From(), msg.To(), vm.ActivePrecompiles(rules), msg.AccessList()) + statedb.Prepare(rules, msg.From(), context.Coinbase, msg.To(), vm.ActivePrecompiles(rules), msg.AccessList()) b.StartTimer() start := time.Now()