From 77c43453c94e12288e02d5b024809f01dc0882fb Mon Sep 17 00:00:00 2001 From: Ignacio Hagopian Date: Thu, 8 Feb 2024 10:20:43 -0300 Subject: [PATCH 1/4] instructions: add access witness recording for EXTCODEHASH --- core/vm/instructions.go | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/core/vm/instructions.go b/core/vm/instructions.go index 7fb340da7b24..78c8cbbc8c8f 100644 --- a/core/vm/instructions.go +++ b/core/vm/instructions.go @@ -490,6 +490,14 @@ func opExtCodeHash(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) } else { slot.SetBytes(interpreter.evm.StateDB.GetCodeHash(address).Bytes()) } + if interpreter.evm.chainRules.IsPrague { + statelessGas := interpreter.evm.Accesses.TouchAddressOnReadAndComputeGas(slot.Bytes(), uint256.Int{}, trieUtils.VersionLeafKey) + statelessGas += interpreter.evm.Accesses.TouchAddressOnReadAndComputeGas(slot.Bytes(), uint256.Int{}, trieUtils.CodeKeccakLeafKey) + if !scope.Contract.UseGas(statelessGas) { + scope.Contract.Gas = 0 + return nil, ErrOutOfGas + } + } return nil, nil } From c977e636f73ad1ddff55c47ac7ecfa2bc45ce409 Mon Sep 17 00:00:00 2001 From: Ignacio Hagopian Date: Thu, 8 Feb 2024 10:20:54 -0300 Subject: [PATCH 2/4] add test for EXTCODEHASH witness recording --- core/state_processor_test.go | 128 +++++++++++++++++++++++++++++++++++ 1 file changed, 128 insertions(+) diff --git a/core/state_processor_test.go b/core/state_processor_test.go index f2e5c2769c99..434f90b636b5 100644 --- a/core/state_processor_test.go +++ b/core/state_processor_test.go @@ -37,6 +37,7 @@ import ( "github.com/ethereum/go-ethereum/core/vm" "github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/params" + "github.com/ethereum/go-ethereum/trie/utils" //"github.com/ethereum/go-ethereum/rlp" "github.com/ethereum/go-ethereum/trie" @@ -794,3 +795,130 @@ func TestProcessVerkleContractWithEmptyCode(t *testing.T) { } } } + +func TestProcessVerklExtCodeHashOpcode(t *testing.T) { + var ( + config = ¶ms.ChainConfig{ + ChainID: big.NewInt(69421), + 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), + Ethash: new(params.EthashConfig), + ShanghaiTime: u64(0), + PragueTime: u64(0), + TerminalTotalDifficulty: common.Big0, + TerminalTotalDifficultyPassed: true, + ProofInBlocks: true, + } + signer = types.LatestSigner(config) + testKey, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291") + bcdb = rawdb.NewMemoryDatabase() // Database for the blockchain + gendb = rawdb.NewMemoryDatabase() // Database for the block-generation code, they must be separate as they are path-based. + coinbase = common.HexToAddress("0x71562b71999873DB5b286dF957af199Ec94617F7") + account1 = common.HexToAddress("0x687704DB07e902e9A8B3754031D168D46E3D586e") + account2 = common.HexToAddress("0x6177843db3138ae69679A54b95cf345ED759450d") + gspec = &Genesis{ + Config: config, + Alloc: GenesisAlloc{ + coinbase: GenesisAccount{ + Balance: big.NewInt(1000000000000000000), // 1 ether + Nonce: 0, + }, + account1: GenesisAccount{ + Balance: big.NewInt(1000000000000000000), // 1 ether + Nonce: 0, + }, + account2: GenesisAccount{ + Balance: big.NewInt(1000000000000000000), // 1 ether + Nonce: 3, + }, + }, + } + ) + // Verkle trees use the snapshot, which must be enabled before the + // data is saved into the tree+database. + genesis := gspec.MustCommit(bcdb) + + // Commit the genesis block to the block-generation database as it + // is now independent of the blockchain database. + gspec.MustCommit(gendb) + + contractAddr := common.HexToAddress("3a220f351252089d385b29beca14e27f204c296a") + _, _, _, statediff := GenerateVerkleChain(gspec.Config, genesis, beacon.New(ethash.NewFaker()), gendb, 1, func(i int, gen *BlockGen) { + gen.SetPoS() + txData := []byte{ + 0x60, 22, // PUSH1 22 + 0x60, 12, // PUSH1 12 + 0x60, 0x00, // PUSH1 0 + 0x39, // CODECOPY + + 0x60, 22, // PUSH1 22 + 0x60, 0x00, // PUSH1 0 + 0xF3, // RETURN + + // Contract that auto-calls EXTCODEHASH + 0x73, // PUSH20 + 0x3a, 0x22, 0x0f, 0x35, 0x12, 0x52, 0x08, 0x9d, 0x38, 0x5b, 0x29, 0xbe, 0xca, 0x14, 0xe2, 0x7f, 0x20, 0x4c, 0x29, 0x6a, + 0x3F, // EXTCODEHASH + } + // Create dummy contract. + tx, _ := types.SignTx(types.NewContractCreation(0, big.NewInt(0), 100_000, big.NewInt(875000000), txData), signer, testKey) + gen.AddTx(tx) + + tx, _ = types.SignTx(types.NewTransaction(1, contractAddr, big.NewInt(0), 100_000, big.NewInt(875000000), nil), signer, testKey) + gen.AddTx(tx) + + }) + + contractKeccakTreeKey := utils.GetTreeKeyCodeKeccak(contractAddr[:]) + + var stateDiffIdx = -1 + for i, stemStateDiff := range statediff[0] { + if bytes.Equal(stemStateDiff.Stem[:], contractKeccakTreeKey[:31]) { + stateDiffIdx = i + break + } + } + if stateDiffIdx == -1 { + t.Fatalf("no state diff found for account2 header") + } + + var one [32]byte + one[31] = 1 + versionStateDiff := statediff[0][stateDiffIdx].SuffixDiffs[0] + if versionStateDiff.Suffix != utils.VersionLeafKey { + t.Fatalf("invalid suffix diff") + } + if versionStateDiff.CurrentValue == nil { + t.Fatalf("invalid current value") + } + if *versionStateDiff.CurrentValue == one { + t.Fatalf("invalid current value") + } + if versionStateDiff.NewValue != nil { + t.Fatalf("invalid new value") + } + + var zero [32]byte + codeHashStateDiff := statediff[0][stateDiffIdx].SuffixDiffs[0] + if codeHashStateDiff.Suffix != utils.CodeKeccakLeafKey { + t.Fatalf("invalid suffix diff") + } + if codeHashStateDiff.CurrentValue == nil { + t.Fatalf("invalid current value") + } + if *codeHashStateDiff.CurrentValue == zero { + t.Fatalf("invalid current value") + } + if codeHashStateDiff.NewValue != nil { + t.Fatalf("invalid new value") + } +} From 92fadfeac6f25072c3992438dd19ce2050626926 Mon Sep 17 00:00:00 2001 From: Ignacio Hagopian Date: Thu, 8 Feb 2024 12:58:26 -0300 Subject: [PATCH 3/4] add test for access witness EXTCODEHASH Signed-off-by: Ignacio Hagopian --- core/state_processor_test.go | 100 +++++++++++++++++++++-------------- core/vm/instructions.go | 10 ++-- 2 files changed, 65 insertions(+), 45 deletions(-) diff --git a/core/state_processor_test.go b/core/state_processor_test.go index 434f90b636b5..051df48e1afb 100644 --- a/core/state_processor_test.go +++ b/core/state_processor_test.go @@ -851,74 +851,94 @@ func TestProcessVerklExtCodeHashOpcode(t *testing.T) { // is now independent of the blockchain database. gspec.MustCommit(gendb) - contractAddr := common.HexToAddress("3a220f351252089d385b29beca14e27f204c296a") - _, _, _, statediff := GenerateVerkleChain(gspec.Config, genesis, beacon.New(ethash.NewFaker()), gendb, 1, func(i int, gen *BlockGen) { + dummyContract := []byte{ + 0x60, 2, // PUSH1 2 + 0x60, 12, // PUSH1 12 + 0x60, 0x00, // PUSH1 0 + 0x39, // CODECOPY + + 0x60, 2, // PUSH1 2 + 0x60, 0x00, // PUSH1 0 + 0xF3, // RETURN + + // Contract that auto-calls EXTCODEHASH + 0x60, 42, // PUSH1 42 + } + dummyContractAddr := common.HexToAddress("3a220f351252089d385b29beca14e27f204c296a") + extCodeHashContract := []byte{ + 0x60, 22, // PUSH1 22 + 0x60, 12, // PUSH1 12 + 0x60, 0x00, // PUSH1 0 + 0x39, // CODECOPY + + 0x60, 22, // PUSH1 22 + 0x60, 0x00, // PUSH1 0 + 0xF3, // RETURN + + // Contract that auto-calls EXTCODEHASH + 0x73, // PUSH20 + 0x3a, 0x22, 0x0f, 0x35, 0x12, 0x52, 0x08, 0x9d, 0x38, 0x5b, 0x29, 0xbe, 0xca, 0x14, 0xe2, 0x7f, 0x20, 0x4c, 0x29, 0x6a, + 0x3F, // EXTCODEHASH + } + extCodeHashContractAddr := common.HexToAddress("db7d6ab1f17c6b31909ae466702703daef9269cf") + _, _, _, statediff := GenerateVerkleChain(gspec.Config, genesis, beacon.New(ethash.NewFaker()), gendb, 2, func(i int, gen *BlockGen) { gen.SetPoS() - txData := []byte{ - 0x60, 22, // PUSH1 22 - 0x60, 12, // PUSH1 12 - 0x60, 0x00, // PUSH1 0 - 0x39, // CODECOPY - - 0x60, 22, // PUSH1 22 - 0x60, 0x00, // PUSH1 0 - 0xF3, // RETURN - - // Contract that auto-calls EXTCODEHASH - 0x73, // PUSH20 - 0x3a, 0x22, 0x0f, 0x35, 0x12, 0x52, 0x08, 0x9d, 0x38, 0x5b, 0x29, 0xbe, 0xca, 0x14, 0xe2, 0x7f, 0x20, 0x4c, 0x29, 0x6a, - 0x3F, // EXTCODEHASH - } - // Create dummy contract. - tx, _ := types.SignTx(types.NewContractCreation(0, big.NewInt(0), 100_000, big.NewInt(875000000), txData), signer, testKey) - gen.AddTx(tx) - tx, _ = types.SignTx(types.NewTransaction(1, contractAddr, big.NewInt(0), 100_000, big.NewInt(875000000), nil), signer, testKey) - gen.AddTx(tx) + if i == 0 { + // Create dummy contract. + tx, _ := types.SignTx(types.NewContractCreation(0, big.NewInt(0), 100_000, big.NewInt(875000000), dummyContract), signer, testKey) + gen.AddTx(tx) + + // Create contract with EXTCODEHASH opcode. + tx, _ = types.SignTx(types.NewContractCreation(1, big.NewInt(0), 100_000, big.NewInt(875000000), extCodeHashContract), signer, testKey) + gen.AddTx(tx) + } else { + tx, _ := types.SignTx(types.NewTransaction(2, extCodeHashContractAddr, big.NewInt(0), 100_000, big.NewInt(875000000), nil), signer, testKey) + gen.AddTx(tx) + } }) - contractKeccakTreeKey := utils.GetTreeKeyCodeKeccak(contractAddr[:]) + contractKeccakTreeKey := utils.GetTreeKeyCodeKeccak(dummyContractAddr[:]) var stateDiffIdx = -1 - for i, stemStateDiff := range statediff[0] { + for i, stemStateDiff := range statediff[1] { if bytes.Equal(stemStateDiff.Stem[:], contractKeccakTreeKey[:31]) { stateDiffIdx = i break } } if stateDiffIdx == -1 { - t.Fatalf("no state diff found for account2 header") + t.Fatalf("no state diff found for stem") } - var one [32]byte - one[31] = 1 - versionStateDiff := statediff[0][stateDiffIdx].SuffixDiffs[0] + var zero [32]byte + versionStateDiff := statediff[1][stateDiffIdx].SuffixDiffs[0] if versionStateDiff.Suffix != utils.VersionLeafKey { - t.Fatalf("invalid suffix diff") + t.Fatalf("version invalid suffix") } if versionStateDiff.CurrentValue == nil { - t.Fatalf("invalid current value") + t.Fatalf("version.CurrentValue must not be nil") } - if *versionStateDiff.CurrentValue == one { - t.Fatalf("invalid current value") + if *versionStateDiff.CurrentValue != zero { + t.Fatalf("version.CurrentValue must be zero") } if versionStateDiff.NewValue != nil { - t.Fatalf("invalid new value") + t.Fatalf("version.NewValue must be nil") } - var zero [32]byte - codeHashStateDiff := statediff[0][stateDiffIdx].SuffixDiffs[0] + codeHashStateDiff := statediff[1][stateDiffIdx].SuffixDiffs[1] if codeHashStateDiff.Suffix != utils.CodeKeccakLeafKey { - t.Fatalf("invalid suffix diff") + t.Fatalf("code hash invalid suffix") } if codeHashStateDiff.CurrentValue == nil { - t.Fatalf("invalid current value") + t.Fatalf("codeHash.CurrentValue must not be empty") } - if *codeHashStateDiff.CurrentValue == zero { - t.Fatalf("invalid current value") + expCodeHash := crypto.Keccak256Hash(dummyContract[12:]) + if *codeHashStateDiff.CurrentValue != expCodeHash { + t.Fatalf("codeHash.CurrentValue unexpected code hash") } if codeHashStateDiff.NewValue != nil { - t.Fatalf("invalid new value") + t.Fatalf("codeHash.NewValue must be nil") } } diff --git a/core/vm/instructions.go b/core/vm/instructions.go index 78c8cbbc8c8f..d80abf9739f3 100644 --- a/core/vm/instructions.go +++ b/core/vm/instructions.go @@ -485,11 +485,6 @@ func opExtCodeCopy(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) func opExtCodeHash(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) { slot := scope.Stack.peek() address := common.Address(slot.Bytes20()) - if interpreter.evm.StateDB.Empty(address) { - slot.Clear() - } else { - slot.SetBytes(interpreter.evm.StateDB.GetCodeHash(address).Bytes()) - } if interpreter.evm.chainRules.IsPrague { statelessGas := interpreter.evm.Accesses.TouchAddressOnReadAndComputeGas(slot.Bytes(), uint256.Int{}, trieUtils.VersionLeafKey) statelessGas += interpreter.evm.Accesses.TouchAddressOnReadAndComputeGas(slot.Bytes(), uint256.Int{}, trieUtils.CodeKeccakLeafKey) @@ -498,6 +493,11 @@ func opExtCodeHash(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) return nil, ErrOutOfGas } } + if interpreter.evm.StateDB.Empty(address) { + slot.Clear() + } else { + slot.SetBytes(interpreter.evm.StateDB.GetCodeHash(address).Bytes()) + } return nil, nil } From e4008607ff66201585752eb9730f96c29d84d78b Mon Sep 17 00:00:00 2001 From: Ignacio Hagopian Date: Fri, 9 Feb 2024 13:51:53 -0300 Subject: [PATCH 4/4] do not touch version Signed-off-by: Ignacio Hagopian --- core/state_processor_test.go | 17 +---------------- core/vm/instructions.go | 3 +-- 2 files changed, 2 insertions(+), 18 deletions(-) diff --git a/core/state_processor_test.go b/core/state_processor_test.go index 051df48e1afb..1756daaf8c66 100644 --- a/core/state_processor_test.go +++ b/core/state_processor_test.go @@ -912,22 +912,7 @@ func TestProcessVerklExtCodeHashOpcode(t *testing.T) { t.Fatalf("no state diff found for stem") } - var zero [32]byte - versionStateDiff := statediff[1][stateDiffIdx].SuffixDiffs[0] - if versionStateDiff.Suffix != utils.VersionLeafKey { - t.Fatalf("version invalid suffix") - } - if versionStateDiff.CurrentValue == nil { - t.Fatalf("version.CurrentValue must not be nil") - } - if *versionStateDiff.CurrentValue != zero { - t.Fatalf("version.CurrentValue must be zero") - } - if versionStateDiff.NewValue != nil { - t.Fatalf("version.NewValue must be nil") - } - - codeHashStateDiff := statediff[1][stateDiffIdx].SuffixDiffs[1] + codeHashStateDiff := statediff[1][stateDiffIdx].SuffixDiffs[0] if codeHashStateDiff.Suffix != utils.CodeKeccakLeafKey { t.Fatalf("code hash invalid suffix") } diff --git a/core/vm/instructions.go b/core/vm/instructions.go index d80abf9739f3..35d9ccc62197 100644 --- a/core/vm/instructions.go +++ b/core/vm/instructions.go @@ -486,8 +486,7 @@ func opExtCodeHash(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) slot := scope.Stack.peek() address := common.Address(slot.Bytes20()) if interpreter.evm.chainRules.IsPrague { - statelessGas := interpreter.evm.Accesses.TouchAddressOnReadAndComputeGas(slot.Bytes(), uint256.Int{}, trieUtils.VersionLeafKey) - statelessGas += interpreter.evm.Accesses.TouchAddressOnReadAndComputeGas(slot.Bytes(), uint256.Int{}, trieUtils.CodeKeccakLeafKey) + statelessGas := interpreter.evm.Accesses.TouchAddressOnReadAndComputeGas(slot.Bytes(), uint256.Int{}, trieUtils.CodeKeccakLeafKey) if !scope.Contract.UseGas(statelessGas) { scope.Contract.Gas = 0 return nil, ErrOutOfGas