From abfdbca7b2f6157a13383281cb372d80d8a432e1 Mon Sep 17 00:00:00 2001 From: Somnath Banerjee Date: Fri, 19 Apr 2024 03:53:30 +0400 Subject: [PATCH 01/18] Add 2935 draft --- consensus/merge/merge.go | 3 +++ consensus/misc/eip2935.go | 23 +++++++++++++++++++++++ core/vm/instructions.go | 11 +++++++++++ params/protocol_params.go | 3 +++ 4 files changed, 40 insertions(+) create mode 100644 consensus/misc/eip2935.go diff --git a/consensus/merge/merge.go b/consensus/merge/merge.go index c5544ef33bd..1e77898f33a 100644 --- a/consensus/merge/merge.go +++ b/consensus/merge/merge.go @@ -282,6 +282,9 @@ func (s *Merge) Initialize(config *chain.Config, chain consensus.ChainHeaderRead return syscall(addr, data, state, header, false /* constCall */) }) } + if chain.Config().IsPrague(header.Time) { + misc.StoreBlockHash2935(header, state) + } } func (s *Merge) APIs(chain consensus.ChainHeaderReader) []rpc.API { diff --git a/consensus/misc/eip2935.go b/consensus/misc/eip2935.go new file mode 100644 index 00000000000..733814c6122 --- /dev/null +++ b/consensus/misc/eip2935.go @@ -0,0 +1,23 @@ +package misc + +import ( + "math/big" + + "github.com/holiman/uint256" + + libcommon "github.com/ledgerwatch/erigon-lib/common" + "github.com/ledgerwatch/erigon/core/state" + "github.com/ledgerwatch/erigon/core/types" + "github.com/ledgerwatch/erigon/params" +) + +func StoreBlockHash2935(header *types.Header, state *state.IntraBlockState ) { + // EIP-2935 + // TODO @somnathb1 Hash the slot? + storageSlot := libcommon.BigToHash(big.NewInt(0).Sub(header.Number, big.NewInt(1))) + parentHashInt, err := uint256.FromHex(header.ParentHash.String()) + if err !=nil { + panic(err) // should not happen + } + state.SetState(params.HistoryStorageAddress, &storageSlot, *parentHashInt) +} diff --git a/core/vm/instructions.go b/core/vm/instructions.go index b35de6adee6..35ffb067a8b 100644 --- a/core/vm/instructions.go +++ b/core/vm/instructions.go @@ -473,6 +473,17 @@ func opBlockhash(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ( } var upper, lower uint64 upper = interpreter.evm.Context.BlockNumber + if interpreter.evm.chainRules.IsPrague { + if !num.Lt(uint256.MustFromBig(interpreter.evm.chainConfig.PragueTime)) && num.Lt(uint256.NewInt(upper)){ + var out *uint256.Int + interpreter.evm.intraBlockState.GetState(params.HistoryStorageAddress, (*libcommon.Hash)(num.Bytes()), out) + scope.Stack.Push(out) + } else { + scope.Stack.Push(uint256.NewInt(0)) + } + return nil, nil + } + if upper < 257 { lower = 0 } else { diff --git a/params/protocol_params.go b/params/protocol_params.go index d760de8658d..515aefbf956 100644 --- a/params/protocol_params.go +++ b/params/protocol_params.go @@ -176,6 +176,9 @@ const ( // EIP-4788: Beacon block root in the EVM var BeaconRootsAddress = common.HexToAddress("0x000F3df6D732807Ef1319fB7B8bB8522d0Beac02") +// EIP-2935: Historical block hashes in state +var HistoryStorageAddress = common.HexToAddress("0xfffffffffffffffffffffffffffffffffffffffe") + // Gas discount table for BLS12-381 G1 and G2 multi exponentiation operations var Bls12381MultiExpDiscountTable = [128]uint64{1200, 888, 764, 641, 594, 547, 500, 453, 438, 423, 408, 394, 379, 364, 349, 334, 330, 326, 322, 318, 314, 310, 306, 302, 298, 294, 289, 285, 281, 277, 273, 269, 268, 266, 265, 263, 262, 260, 259, 257, 256, 254, 253, 251, 250, 248, 247, 245, 244, 242, 241, 239, 238, 236, 235, 233, 232, 231, 229, 228, 226, 225, 223, 222, 221, 220, 219, 219, 218, 217, 216, 216, 215, 214, 213, 213, 212, 211, 211, 210, 209, 208, 208, 207, 206, 205, 205, 204, 203, 202, 202, 201, 200, 199, 199, 198, 197, 196, 196, 195, 194, 193, 193, 192, 191, 191, 190, 189, 188, 188, 187, 186, 185, 185, 184, 183, 182, 182, 181, 180, 179, 179, 178, 177, 176, 176, 175, 174} From 071ec48c5380970bda4a16cb5b36c5bf2c26b1f9 Mon Sep 17 00:00:00 2001 From: Somnath Banerjee Date: Mon, 29 Apr 2024 02:58:54 +0400 Subject: [PATCH 02/18] Update with latest eip --- consensus/merge/merge.go | 2 +- consensus/misc/eip2935.go | 31 +++++++++++++++++++++++-------- core/vm/instructions.go | 28 ++++++++++++++++------------ params/protocol_params.go | 6 +++++- 4 files changed, 45 insertions(+), 22 deletions(-) diff --git a/consensus/merge/merge.go b/consensus/merge/merge.go index 1e77898f33a..823be69a219 100644 --- a/consensus/merge/merge.go +++ b/consensus/merge/merge.go @@ -283,7 +283,7 @@ func (s *Merge) Initialize(config *chain.Config, chain consensus.ChainHeaderRead }) } if chain.Config().IsPrague(header.Time) { - misc.StoreBlockHash2935(header, state) + misc.StoreBlockHashesEip2935(header, state, config, chain) } } diff --git a/consensus/misc/eip2935.go b/consensus/misc/eip2935.go index 733814c6122..1878d0a1b46 100644 --- a/consensus/misc/eip2935.go +++ b/consensus/misc/eip2935.go @@ -5,19 +5,34 @@ import ( "github.com/holiman/uint256" + "github.com/ledgerwatch/erigon-lib/chain" libcommon "github.com/ledgerwatch/erigon-lib/common" + "github.com/ledgerwatch/erigon/consensus" "github.com/ledgerwatch/erigon/core/state" "github.com/ledgerwatch/erigon/core/types" "github.com/ledgerwatch/erigon/params" ) -func StoreBlockHash2935(header *types.Header, state *state.IntraBlockState ) { - // EIP-2935 - // TODO @somnathb1 Hash the slot? - storageSlot := libcommon.BigToHash(big.NewInt(0).Sub(header.Number, big.NewInt(1))) - parentHashInt, err := uint256.FromHex(header.ParentHash.String()) - if err !=nil { - panic(err) // should not happen +func StoreBlockHashesEip2935(header *types.Header, state *state.IntraBlockState, config *chain.Config, headerReader consensus.ChainHeaderReader) { + parent := headerReader.GetHeaderByHash(header.ParentHash) + if parent == nil && header.Number.Cmp(big.NewInt(0)) == 0 { // Only Genesis block shouldn't have a parent + return + } + _setStorage(parent.Number, parent.Hash(), state) + // If this is the fork block, add the parent's direct `HISTORY_SERVE_WINDOW - 1` ancestors as well + if parent.Time < config.OsakaTime.Uint64() { + for i := params.HISTORY_SERVE_WINDOW - 1; i > 0; i-- { + parent = headerReader.GetHeaderByHash(parent.ParentHash) + _setStorage(parent.Number, parent.Hash(), state) + if parent.Number.Cmp(big.NewInt(0)) == 0 { // Genesis + break + } } - state.SetState(params.HistoryStorageAddress, &storageSlot, *parentHashInt) + } +} + +func _setStorage(num *big.Int, hash libcommon.Hash, state *state.IntraBlockState) { + storageSlot := libcommon.BigToHash(big.NewInt(0).Mod(num, big.NewInt(8192))) + parentHashInt := uint256.MustFromHex(hash.String()) + state.SetState(params.HistoryStorageAddress, &storageSlot, *parentHashInt) } diff --git a/core/vm/instructions.go b/core/vm/instructions.go index 35ffb067a8b..78ae73c36db 100644 --- a/core/vm/instructions.go +++ b/core/vm/instructions.go @@ -465,34 +465,38 @@ func opGasprice(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([ } func opBlockhash(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) { - num := scope.Stack.Peek() - num64, overflow := num.Uint64WithOverflow() + arg := scope.Stack.Peek() + arg64, overflow := arg.Uint64WithOverflow() if overflow { - num.Clear() + arg.Clear() return nil, nil } var upper, lower uint64 upper = interpreter.evm.Context.BlockNumber if interpreter.evm.chainRules.IsPrague { - if !num.Lt(uint256.MustFromBig(interpreter.evm.chainConfig.PragueTime)) && num.Lt(uint256.NewInt(upper)){ + if arg64 >= upper || arg64+params.HISTORY_SERVE_WINDOW < upper { + scope.Stack.Push(uint256.NewInt(0)) + } else { var out *uint256.Int - interpreter.evm.intraBlockState.GetState(params.HistoryStorageAddress, (*libcommon.Hash)(num.Bytes()), out) + interpreter.evm.intraBlockState.GetState( + params.HistoryStorageAddress, + (*libcommon.Hash)(uint256.NewInt(arg64%params.HISTORY_SERVE_WINDOW).Bytes()), + out, + ) scope.Stack.Push(out) - } else { - scope.Stack.Push(uint256.NewInt(0)) } return nil, nil } - if upper < 257 { + if upper <= params.BLOCKHASH_OLD_WINDOW { lower = 0 } else { - lower = upper - 256 + lower = upper - params.BLOCKHASH_OLD_WINDOW } - if num64 >= lower && num64 < upper { - num.SetBytes(interpreter.evm.Context.GetHash(num64).Bytes()) + if arg64 >= lower && arg64 < upper { + arg.SetBytes(interpreter.evm.Context.GetHash(arg64).Bytes()) } else { - num.Clear() + arg.Clear() } return nil, nil } diff --git a/params/protocol_params.go b/params/protocol_params.go index 515aefbf956..dcbeb354190 100644 --- a/params/protocol_params.go +++ b/params/protocol_params.go @@ -171,13 +171,17 @@ const ( // PIP-27: secp256r1 elliptic curve signature verifier gas price P256VerifyGas uint64 = 3450 + + // EIP-2935 + HISTORY_SERVE_WINDOW uint64 = 8192 + BLOCKHASH_OLD_WINDOW uint64 = 256 ) // EIP-4788: Beacon block root in the EVM var BeaconRootsAddress = common.HexToAddress("0x000F3df6D732807Ef1319fB7B8bB8522d0Beac02") // EIP-2935: Historical block hashes in state -var HistoryStorageAddress = common.HexToAddress("0xfffffffffffffffffffffffffffffffffffffffe") +var HistoryStorageAddress = common.HexToAddress("0x25a219378dad9b3503c8268c9ca836a52427a4fb") // Gas discount table for BLS12-381 G1 and G2 multi exponentiation operations var Bls12381MultiExpDiscountTable = [128]uint64{1200, 888, 764, 641, 594, 547, 500, 453, 438, 423, 408, 394, 379, 364, 349, 334, 330, 326, 322, 318, 314, 310, 306, 302, 298, 294, 289, 285, 281, 277, 273, 269, 268, 266, 265, 263, 262, 260, 259, 257, 256, 254, 253, 251, 250, 248, 247, 245, 244, 242, 241, 239, 238, 236, 235, 233, 232, 231, 229, 228, 226, 225, 223, 222, 221, 220, 219, 219, 218, 217, 216, 216, 215, 214, 213, 213, 212, 211, 211, 210, 209, 208, 208, 207, 206, 205, 205, 204, 203, 202, 202, 201, 200, 199, 199, 198, 197, 196, 196, 195, 194, 193, 193, 192, 191, 191, 190, 189, 188, 188, 187, 186, 185, 185, 184, 183, 182, 182, 181, 180, 179, 179, 178, 177, 176, 176, 175, 174} From 61a6d81172d25c670efab533ca4730a67b677b97 Mon Sep 17 00:00:00 2001 From: Somnath Banerjee Date: Mon, 29 Apr 2024 04:13:48 +0400 Subject: [PATCH 03/18] Temporary test fix --- core/vm/runtime/runtime.go | 4 ++-- core/vm/runtime/runtime_test.go | 6 ++++-- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/core/vm/runtime/runtime.go b/core/vm/runtime/runtime.go index 032d1b2e4d9..73360d9a1fa 100644 --- a/core/vm/runtime/runtime.go +++ b/core/vm/runtime/runtime.go @@ -73,8 +73,8 @@ func setDefaults(cfg *Config) { GrayGlacierBlock: new(big.Int), ShanghaiTime: new(big.Int), CancunTime: new(big.Int), - PragueTime: new(big.Int), - OsakaTime: new(big.Int), + PragueTime: big.NewInt(1), + OsakaTime: big.NewInt(2), } } diff --git a/core/vm/runtime/runtime_test.go b/core/vm/runtime/runtime_test.go index 1e326eea237..c47767b1fe6 100644 --- a/core/vm/runtime/runtime_test.go +++ b/core/vm/runtime/runtime_test.go @@ -313,10 +313,12 @@ func TestBlockhash(t *testing.T) { // The method call to 'test()' input := libcommon.Hex2Bytes("f8a8fd6d") chain := &dummyChain{} - ret, _, err := Execute(data, input, &Config{ + cfg := &Config{ GetHashFn: core.GetHashFn(header, chain.GetHeader), BlockNumber: new(big.Int).Set(header.Number), - }, header.Number.Uint64()) + Time: new(big.Int), + } + ret, _, err := Execute(data, input, cfg, header.Number.Uint64()) if err != nil { t.Fatalf("expected no error, got %v", err) } From 4bb2f74860b892e7e2cd564143b3dfc007c187b8 Mon Sep 17 00:00:00 2001 From: Somnath Banerjee Date: Mon, 29 Apr 2024 04:24:49 +0400 Subject: [PATCH 04/18] Update naming --- consensus/misc/eip2935.go | 2 +- core/vm/instructions.go | 8 ++++---- core/vm/runtime/runtime_test.go | 2 +- params/protocol_params.go | 4 ++-- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/consensus/misc/eip2935.go b/consensus/misc/eip2935.go index 1878d0a1b46..a0beeb3d1ea 100644 --- a/consensus/misc/eip2935.go +++ b/consensus/misc/eip2935.go @@ -21,7 +21,7 @@ func StoreBlockHashesEip2935(header *types.Header, state *state.IntraBlockState, _setStorage(parent.Number, parent.Hash(), state) // If this is the fork block, add the parent's direct `HISTORY_SERVE_WINDOW - 1` ancestors as well if parent.Time < config.OsakaTime.Uint64() { - for i := params.HISTORY_SERVE_WINDOW - 1; i > 0; i-- { + for i := params.BlockHashServeWindow - 1; i > 0; i-- { parent = headerReader.GetHeaderByHash(parent.ParentHash) _setStorage(parent.Number, parent.Hash(), state) if parent.Number.Cmp(big.NewInt(0)) == 0 { // Genesis diff --git a/core/vm/instructions.go b/core/vm/instructions.go index 78ae73c36db..fea69055e5c 100644 --- a/core/vm/instructions.go +++ b/core/vm/instructions.go @@ -474,13 +474,13 @@ func opBlockhash(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ( var upper, lower uint64 upper = interpreter.evm.Context.BlockNumber if interpreter.evm.chainRules.IsPrague { - if arg64 >= upper || arg64+params.HISTORY_SERVE_WINDOW < upper { + if arg64 >= upper || arg64+params.BlockHashServeWindow < upper { scope.Stack.Push(uint256.NewInt(0)) } else { var out *uint256.Int interpreter.evm.intraBlockState.GetState( params.HistoryStorageAddress, - (*libcommon.Hash)(uint256.NewInt(arg64%params.HISTORY_SERVE_WINDOW).Bytes()), + (*libcommon.Hash)(uint256.NewInt(arg64%params.BlockHashServeWindow).Bytes()), out, ) scope.Stack.Push(out) @@ -488,10 +488,10 @@ func opBlockhash(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ( return nil, nil } - if upper <= params.BLOCKHASH_OLD_WINDOW { + if upper <= params.BlockHashOldWindow { lower = 0 } else { - lower = upper - params.BLOCKHASH_OLD_WINDOW + lower = upper - params.BlockHashOldWindow } if arg64 >= lower && arg64 < upper { arg.SetBytes(interpreter.evm.Context.GetHash(arg64).Bytes()) diff --git a/core/vm/runtime/runtime_test.go b/core/vm/runtime/runtime_test.go index c47767b1fe6..30b0824a96f 100644 --- a/core/vm/runtime/runtime_test.go +++ b/core/vm/runtime/runtime_test.go @@ -316,7 +316,7 @@ func TestBlockhash(t *testing.T) { cfg := &Config{ GetHashFn: core.GetHashFn(header, chain.GetHeader), BlockNumber: new(big.Int).Set(header.Number), - Time: new(big.Int), + Time: new(big.Int), } ret, _, err := Execute(data, input, cfg, header.Number.Uint64()) if err != nil { diff --git a/params/protocol_params.go b/params/protocol_params.go index dcbeb354190..6c047975f70 100644 --- a/params/protocol_params.go +++ b/params/protocol_params.go @@ -173,8 +173,8 @@ const ( P256VerifyGas uint64 = 3450 // EIP-2935 - HISTORY_SERVE_WINDOW uint64 = 8192 - BLOCKHASH_OLD_WINDOW uint64 = 256 + BlockHashServeWindow uint64 = 8192 + BlockHashOldWindow uint64 = 256 ) // EIP-4788: Beacon block root in the EVM From 007b006baf5144d21fc373c4c3691d5e17064933 Mon Sep 17 00:00:00 2001 From: Somnath Banerjee Date: Mon, 29 Apr 2024 15:40:49 +0400 Subject: [PATCH 05/18] Optimize --- consensus/misc/eip2935.go | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/consensus/misc/eip2935.go b/consensus/misc/eip2935.go index a0beeb3d1ea..e116a730b01 100644 --- a/consensus/misc/eip2935.go +++ b/consensus/misc/eip2935.go @@ -14,25 +14,27 @@ import ( ) func StoreBlockHashesEip2935(header *types.Header, state *state.IntraBlockState, config *chain.Config, headerReader consensus.ChainHeaderReader) { - parent := headerReader.GetHeaderByHash(header.ParentHash) - if parent == nil && header.Number.Cmp(big.NewInt(0)) == 0 { // Only Genesis block shouldn't have a parent + if header.Number.Cmp(big.NewInt(0)) == 0 { // Activation of fork at Genesis return } - _setStorage(parent.Number, parent.Hash(), state) + parent := headerReader.GetHeaderByHash(header.ParentHash) + _setStorage(parent.Number, header.ParentHash, state) // If this is the fork block, add the parent's direct `HISTORY_SERVE_WINDOW - 1` ancestors as well if parent.Time < config.OsakaTime.Uint64() { - for i := params.BlockHashServeWindow - 1; i > 0; i-- { + p := parent.Number.Uint64() + window := params.BlockHashHistoryServeWindow - 1 + if p < window { + window = p + } + for i := window - 1; i >= 0; i-- { + _setStorage(big.NewInt(0).Sub(parent.Number, big.NewInt(1)), parent.ParentHash, state) parent = headerReader.GetHeaderByHash(parent.ParentHash) - _setStorage(parent.Number, parent.Hash(), state) - if parent.Number.Cmp(big.NewInt(0)) == 0 { // Genesis - break - } } } } func _setStorage(num *big.Int, hash libcommon.Hash, state *state.IntraBlockState) { - storageSlot := libcommon.BigToHash(big.NewInt(0).Mod(num, big.NewInt(8192))) + storageSlot := libcommon.BigToHash(big.NewInt(0).Mod(num, big.NewInt(int64(params.BlockHashHistoryServeWindow)))) parentHashInt := uint256.MustFromHex(hash.String()) state.SetState(params.HistoryStorageAddress, &storageSlot, *parentHashInt) } From bcab9804b7f1c1fc9941047c895bebd1b0628786 Mon Sep 17 00:00:00 2001 From: Somnath Banerjee Date: Mon, 29 Apr 2024 15:41:11 +0400 Subject: [PATCH 06/18] Rename param --- core/vm/instructions.go | 4 ++-- params/protocol_params.go | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/core/vm/instructions.go b/core/vm/instructions.go index fea69055e5c..1409c6d05c2 100644 --- a/core/vm/instructions.go +++ b/core/vm/instructions.go @@ -474,13 +474,13 @@ func opBlockhash(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ( var upper, lower uint64 upper = interpreter.evm.Context.BlockNumber if interpreter.evm.chainRules.IsPrague { - if arg64 >= upper || arg64+params.BlockHashServeWindow < upper { + if arg64 >= upper || arg64+params.BlockHashHistoryServeWindow < upper { scope.Stack.Push(uint256.NewInt(0)) } else { var out *uint256.Int interpreter.evm.intraBlockState.GetState( params.HistoryStorageAddress, - (*libcommon.Hash)(uint256.NewInt(arg64%params.BlockHashServeWindow).Bytes()), + (*libcommon.Hash)(uint256.NewInt(arg64%params.BlockHashHistoryServeWindow).Bytes()), out, ) scope.Stack.Push(out) diff --git a/params/protocol_params.go b/params/protocol_params.go index 6c047975f70..99387bdab0d 100644 --- a/params/protocol_params.go +++ b/params/protocol_params.go @@ -173,8 +173,8 @@ const ( P256VerifyGas uint64 = 3450 // EIP-2935 - BlockHashServeWindow uint64 = 8192 - BlockHashOldWindow uint64 = 256 + BlockHashHistoryServeWindow uint64 = 8192 + BlockHashOldWindow uint64 = 256 ) // EIP-4788: Beacon block root in the EVM From 45bc07ee16a0a18e03f9a1c05ba1ded83d85c1cf Mon Sep 17 00:00:00 2001 From: Somnath Banerjee Date: Tue, 30 Apr 2024 13:28:56 +0400 Subject: [PATCH 07/18] Save --- consensus/misc/eip2935.go | 15 ++-- core/vm/instructions.go | 17 ++-- core/vm/runtime/runtime.go | 1 - core/vm/runtime/runtime_test.go | 132 +++++++++++++++++++++++++++++++- 4 files changed, 151 insertions(+), 14 deletions(-) diff --git a/consensus/misc/eip2935.go b/consensus/misc/eip2935.go index e116a730b01..fd535a77812 100644 --- a/consensus/misc/eip2935.go +++ b/consensus/misc/eip2935.go @@ -1,6 +1,7 @@ package misc import ( + // "fmt" "math/big" "github.com/holiman/uint256" @@ -18,23 +19,25 @@ func StoreBlockHashesEip2935(header *types.Header, state *state.IntraBlockState, return } parent := headerReader.GetHeaderByHash(header.ParentHash) - _setStorage(parent.Number, header.ParentHash, state) + _storeHash(parent.Number, header.ParentHash, state) // If this is the fork block, add the parent's direct `HISTORY_SERVE_WINDOW - 1` ancestors as well - if parent.Time < config.OsakaTime.Uint64() { + if parent.Time < config.PragueTime.Uint64() { p := parent.Number.Uint64() window := params.BlockHashHistoryServeWindow - 1 if p < window { window = p } - for i := window - 1; i >= 0; i-- { - _setStorage(big.NewInt(0).Sub(parent.Number, big.NewInt(1)), parent.ParentHash, state) + for i := window; i > 0; i-- { + _storeHash(big.NewInt(0).Sub(parent.Number, big.NewInt(1)), parent.ParentHash, state) parent = headerReader.GetHeaderByHash(parent.ParentHash) + // fmt.Println("Storing parent %x, for i=%d", parent.ParentHash, i) } } } -func _setStorage(num *big.Int, hash libcommon.Hash, state *state.IntraBlockState) { +func _storeHash(num *big.Int, hash libcommon.Hash, state *state.IntraBlockState) { storageSlot := libcommon.BigToHash(big.NewInt(0).Mod(num, big.NewInt(int64(params.BlockHashHistoryServeWindow)))) - parentHashInt := uint256.MustFromHex(hash.String()) + hh := hash.Big() + parentHashInt := uint256.MustFromBig(hh) state.SetState(params.HistoryStorageAddress, &storageSlot, *parentHashInt) } diff --git a/core/vm/instructions.go b/core/vm/instructions.go index 1409c6d05c2..022a9a41e0d 100644 --- a/core/vm/instructions.go +++ b/core/vm/instructions.go @@ -19,6 +19,7 @@ package vm import ( "fmt" "math" + "math/big" "github.com/holiman/uint256" libcommon "github.com/ledgerwatch/erigon-lib/common" @@ -475,15 +476,21 @@ func opBlockhash(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ( upper = interpreter.evm.Context.BlockNumber if interpreter.evm.chainRules.IsPrague { if arg64 >= upper || arg64+params.BlockHashHistoryServeWindow < upper { - scope.Stack.Push(uint256.NewInt(0)) + // scope.Stack.Push(uint256.NewInt(0)) + arg.Clear() } else { - var out *uint256.Int + // out := uint256.NewInt(0) + storageSlot := libcommon.BigToHash(big.NewInt(0).Mod(big.NewInt(int64(arg64)), big.NewInt(int64(params.BlockHashHistoryServeWindow)))) + + // storageSlot := libcommon.BytesToHash(uint256.NewInt(arg64%params.BlockHashHistoryServeWindow).Bytes()) interpreter.evm.intraBlockState.GetState( params.HistoryStorageAddress, - (*libcommon.Hash)(uint256.NewInt(arg64%params.BlockHashHistoryServeWindow).Bytes()), - out, + &storageSlot, + // (*libcommon.Hash)(uint256.NewInt(arg64%params.BlockHashHistoryServeWindow).Bytes()), + arg, ) - scope.Stack.Push(out) + // arg.SetBytes(out.Bytes()) + // scope.Stack.Push(out) } return nil, nil } diff --git a/core/vm/runtime/runtime.go b/core/vm/runtime/runtime.go index 73360d9a1fa..0cdb2453385 100644 --- a/core/vm/runtime/runtime.go +++ b/core/vm/runtime/runtime.go @@ -112,7 +112,6 @@ func Execute(code, input []byte, cfg *Config, bn uint64) ([]byte, *state.IntraBl if cfg == nil { cfg = new(Config) } - setDefaults(cfg) externalState := cfg.State != nil var tx kv.RwTx diff --git a/core/vm/runtime/runtime_test.go b/core/vm/runtime/runtime_test.go index 30b0824a96f..5f55518cd1c 100644 --- a/core/vm/runtime/runtime_test.go +++ b/core/vm/runtime/runtime_test.go @@ -30,12 +30,15 @@ import ( "github.com/ledgerwatch/erigon/accounts/abi" "github.com/ledgerwatch/erigon/common" "github.com/ledgerwatch/erigon/consensus" + "github.com/ledgerwatch/erigon/consensus/misc" "github.com/ledgerwatch/erigon/core" "github.com/ledgerwatch/erigon/core/asm" "github.com/ledgerwatch/erigon/core/state" "github.com/ledgerwatch/erigon/core/types" "github.com/ledgerwatch/erigon/core/vm" "github.com/ledgerwatch/erigon/eth/tracers/logger" + "github.com/ledgerwatch/erigon/params" + "github.com/ledgerwatch/erigon/rlp" ) func TestDefaults(t *testing.T) { @@ -235,7 +238,7 @@ func fakeHeader(n uint64, parentHash libcommon.Hash) *types.Header { Coinbase: libcommon.HexToAddress("0x00000000000000000000000000000000deadbeef"), Number: big.NewInt(int64(n)), ParentHash: parentHash, - Time: 1000, + Time: n, Nonce: types.BlockNonce{0x1}, Extra: []byte{}, Difficulty: big.NewInt(0), @@ -244,6 +247,46 @@ func fakeHeader(n uint64, parentHash libcommon.Hash) *types.Header { return &header } +type FakeChainHeaderReader struct { + // Cfg *chain.Config + // current *types.Block +} + +func (cr *FakeChainHeaderReader) GetHeaderByHash(hash libcommon.Hash) *types.Header { + num := hash.Big() + return &types.Header{ + Coinbase: libcommon.HexToAddress("0x00000000000000000000000000000000deadbeef"), + Number: num, + ParentHash: libcommon.BigToHash(big.NewInt(0).Sub(num, big.NewInt(1))), + Time: num.Uint64(), + Nonce: types.BlockNonce{0x1}, + Extra: []byte{}, + Difficulty: big.NewInt(0), + GasLimit: 100000, + } +} +func (cr *FakeChainHeaderReader) GetHeaderByNumber(number uint64) *types.Header { + return cr.GetHeaderByHash(libcommon.BigToHash(big.NewInt(int64(number)))) +} +func (cr *FakeChainHeaderReader) Config() *chain.Config { return nil } +func (cr *FakeChainHeaderReader) CurrentHeader() *types.Header { return nil } +func (cr *FakeChainHeaderReader) GetHeader(hash libcommon.Hash, number uint64) *types.Header { + return nil +} +func (cr *FakeChainHeaderReader) GetBlock(hash libcommon.Hash, number uint64) *types.Block { + return nil +} +func (cr *FakeChainHeaderReader) HasBlock(hash libcommon.Hash, number uint64) bool { return false } +func (cr *FakeChainHeaderReader) GetTd(hash libcommon.Hash, number uint64) *big.Int { return nil } +func (cr *FakeChainHeaderReader) FrozenBlocks() uint64 { return 0 } +func (cr *FakeChainHeaderReader) BorEventsByBlock(hash libcommon.Hash, number uint64) []rlp.RawValue { + return nil +} +func (cr *FakeChainHeaderReader) BorStartEventID(hash libcommon.Hash, number uint64) uint64 { + return 0 +} +func (cr *FakeChainHeaderReader) BorSpan(spanId uint64) []byte { return nil } + type dummyChain struct { counter int } @@ -309,7 +352,7 @@ func TestBlockhash(t *testing.T) { */ // The contract above - data := libcommon.Hex2Bytes("6080604052348015600f57600080fd5b50600436106045576000357c010000000000000000000000000000000000000000000000000000000090048063f8a8fd6d14604a575b600080fd5b60506074565b60405180848152602001838152602001828152602001935050505060405180910390f35b600080600080439050600080600083409050600184034092506000600290505b61010481101560c35760008186034090506000816001900414151560b6578093505b5080806001019150506094565b508083839650965096505050505090919256fea165627a7a72305820462d71b510c1725ff35946c20b415b0d50b468ea157c8c77dff9466c9cb85f560029") + data := libcommon.Hex2Bytes("6080604052348015600f57600080fd5b50600436106045576000357c010000000000000000000000000000000000000000000000000000000090048063f8a8fd6d14604a575b600080fd5b60506074565b60405180848152602001838152602001828152602001935050505060405180910390f35b600080600080439050600080600083409050600184034092506000600290505b61010481101560c35760008186034090506000816001900414151560b6578093505b5080806001019150506094565b508083839650965096505050505090919256fea165627a7a72305820dfe2458cc086a06042239d07ce304746e65c88f8a42a9b5dfb0ac053c287d8380029") // The method call to 'test()' input := libcommon.Hex2Bytes("f8a8fd6d") chain := &dummyChain{} @@ -318,6 +361,7 @@ func TestBlockhash(t *testing.T) { BlockNumber: new(big.Int).Set(header.Number), Time: new(big.Int), } + setDefaults(cfg) ret, _, err := Execute(data, input, cfg, header.Number.Uint64()) if err != nil { t.Fatalf("expected no error, got %v", err) @@ -343,6 +387,90 @@ func TestBlockhash(t *testing.T) { } } +func TestBlockHashEip2935(t *testing.T) { + t.Parallel() + + // This is the contract we're using. It requests the blockhash for current num (should be all zeroes), + // then iteratively fetches all blockhashes back to n - params.HISTORY_SERVE_WINDOW. + // It returns + // 1. the first (should be zero) + // 2. the second (should be the parent hash) + // 3. the last non-zero hash + // By making the chain reader return hashes which correlate to the number, we can + // verify that it obtained the right hashes where it should + + /* + + pragma solidity ^0.5.3; + contract Hasher{ + + function test() public view returns (bytes32, bytes32, bytes32){ + uint256 x = block.number; + bytes32 first; + bytes32 last; + bytes32 zero; + zero = blockhash(x); // Should be zeroes + first = blockhash(x-1); + for(uint256 i = 2 ; i < 260; i++){ + bytes32 hash = blockhash(x - i); + if (uint256(hash) != 0){ + last = hash; + } + } + return (zero, first, last); + } + } + + */ + // The contract above + data := libcommon.Hex2Bytes("608060405234801561000f575f80fd5b5060043610610029575f3560e01c8063f8a8fd6d1461002d575b5f80fd5b61003561004e565b60405161004594939291906100bf565b60405180910390f35b5f805f805f4390505f814090505f6001836100699190610138565b4090505f6120008461007b9190610138565b4090505f6120018561008d9190610138565b409050838383839850985098509850505050505090919293565b5f819050919050565b6100b9816100a7565b82525050565b5f6080820190506100d25f8301876100b0565b6100df60208301866100b0565b6100ec60408301856100b0565b6100f960608301846100b0565b95945050505050565b5f819050919050565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52601160045260245ffd5b5f61014282610102565b915061014d83610102565b92508282039050818111156101655761016461010b565b5b9291505056fea2646970667358221220bac67d00c05154c1dca13fe3c1493172d44692d312cb3fd72a3d7457874d595464736f6c63430008190033") + // The method call to 'test()' + input := libcommon.Hex2Bytes("f8a8fd6d") + + // Current head + n := uint64(10000) + parentHash := libcommon.Hash{} + s := common.LeftPadBytes(big.NewInt(int64(n-1)).Bytes(), 32) + copy(parentHash[:], s) + fakeHeaderReader := &FakeChainHeaderReader{} + header := fakeHeaderReader.GetHeaderByNumber(n) + + chain := &dummyChain{} + cfg := &Config{ + GetHashFn: core.GetHashFn(header, chain.GetHeader), + BlockNumber: new(big.Int).Set(header.Number), + Time: big.NewInt(10000), + } + setDefaults(cfg) + cfg.ChainConfig.PragueTime = big.NewInt(10000) + _, tx := memdb.NewTestTx(t) + cfg.State = state.New(state.NewPlainStateReader(tx)) + cfg.State.CreateAccount(params.HistoryStorageAddress, true) + misc.StoreBlockHashesEip2935(header, cfg.State, cfg.ChainConfig, &FakeChainHeaderReader{}) + + ret, _, err := Execute(data, input, cfg, header.Number.Uint64()) + if err != nil { + t.Fatalf("expected no error, got %v", err) + } + if len(ret) != 128 { + t.Fatalf("expected returndata to be 128 bytes, got %d", len(ret)) + } + + zero := new(big.Int).SetBytes(ret[0:32]) + first := new(big.Int).SetBytes(ret[32:64]) + last := new(big.Int).SetBytes(ret[64:96]) + beyond := new(big.Int).SetBytes(ret[96:128]) + if zero.Sign() != 0 || beyond.Sign() != 0 { + t.Fatalf("expected zeroes, got %x %x", ret[0:32], ret[96:128]) + } + if first.Uint64() != 9999 { + t.Fatalf("second block should be 9999, got %d (%x)", first, ret[32:64]) + } + if last.Uint64() != 1808 { + t.Fatalf("last block should be 1808, got %d (%x)", last, ret[64:96]) + } +} + // benchmarkNonModifyingCode benchmarks code, but if the code modifies the // state, this should not be used, since it does not reset the state between runs. func benchmarkNonModifyingCode(b *testing.B, gas uint64, code []byte, name string) { //nolint:unparam From c3d8d95595267e806593eedbcfc68a7707feb2fa Mon Sep 17 00:00:00 2001 From: Somnath Banerjee Date: Tue, 30 Apr 2024 13:44:09 +0400 Subject: [PATCH 08/18] Cleanup --- consensus/misc/eip2935.go | 1 - core/vm/instructions.go | 10 +-------- core/vm/runtime/runtime_test.go | 37 +++++++++------------------------ 3 files changed, 11 insertions(+), 37 deletions(-) diff --git a/consensus/misc/eip2935.go b/consensus/misc/eip2935.go index fd535a77812..84b02a9c9e8 100644 --- a/consensus/misc/eip2935.go +++ b/consensus/misc/eip2935.go @@ -30,7 +30,6 @@ func StoreBlockHashesEip2935(header *types.Header, state *state.IntraBlockState, for i := window; i > 0; i-- { _storeHash(big.NewInt(0).Sub(parent.Number, big.NewInt(1)), parent.ParentHash, state) parent = headerReader.GetHeaderByHash(parent.ParentHash) - // fmt.Println("Storing parent %x, for i=%d", parent.ParentHash, i) } } } diff --git a/core/vm/instructions.go b/core/vm/instructions.go index 022a9a41e0d..9194ca78f18 100644 --- a/core/vm/instructions.go +++ b/core/vm/instructions.go @@ -19,7 +19,6 @@ package vm import ( "fmt" "math" - "math/big" "github.com/holiman/uint256" libcommon "github.com/ledgerwatch/erigon-lib/common" @@ -476,21 +475,14 @@ func opBlockhash(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ( upper = interpreter.evm.Context.BlockNumber if interpreter.evm.chainRules.IsPrague { if arg64 >= upper || arg64+params.BlockHashHistoryServeWindow < upper { - // scope.Stack.Push(uint256.NewInt(0)) arg.Clear() } else { - // out := uint256.NewInt(0) - storageSlot := libcommon.BigToHash(big.NewInt(0).Mod(big.NewInt(int64(arg64)), big.NewInt(int64(params.BlockHashHistoryServeWindow)))) - - // storageSlot := libcommon.BytesToHash(uint256.NewInt(arg64%params.BlockHashHistoryServeWindow).Bytes()) + storageSlot := libcommon.BytesToHash(uint256.NewInt(arg64 % params.BlockHashHistoryServeWindow).Bytes()) interpreter.evm.intraBlockState.GetState( params.HistoryStorageAddress, &storageSlot, - // (*libcommon.Hash)(uint256.NewInt(arg64%params.BlockHashHistoryServeWindow).Bytes()), arg, ) - // arg.SetBytes(out.Bytes()) - // scope.Stack.Push(out) } return nil, nil } diff --git a/core/vm/runtime/runtime_test.go b/core/vm/runtime/runtime_test.go index 5f55518cd1c..bcebf55b39b 100644 --- a/core/vm/runtime/runtime_test.go +++ b/core/vm/runtime/runtime_test.go @@ -390,37 +390,20 @@ func TestBlockhash(t *testing.T) { func TestBlockHashEip2935(t *testing.T) { t.Parallel() - // This is the contract we're using. It requests the blockhash for current num (should be all zeroes), - // then iteratively fetches all blockhashes back to n - params.HISTORY_SERVE_WINDOW. - // It returns - // 1. the first (should be zero) - // 2. the second (should be the parent hash) - // 3. the last non-zero hash - // By making the chain reader return hashes which correlate to the number, we can - // verify that it obtained the right hashes where it should + // This is the contract we're using. It requests the blockhash for current num (should be all zeroes), We are fetching BlockHash for current block (should be zer0), parent block, last block which is supposed to be there (head - HISTORY_SERVE_WINDOW) and also one block before that (should be zero) /* - - pragma solidity ^0.5.3; - contract Hasher{ - - function test() public view returns (bytes32, bytes32, bytes32){ - uint256 x = block.number; - bytes32 first; - bytes32 last; - bytes32 zero; - zero = blockhash(x); // Should be zeroes - first = blockhash(x-1); - for(uint256 i = 2 ; i < 260; i++){ - bytes32 hash = blockhash(x - i); - if (uint256(hash) != 0){ - last = hash; - } - } - return (zero, first, last); + pragma solidity ^0.8.25; + contract BlockHashTestPrague{ + function test() public view returns (bytes32, bytes32, bytes32, bytes32){ + uint256 head = block.number; + bytes32 zero = blockhash(head); + bytes32 first = blockhash(head-1); + bytes32 last = blockhash(head - 8192); + bytes32 beyond = blockhash(head - 8193); + return (zero, first, last, beyond); } } - */ // The contract above data := libcommon.Hex2Bytes("608060405234801561000f575f80fd5b5060043610610029575f3560e01c8063f8a8fd6d1461002d575b5f80fd5b61003561004e565b60405161004594939291906100bf565b60405180910390f35b5f805f805f4390505f814090505f6001836100699190610138565b4090505f6120008461007b9190610138565b4090505f6120018561008d9190610138565b409050838383839850985098509850505050505090919293565b5f819050919050565b6100b9816100a7565b82525050565b5f6080820190506100d25f8301876100b0565b6100df60208301866100b0565b6100ec60408301856100b0565b6100f960608301846100b0565b95945050505050565b5f819050919050565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52601160045260245ffd5b5f61014282610102565b915061014d83610102565b92508282039050818111156101655761016461010b565b5b9291505056fea2646970667358221220bac67d00c05154c1dca13fe3c1493172d44692d312cb3fd72a3d7457874d595464736f6c63430008190033") From d169572f49cd074e38422f09b0f0c294d2edfc6d Mon Sep 17 00:00:00 2001 From: Somnath Banerjee Date: Tue, 30 Apr 2024 11:56:17 -0400 Subject: [PATCH 09/18] Default cfg for Execute fn --- core/vm/runtime/runtime.go | 1 + 1 file changed, 1 insertion(+) diff --git a/core/vm/runtime/runtime.go b/core/vm/runtime/runtime.go index 0cdb2453385..340ececb914 100644 --- a/core/vm/runtime/runtime.go +++ b/core/vm/runtime/runtime.go @@ -111,6 +111,7 @@ func setDefaults(cfg *Config) { func Execute(code, input []byte, cfg *Config, bn uint64) ([]byte, *state.IntraBlockState, error) { if cfg == nil { cfg = new(Config) + setDefaults(cfg) } externalState := cfg.State != nil From 31ac3941e9a7b75a83cb1e02d22c6b4ac17c3715 Mon Sep 17 00:00:00 2001 From: Somnath Banerjee Date: Tue, 30 Apr 2024 12:02:03 -0400 Subject: [PATCH 10/18] Fix test --- core/vm/runtime/runtime_test.go | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/core/vm/runtime/runtime_test.go b/core/vm/runtime/runtime_test.go index bcebf55b39b..b08716521e5 100644 --- a/core/vm/runtime/runtime_test.go +++ b/core/vm/runtime/runtime_test.go @@ -634,14 +634,16 @@ func TestEip2929Cases(t *testing.T) { fmt.Printf("%v\n\nBytecode: \n```\n0x%x\n```\nOperations: \n```\n%v\n```\n\n", comment, code, ops) - //nolint:errcheck - Execute(code, nil, &Config{ + cfg := &Config{ EVMConfig: vm.Config{ Debug: true, Tracer: logger.NewMarkdownLogger(nil, os.Stdout), ExtraEips: []int{2929}, }, - }, 0) + } + setDefaults(cfg) + //nolint:errcheck + Execute(code, nil, cfg, 0) } { // First eip testcase From ad60013c09b1b8c7ec08bd6decef633db8b3cd36 Mon Sep 17 00:00:00 2001 From: Somnath Banerjee Date: Fri, 3 May 2024 14:27:39 -0600 Subject: [PATCH 11/18] review comments --- consensus/misc/eip2935.go | 24 +++++++++++++----------- core/vm/runtime/runtime_test.go | 29 ++++++++++++++--------------- 2 files changed, 27 insertions(+), 26 deletions(-) diff --git a/consensus/misc/eip2935.go b/consensus/misc/eip2935.go index 84b02a9c9e8..e3485a0194c 100644 --- a/consensus/misc/eip2935.go +++ b/consensus/misc/eip2935.go @@ -2,7 +2,7 @@ package misc import ( // "fmt" - "math/big" + // "math/big" "github.com/holiman/uint256" @@ -15,28 +15,30 @@ import ( ) func StoreBlockHashesEip2935(header *types.Header, state *state.IntraBlockState, config *chain.Config, headerReader consensus.ChainHeaderReader) { - if header.Number.Cmp(big.NewInt(0)) == 0 { // Activation of fork at Genesis + headerNum := header.Number.Uint64() + if headerNum == 0 { // Activation of fork at Genesis return } - parent := headerReader.GetHeaderByHash(header.ParentHash) - _storeHash(parent.Number, header.ParentHash, state) + storeHash(headerNum - 1, header.ParentHash, state) // If this is the fork block, add the parent's direct `HISTORY_SERVE_WINDOW - 1` ancestors as well + parent := headerReader.GetHeader(header.ParentHash, headerNum - 1) if parent.Time < config.PragueTime.Uint64() { - p := parent.Number.Uint64() + p := headerNum - 1 window := params.BlockHashHistoryServeWindow - 1 if p < window { window = p } for i := window; i > 0; i-- { - _storeHash(big.NewInt(0).Sub(parent.Number, big.NewInt(1)), parent.ParentHash, state) - parent = headerReader.GetHeaderByHash(parent.ParentHash) + p = p - 1 + storeHash(p, parent.ParentHash, state) + parent = headerReader.GetHeader(parent.ParentHash, p) } } } -func _storeHash(num *big.Int, hash libcommon.Hash, state *state.IntraBlockState) { - storageSlot := libcommon.BigToHash(big.NewInt(0).Mod(num, big.NewInt(int64(params.BlockHashHistoryServeWindow)))) - hh := hash.Big() - parentHashInt := uint256.MustFromBig(hh) +func storeHash(num uint64, hash libcommon.Hash, state *state.IntraBlockState) { + slotNum := num % params.BlockHashHistoryServeWindow + storageSlot := libcommon.BytesToHash(uint256.NewInt(slotNum).Bytes()) + parentHashInt := uint256.NewInt(0).SetBytes32(hash.Bytes()) state.SetState(params.HistoryStorageAddress, &storageSlot, *parentHashInt) } diff --git a/core/vm/runtime/runtime_test.go b/core/vm/runtime/runtime_test.go index b08716521e5..81cb864f82b 100644 --- a/core/vm/runtime/runtime_test.go +++ b/core/vm/runtime/runtime_test.go @@ -253,17 +253,7 @@ type FakeChainHeaderReader struct { } func (cr *FakeChainHeaderReader) GetHeaderByHash(hash libcommon.Hash) *types.Header { - num := hash.Big() - return &types.Header{ - Coinbase: libcommon.HexToAddress("0x00000000000000000000000000000000deadbeef"), - Number: num, - ParentHash: libcommon.BigToHash(big.NewInt(0).Sub(num, big.NewInt(1))), - Time: num.Uint64(), - Nonce: types.BlockNonce{0x1}, - Extra: []byte{}, - Difficulty: big.NewInt(0), - GasLimit: 100000, - } + return nil } func (cr *FakeChainHeaderReader) GetHeaderByNumber(number uint64) *types.Header { return cr.GetHeaderByHash(libcommon.BigToHash(big.NewInt(int64(number)))) @@ -271,7 +261,16 @@ func (cr *FakeChainHeaderReader) GetHeaderByNumber(number uint64) *types.Header func (cr *FakeChainHeaderReader) Config() *chain.Config { return nil } func (cr *FakeChainHeaderReader) CurrentHeader() *types.Header { return nil } func (cr *FakeChainHeaderReader) GetHeader(hash libcommon.Hash, number uint64) *types.Header { - return nil + return &types.Header{ + Coinbase: libcommon.HexToAddress("0x00000000000000000000000000000000deadbeef"), + Number: big.NewInt(int64(number)), + ParentHash: libcommon.BigToHash(big.NewInt(int64(number - 1))), + Time: number, + Nonce: types.BlockNonce{0x1}, + Extra: []byte{}, + Difficulty: big.NewInt(0), + GasLimit: 100000, + } } func (cr *FakeChainHeaderReader) GetBlock(hash libcommon.Hash, number uint64) *types.Block { return nil @@ -352,7 +351,7 @@ func TestBlockhash(t *testing.T) { */ // The contract above - data := libcommon.Hex2Bytes("6080604052348015600f57600080fd5b50600436106045576000357c010000000000000000000000000000000000000000000000000000000090048063f8a8fd6d14604a575b600080fd5b60506074565b60405180848152602001838152602001828152602001935050505060405180910390f35b600080600080439050600080600083409050600184034092506000600290505b61010481101560c35760008186034090506000816001900414151560b6578093505b5080806001019150506094565b508083839650965096505050505090919256fea165627a7a72305820dfe2458cc086a06042239d07ce304746e65c88f8a42a9b5dfb0ac053c287d8380029") + data := libcommon.Hex2Bytes("6080604052348015600f57600080fd5b50600436106045576000357c010000000000000000000000000000000000000000000000000000000090048063f8a8fd6d14604a575b600080fd5b60506074565b60405180848152602001838152602001828152602001935050505060405180910390f35b600080600080439050600080600083409050600184034092506000600290505b61010481101560c35760008186034090506000816001900414151560b6578093505b5080806001019150506094565b508083839650965096505050505090919256fea165627a7a72305820462d71b510c1725ff35946c20b415b0d50b468ea157c8c77dff9466c9cb85f560029") // The method call to 'test()' input := libcommon.Hex2Bytes("f8a8fd6d") chain := &dummyChain{} @@ -416,7 +415,7 @@ func TestBlockHashEip2935(t *testing.T) { s := common.LeftPadBytes(big.NewInt(int64(n-1)).Bytes(), 32) copy(parentHash[:], s) fakeHeaderReader := &FakeChainHeaderReader{} - header := fakeHeaderReader.GetHeaderByNumber(n) + header := fakeHeaderReader.GetHeader(libcommon.BigToHash(big.NewInt(int64(n))), n) chain := &dummyChain{} cfg := &Config{ @@ -447,7 +446,7 @@ func TestBlockHashEip2935(t *testing.T) { t.Fatalf("expected zeroes, got %x %x", ret[0:32], ret[96:128]) } if first.Uint64() != 9999 { - t.Fatalf("second block should be 9999, got %d (%x)", first, ret[32:64]) + t.Fatalf("first block should be 9999, got %d (%x)", first, ret[32:64]) } if last.Uint64() != 1808 { t.Fatalf("last block should be 1808, got %d (%x)", last, ret[64:96]) From b419d41ef3e5e7f99bf36dcd9ccbe5a3b7b26cc5 Mon Sep 17 00:00:00 2001 From: Somnath Banerjee Date: Fri, 3 May 2024 14:29:36 -0600 Subject: [PATCH 12/18] revert defaults --- core/vm/runtime/runtime.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/core/vm/runtime/runtime.go b/core/vm/runtime/runtime.go index 340ececb914..cec1e7078b1 100644 --- a/core/vm/runtime/runtime.go +++ b/core/vm/runtime/runtime.go @@ -73,8 +73,8 @@ func setDefaults(cfg *Config) { GrayGlacierBlock: new(big.Int), ShanghaiTime: new(big.Int), CancunTime: new(big.Int), - PragueTime: big.NewInt(1), - OsakaTime: big.NewInt(2), + PragueTime: new(big.Int), + OsakaTime: new(big.Int), } } From 0ecac2eaf69ad11a27263e1060e7c44230253c0e Mon Sep 17 00:00:00 2001 From: Somnath Banerjee Date: Mon, 6 May 2024 15:29:35 -0700 Subject: [PATCH 13/18] Add gas costs and fork enable --- consensus/misc/eip2935.go | 6 +++--- core/vm/eips.go | 12 +++++++++++ core/vm/instructions.go | 45 ++++++++++++++++++++++++++++----------- core/vm/jump_table.go | 1 + core/vm/operations_acl.go | 5 +++++ 5 files changed, 53 insertions(+), 16 deletions(-) diff --git a/consensus/misc/eip2935.go b/consensus/misc/eip2935.go index e3485a0194c..8e567bdd499 100644 --- a/consensus/misc/eip2935.go +++ b/consensus/misc/eip2935.go @@ -19,9 +19,9 @@ func StoreBlockHashesEip2935(header *types.Header, state *state.IntraBlockState, if headerNum == 0 { // Activation of fork at Genesis return } - storeHash(headerNum - 1, header.ParentHash, state) + storeHash(headerNum-1, header.ParentHash, state) // If this is the fork block, add the parent's direct `HISTORY_SERVE_WINDOW - 1` ancestors as well - parent := headerReader.GetHeader(header.ParentHash, headerNum - 1) + parent := headerReader.GetHeader(header.ParentHash, headerNum-1) if parent.Time < config.PragueTime.Uint64() { p := headerNum - 1 window := params.BlockHashHistoryServeWindow - 1 @@ -38,7 +38,7 @@ func StoreBlockHashesEip2935(header *types.Header, state *state.IntraBlockState, func storeHash(num uint64, hash libcommon.Hash, state *state.IntraBlockState) { slotNum := num % params.BlockHashHistoryServeWindow - storageSlot := libcommon.BytesToHash(uint256.NewInt(slotNum).Bytes()) + storageSlot := libcommon.BytesToHash(uint256.NewInt(slotNum).Bytes()) parentHashInt := uint256.NewInt(0).SetBytes32(hash.Bytes()) state.SetState(params.HistoryStorageAddress, &storageSlot, *parentHashInt) } diff --git a/core/vm/eips.go b/core/vm/eips.go index 8d48f1a7b33..16e8121b3f5 100644 --- a/core/vm/eips.go +++ b/core/vm/eips.go @@ -29,6 +29,7 @@ import ( ) var activators = map[int]func(*JumpTable){ + 2935: enable2935, 7516: enable7516, 6780: enable6780, 5656: enable5656, @@ -327,3 +328,14 @@ func enable7516(jt *JumpTable) { numPush: 1, } } + +// enable2935 applies EIP-2935 (Historical block hashes in state) +func enable2935(jt *JumpTable) { + jt[BLOCKHASH] = &operation{ + execute: opBlockhash2935, + constantGas: GasExtStep, + dynamicGas: gasOpBlockhashEIP2935, + numPop: 0, + numPush: 1, + } +} diff --git a/core/vm/instructions.go b/core/vm/instructions.go index 9194ca78f18..6e379d83c65 100644 --- a/core/vm/instructions.go +++ b/core/vm/instructions.go @@ -464,6 +464,7 @@ func opGasprice(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([ return nil, nil } +// opBlockhash executes the BLOCKHASH opcode pre-EIP-2935 func opBlockhash(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) { arg := scope.Stack.Peek() arg64, overflow := arg.Uint64WithOverflow() @@ -473,30 +474,48 @@ func opBlockhash(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ( } var upper, lower uint64 upper = interpreter.evm.Context.BlockNumber - if interpreter.evm.chainRules.IsPrague { + if upper <= params.BlockHashOldWindow { + lower = 0 + } else { + lower = upper - params.BlockHashOldWindow + } + if arg64 >= lower && arg64 < upper { + arg.SetBytes(interpreter.evm.Context.GetHash(arg64).Bytes()) + } else { + arg.Clear() + } + return nil, nil +} + +// opBlockhash2935 executes for the BLOCKHASH opcode post EIP-2935 by returning the +// corresponding hash for the blocknumber from the state, if within range. +// The range is defined by [head - params.BlockHashHistoryServeWindow - 1, head - 1] +// This should not be used without activating EIP-2935 +func opBlockhash2935(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) { + arg := scope.Stack.Peek() + refundAmt := params.ColdSloadCostEIP2929 + arg64, overflow := arg.Uint64WithOverflow() + if overflow { + arg.Clear() + } else { + var upper uint64 + upper = interpreter.evm.Context.BlockNumber if arg64 >= upper || arg64+params.BlockHashHistoryServeWindow < upper { arg.Clear() } else { storageSlot := libcommon.BytesToHash(uint256.NewInt(arg64 % params.BlockHashHistoryServeWindow).Bytes()) + if _, slotMod := interpreter.evm.IntraBlockState().AddSlotToAccessList(params.HistoryStorageAddress, storageSlot); !slotMod { + refundAmt = params.ColdSloadCostEIP2929 - params.WarmStorageReadCostEIP2929 + } interpreter.evm.intraBlockState.GetState( params.HistoryStorageAddress, &storageSlot, arg, ) } - return nil, nil - } - - if upper <= params.BlockHashOldWindow { - lower = 0 - } else { - lower = upper - params.BlockHashOldWindow - } - if arg64 >= lower && arg64 < upper { - arg.SetBytes(interpreter.evm.Context.GetHash(arg64).Bytes()) - } else { - arg.Clear() } + // The gas func for this charges max (ColdSloadCostEIP2929) gas, refunding the rest here + interpreter.evm.intraBlockState.AddRefund(refundAmt) return nil, nil } diff --git a/core/vm/jump_table.go b/core/vm/jump_table.go index 806ae494133..82c43dd3167 100644 --- a/core/vm/jump_table.go +++ b/core/vm/jump_table.go @@ -92,6 +92,7 @@ func validateAndFillMaxStack(jt *JumpTable) { // cancun, and prague instructions. func newPragueInstructionSet() JumpTable { instructionSet := newCancunInstructionSet() + enable2935(&instructionSet) validateAndFillMaxStack(&instructionSet) return instructionSet } diff --git a/core/vm/operations_acl.go b/core/vm/operations_acl.go index 6256ae5740b..960a66ab6d1 100644 --- a/core/vm/operations_acl.go +++ b/core/vm/operations_acl.go @@ -235,3 +235,8 @@ func makeSelfdestructGasFn(refundsEnabled bool) gasFunc { } return gasFunc } + +// gasOpBlockhashEIP2935 returns the max possible gas here, and refund the rest at its execute function +func gasOpBlockhashEIP2935(evm *EVM, contract *Contract, stack *stack.Stack, mem *Memory, memorySize uint64) (uint64, error) { + return params.ColdSloadCostEIP2929, nil +} From 4349ff0f73137ef09566b60e5f720549fbe8a81e Mon Sep 17 00:00:00 2001 From: Somnath Banerjee Date: Mon, 6 May 2024 15:55:28 -0700 Subject: [PATCH 14/18] Disable Prague on pre-EIP blockhash --- core/vm/runtime/runtime_test.go | 1 + 1 file changed, 1 insertion(+) diff --git a/core/vm/runtime/runtime_test.go b/core/vm/runtime/runtime_test.go index 81cb864f82b..5a14c8a649b 100644 --- a/core/vm/runtime/runtime_test.go +++ b/core/vm/runtime/runtime_test.go @@ -361,6 +361,7 @@ func TestBlockhash(t *testing.T) { Time: new(big.Int), } setDefaults(cfg) + cfg.ChainConfig.PragueTime = big.NewInt(1) ret, _, err := Execute(data, input, cfg, header.Number.Uint64()) if err != nil { t.Fatalf("expected no error, got %v", err) From 8a08671287cd7e20fba64d438c1ff43b260ed6eb Mon Sep 17 00:00:00 2001 From: Somnath Banerjee Date: Mon, 6 May 2024 17:29:58 -0700 Subject: [PATCH 15/18] Cleanup --- consensus/misc/eip2935.go | 4 +--- core/vm/runtime/runtime_test.go | 8 ++++---- params/protocol_params.go | 2 +- 3 files changed, 6 insertions(+), 8 deletions(-) diff --git a/consensus/misc/eip2935.go b/consensus/misc/eip2935.go index 8e567bdd499..64d4bef1586 100644 --- a/consensus/misc/eip2935.go +++ b/consensus/misc/eip2935.go @@ -1,13 +1,11 @@ package misc import ( - // "fmt" - // "math/big" - "github.com/holiman/uint256" "github.com/ledgerwatch/erigon-lib/chain" libcommon "github.com/ledgerwatch/erigon-lib/common" + "github.com/ledgerwatch/erigon/consensus" "github.com/ledgerwatch/erigon/core/state" "github.com/ledgerwatch/erigon/core/types" diff --git a/core/vm/runtime/runtime_test.go b/core/vm/runtime/runtime_test.go index 5a14c8a649b..8553064707c 100644 --- a/core/vm/runtime/runtime_test.go +++ b/core/vm/runtime/runtime_test.go @@ -247,10 +247,8 @@ func fakeHeader(n uint64, parentHash libcommon.Hash) *types.Header { return &header } -type FakeChainHeaderReader struct { - // Cfg *chain.Config - // current *types.Block -} +// FakeChainHeaderReader implements consensus.ChainHeaderReader interface +type FakeChainHeaderReader struct{} func (cr *FakeChainHeaderReader) GetHeaderByHash(hash libcommon.Hash) *types.Header { return nil @@ -260,6 +258,8 @@ func (cr *FakeChainHeaderReader) GetHeaderByNumber(number uint64) *types.Header } func (cr *FakeChainHeaderReader) Config() *chain.Config { return nil } func (cr *FakeChainHeaderReader) CurrentHeader() *types.Header { return nil } + +// GetHeader returns a fake header with the parentHash equal to the number - 1 func (cr *FakeChainHeaderReader) GetHeader(hash libcommon.Hash, number uint64) *types.Header { return &types.Header{ Coinbase: libcommon.HexToAddress("0x00000000000000000000000000000000deadbeef"), diff --git a/params/protocol_params.go b/params/protocol_params.go index 99387bdab0d..05e4fe52d9f 100644 --- a/params/protocol_params.go +++ b/params/protocol_params.go @@ -172,7 +172,7 @@ const ( // PIP-27: secp256r1 elliptic curve signature verifier gas price P256VerifyGas uint64 = 3450 - // EIP-2935 + // EIP-2935: Historical block hashes in state BlockHashHistoryServeWindow uint64 = 8192 BlockHashOldWindow uint64 = 256 ) From 27c714802ed7014bbe0c6c514dd8ac37bb53ab06 Mon Sep 17 00:00:00 2001 From: Somnath Banerjee Date: Tue, 7 May 2024 10:35:26 -0700 Subject: [PATCH 16/18] Update numPop for BLOCKHASH op --- core/vm/eips.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/vm/eips.go b/core/vm/eips.go index 16e8121b3f5..c05c41006fb 100644 --- a/core/vm/eips.go +++ b/core/vm/eips.go @@ -335,7 +335,7 @@ func enable2935(jt *JumpTable) { execute: opBlockhash2935, constantGas: GasExtStep, dynamicGas: gasOpBlockhashEIP2935, - numPop: 0, + numPop: 1, numPush: 1, } } From 2ef0306ca13db76b91b9f77e8b1c8b843d00c398 Mon Sep 17 00:00:00 2001 From: Somnath Banerjee Date: Tue, 7 May 2024 12:38:02 -0700 Subject: [PATCH 17/18] Gas for blockHash update --- core/vm/instructions.go | 37 ++++++++++++++++++------------------- core/vm/operations_acl.go | 14 +++++++++++++- 2 files changed, 31 insertions(+), 20 deletions(-) diff --git a/core/vm/instructions.go b/core/vm/instructions.go index 6e379d83c65..21f9bf24d15 100644 --- a/core/vm/instructions.go +++ b/core/vm/instructions.go @@ -493,29 +493,28 @@ func opBlockhash(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ( // This should not be used without activating EIP-2935 func opBlockhash2935(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) { arg := scope.Stack.Peek() - refundAmt := params.ColdSloadCostEIP2929 arg64, overflow := arg.Uint64WithOverflow() if overflow { arg.Clear() - } else { - var upper uint64 - upper = interpreter.evm.Context.BlockNumber - if arg64 >= upper || arg64+params.BlockHashHistoryServeWindow < upper { - arg.Clear() - } else { - storageSlot := libcommon.BytesToHash(uint256.NewInt(arg64 % params.BlockHashHistoryServeWindow).Bytes()) - if _, slotMod := interpreter.evm.IntraBlockState().AddSlotToAccessList(params.HistoryStorageAddress, storageSlot); !slotMod { - refundAmt = params.ColdSloadCostEIP2929 - params.WarmStorageReadCostEIP2929 - } - interpreter.evm.intraBlockState.GetState( - params.HistoryStorageAddress, - &storageSlot, - arg, - ) - } + return nil, nil } - // The gas func for this charges max (ColdSloadCostEIP2929) gas, refunding the rest here - interpreter.evm.intraBlockState.AddRefund(refundAmt) + + // Check if arg is within allowed window + var upper uint64 + upper = interpreter.evm.Context.BlockNumber + if arg64 >= upper || arg64+params.BlockHashHistoryServeWindow < upper { + arg.Clear() + return nil, nil + } + + // Return state read value from the slot + storageSlot := libcommon.BytesToHash(uint256.NewInt(arg64 % params.BlockHashHistoryServeWindow).Bytes()) + interpreter.evm.intraBlockState.GetState( + params.HistoryStorageAddress, + &storageSlot, + arg, + ) + return nil, nil } diff --git a/core/vm/operations_acl.go b/core/vm/operations_acl.go index 960a66ab6d1..21224d18471 100644 --- a/core/vm/operations_acl.go +++ b/core/vm/operations_acl.go @@ -238,5 +238,17 @@ func makeSelfdestructGasFn(refundsEnabled bool) gasFunc { // gasOpBlockhashEIP2935 returns the max possible gas here, and refund the rest at its execute function func gasOpBlockhashEIP2935(evm *EVM, contract *Contract, stack *stack.Stack, mem *Memory, memorySize uint64) (uint64, error) { - return params.ColdSloadCostEIP2929, nil + arg := stack.Peek() + arg64, overflow := arg.Uint64WithOverflow() + if overflow { + return 0, nil + } + if arg64 >= evm.Context.BlockNumber || arg64+params.BlockHashHistoryServeWindow < evm.Context.BlockNumber { + return 0, nil + } + storageSlot := libcommon.BytesToHash(uint256.NewInt(arg64 % params.BlockHashHistoryServeWindow).Bytes()) + if _, slotMod := evm.IntraBlockState().AddSlotToAccessList(params.HistoryStorageAddress, storageSlot); slotMod { + return params.ColdSloadCostEIP2929, nil + } + return params.WarmStorageReadCostEIP2929, nil } From 1e223750ec1f008e6b377f7ccc94414952121ef1 Mon Sep 17 00:00:00 2001 From: Somnath Banerjee Date: Wed, 8 May 2024 12:29:25 -0400 Subject: [PATCH 18/18] Update comment --- core/vm/operations_acl.go | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/core/vm/operations_acl.go b/core/vm/operations_acl.go index 21224d18471..1e1b68c6995 100644 --- a/core/vm/operations_acl.go +++ b/core/vm/operations_acl.go @@ -236,7 +236,9 @@ func makeSelfdestructGasFn(refundsEnabled bool) gasFunc { return gasFunc } -// gasOpBlockhashEIP2935 returns the max possible gas here, and refund the rest at its execute function +// gasOpBlockhashEIP2935 returns the gas for the new BLOCKHASH operation post EIP-2935 +// If arg is outside of the params.BlockHashHistoryServeWindow, zero dynamic gas is returned +// EIP-2929 Cold/Warm storage read cost is applicable here similar to SLOAD func gasOpBlockhashEIP2935(evm *EVM, contract *Contract, stack *stack.Stack, mem *Memory, memorySize uint64) (uint64, error) { arg := stack.Peek() arg64, overflow := arg.Uint64WithOverflow()