Merge branch 'ethereum:master' into codespace-legendary-space-disco-q7pwg95px5gx2xpqj

This commit is contained in:
Queenkara7 2025-05-18 04:55:39 +10:00 committed by GitHub
commit b590b5f0e5
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
197 changed files with 14168 additions and 2920 deletions

View file

@ -50,6 +50,10 @@ Boqin Qin <bobbqqin@bupt.edu.cn> <Bobbqqin@gmail.com>
Casey Detrio <cdetrio@gmail.com> Casey Detrio <cdetrio@gmail.com>
Charlotte <tqpcharlie@proton.me>
Charlotte <tqpcharlie@proton.me> <Zachinquarantine@protonmail.com>
Charlotte <tqpcharlie@proton.me> <zachinquarantine@yahoo.com>
Cheng Li <lob4tt@gmail.com> Cheng Li <lob4tt@gmail.com>
Chris Ziogas <ziogaschr@gmail.com> Chris Ziogas <ziogaschr@gmail.com>
@ -301,9 +305,6 @@ Yohann Léon <sybiload@gmail.com>
yzb <335357057@qq.com> yzb <335357057@qq.com>
yzb <335357057@qq.com> <flyingyzb@gmail.com> yzb <335357057@qq.com> <flyingyzb@gmail.com>
Zachinquarantine <Zachinquarantine@protonmail.com>
Zachinquarantine <Zachinquarantine@protonmail.com> <zachinquarantine@yahoo.com>
Ziyuan Zhong <zzy.albert@163.com> Ziyuan Zhong <zzy.albert@163.com>
Zsolt Felföldi <zsfelfoldi@gmail.com> Zsolt Felföldi <zsfelfoldi@gmail.com>

View file

@ -123,6 +123,7 @@ Ceyhun Onur <ceyhun.onur@avalabs.org>
chabashilah <doumodoumo@gmail.com> chabashilah <doumodoumo@gmail.com>
changhong <changhong.yu@shanbay.com> changhong <changhong.yu@shanbay.com>
Charles Cooper <cooper.charles.m@gmail.com> Charles Cooper <cooper.charles.m@gmail.com>
Charlotte <tqpcharlie@proton.me>
Chase Wright <mysticryuujin@gmail.com> Chase Wright <mysticryuujin@gmail.com>
Chawin Aiemvaravutigul <nick41746@hotmail.com> Chawin Aiemvaravutigul <nick41746@hotmail.com>
Chen Quan <terasum@163.com> Chen Quan <terasum@163.com>
@ -839,7 +840,6 @@ ywzqwwt <39263032+ywzqwwt@users.noreply.github.com>
yzb <335357057@qq.com> yzb <335357057@qq.com>
zaccoding <zaccoding725@gmail.com> zaccoding <zaccoding725@gmail.com>
Zach <zach.ramsay@gmail.com> Zach <zach.ramsay@gmail.com>
Zachinquarantine <Zachinquarantine@protonmail.com>
zah <zahary@gmail.com> zah <zahary@gmail.com>
Zahoor Mohamed <zahoor@zahoor.in> Zahoor Mohamed <zahoor@zahoor.in>
Zak Cole <zak@beattiecole.com> Zak Cole <zak@beattiecole.com>

View file

@ -939,6 +939,7 @@ var bindTests = []struct {
if _, err := eventer.RaiseSimpleEvent(auth, common.Address{byte(j)}, [32]byte{byte(j)}, true, big.NewInt(int64(10*i+j))); err != nil { if _, err := eventer.RaiseSimpleEvent(auth, common.Address{byte(j)}, [32]byte{byte(j)}, true, big.NewInt(int64(10*i+j))); err != nil {
t.Fatalf("block %d, event %d: raise failed: %v", i, j, err) t.Fatalf("block %d, event %d: raise failed: %v", i, j, err)
} }
time.Sleep(time.Millisecond * 200)
} }
sim.Commit() sim.Commit()
} }
@ -1495,7 +1496,7 @@ var bindTests = []struct {
if n != 3 { if n != 3 {
t.Fatalf("Invalid bar0 event") t.Fatalf("Invalid bar0 event")
} }
case <-time.NewTimer(3 * time.Second).C: case <-time.NewTimer(10 * time.Second).C:
t.Fatalf("Wait bar0 event timeout") t.Fatalf("Wait bar0 event timeout")
} }
@ -1506,7 +1507,7 @@ var bindTests = []struct {
if n != 1 { if n != 1 {
t.Fatalf("Invalid bar event") t.Fatalf("Invalid bar event")
} }
case <-time.NewTimer(3 * time.Second).C: case <-time.NewTimer(10 * time.Second).C:
t.Fatalf("Wait bar event timeout") t.Fatalf("Wait bar event timeout")
} }
close(stopCh) close(stopCh)

View file

@ -1,116 +0,0 @@
// Code generated via abigen V2 - DO NOT EDIT.
// This file is a generated binding and any manual changes will be lost.
package v1bindtests
import (
"bytes"
"errors"
"math/big"
"github.com/ethereum/go-ethereum/accounts/abi"
"github.com/ethereum/go-ethereum/accounts/abi/bind/v2"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/types"
)
// Reference imports to suppress errors if they are not otherwise used.
var (
_ = bytes.Equal
_ = errors.New
_ = big.NewInt
_ = common.Big1
_ = types.BloomLookup
_ = abi.ConvertType
)
// Struct0 is an auto generated low-level Go binding around an user-defined struct.
type Struct0 struct {
B [32]byte
}
// StructsMetaData contains all meta data concerning the Structs contract.
var StructsMetaData = bind.MetaData{
ABI: "[{\"inputs\":[],\"name\":\"F\",\"outputs\":[{\"components\":[{\"internalType\":\"bytes32\",\"name\":\"B\",\"type\":\"bytes32\"}],\"internalType\":\"structStructs.A[]\",\"name\":\"a\",\"type\":\"tuple[]\"},{\"internalType\":\"uint256[]\",\"name\":\"c\",\"type\":\"uint256[]\"},{\"internalType\":\"bool[]\",\"name\":\"d\",\"type\":\"bool[]\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"G\",\"outputs\":[{\"components\":[{\"internalType\":\"bytes32\",\"name\":\"B\",\"type\":\"bytes32\"}],\"internalType\":\"structStructs.A[]\",\"name\":\"a\",\"type\":\"tuple[]\"}],\"stateMutability\":\"view\",\"type\":\"function\"}]",
ID: "Structs",
}
// Structs is an auto generated Go binding around an Ethereum contract.
type Structs struct {
abi abi.ABI
}
// NewStructs creates a new instance of Structs.
func NewStructs() *Structs {
parsed, err := StructsMetaData.ParseABI()
if err != nil {
panic(errors.New("invalid ABI: " + err.Error()))
}
return &Structs{abi: *parsed}
}
// Instance creates a wrapper for a deployed contract instance at the given address.
<<<<<<< HEAD
// Use this to create the instance object passed to abigen v2 library functions Call, Transact, etc.
func (c *Structs) Instance(backend bind.ContractBackend, addr common.Address) *bind.BoundContract {
return bind.NewBoundContract(addr, c.abi, backend, backend, backend)
=======
// Use this to create the instance object passed to abigen v2 library functions Call,
// Transact, etc.
func (c *Structs) Instance(backend bind.ContractBackend, addr common.Address) bind.BoundContract {
return bind.NewBoundContract(backend, addr, c.abi)
>>>>>>> 854c25e086 (accounts/abi/abigen: improve v2 template)
}
// F is the Go binding used to pack the parameters required for calling
// the contract method 0x28811f59.
//
// Solidity: function F() view returns((bytes32)[] a, uint256[] c, bool[] d)
func (structs *Structs) PackF() ([]byte, error) {
return structs.abi.Pack("F")
}
// FOutput serves as a container for the return parameters of contract
// method F.
type FOutput struct {
A []Struct0
C []*big.Int
D []bool
}
// UnpackF is the Go binding that unpacks the parameters returned
// from invoking the contract method with ID 0x28811f59.
//
// Solidity: function F() view returns((bytes32)[] a, uint256[] c, bool[] d)
func (structs *Structs) UnpackF(data []byte) (*FOutput, error) {
out, err := structs.abi.Unpack("F", data)
if err != nil {
return nil, err
}
ret := new(FOutput)
ret.A = *abi.ConvertType(out[0], new([]Struct0)).(*[]Struct0)
ret.C = *abi.ConvertType(out[1], new([]*big.Int)).(*[]*big.Int)
ret.D = *abi.ConvertType(out[2], new([]bool)).(*[]bool)
return ret, nil
}
// G is the Go binding used to pack the parameters required for calling
// the contract method 0x6fecb623.
//
// Solidity: function G() view returns((bytes32)[] a)
func (structs *Structs) PackG() ([]byte, error) {
return structs.abi.Pack("G")
}
// UnpackG is the Go binding that unpacks the parameters returned
// from invoking the contract method with ID 0x6fecb623.
//
// Solidity: function G() view returns((bytes32)[] a)
func (structs *Structs) UnpackG(data []byte) (*[]Struct0, error) {
out, err := structs.abi.Unpack("G", data)
if err != nil {
return nil, err
}
out0 := *abi.ConvertType(out[0], new([]Struct0)).(*[]Struct0)
return &out0, nil
}

View file

@ -26,6 +26,7 @@ import (
"github.com/ethereum/go-ethereum/beacon/params" "github.com/ethereum/go-ethereum/beacon/params"
"github.com/ethereum/go-ethereum/beacon/types" "github.com/ethereum/go-ethereum/beacon/types"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil"
ctypes "github.com/ethereum/go-ethereum/core/types" ctypes "github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/log"
"github.com/ethereum/go-ethereum/rpc" "github.com/ethereum/go-ethereum/rpc"
@ -104,7 +105,11 @@ func (ec *engineClient) callNewPayload(fork string, event types.ChainHeadEvent)
method = "engine_newPayloadV4" method = "engine_newPayloadV4"
parentBeaconRoot := event.BeaconHead.ParentRoot parentBeaconRoot := event.BeaconHead.ParentRoot
blobHashes := collectBlobHashes(event.Block) blobHashes := collectBlobHashes(event.Block)
params = append(params, blobHashes, parentBeaconRoot, event.ExecRequests) hexRequests := make([]hexutil.Bytes, len(event.ExecRequests))
for i := range event.ExecRequests {
hexRequests[i] = hexutil.Bytes(event.ExecRequests[i])
}
params = append(params, blobHashes, parentBeaconRoot, hexRequests)
case "deneb": case "deneb":
method = "engine_newPayloadV3" method = "engine_newPayloadV3"
parentBeaconRoot := event.BeaconHead.ParentRoot parentBeaconRoot := event.BeaconHead.ParentRoot

View file

@ -131,7 +131,7 @@ type executionPayloadEnvelopeMarshaling struct {
type PayloadStatusV1 struct { type PayloadStatusV1 struct {
Status string `json:"status"` Status string `json:"status"`
Witness *hexutil.Bytes `json:"witness"` Witness *hexutil.Bytes `json:"witness,omitempty"`
LatestValidHash *common.Hash `json:"latestValidHash"` LatestValidHash *common.Hash `json:"latestValidHash"`
ValidationError *string `json:"validationError"` ValidationError *string `json:"validationError"`
} }

View file

@ -1 +1 @@
0xf5606a019f0f1006e9ec2070695045f4334450362a48da4c5965314510e0b4c3 0xd60e5310c5d52ced44cfb13be4e9f22a1e6a6dc56964c3cccd429182d26d72d0

View file

@ -1 +1 @@
0x3c0cb4aa83beded1803d262664ba4392b1023f334d9cca02dc3a6925987ebe91 0x02f0bb348b0d45f95a9b7e2bb5705768ad06548876cee03d880a2c9dabb1ff88

View file

@ -1 +1 @@
0xa8d56457aa414523d93659aef1f7409bbfb72ad75e94d917c8c0b1baa38153ef 0xa0dad451a230c01be6f2492980ec5bb412d8cf33351a75e8b172b5b84a5fd03a

View file

@ -41,7 +41,8 @@ var (
AddFork("ALTAIR", 74240, []byte{1, 0, 0, 0}). AddFork("ALTAIR", 74240, []byte{1, 0, 0, 0}).
AddFork("BELLATRIX", 144896, []byte{2, 0, 0, 0}). AddFork("BELLATRIX", 144896, []byte{2, 0, 0, 0}).
AddFork("CAPELLA", 194048, []byte{3, 0, 0, 0}). AddFork("CAPELLA", 194048, []byte{3, 0, 0, 0}).
AddFork("DENEB", 269568, []byte{4, 0, 0, 0}) AddFork("DENEB", 269568, []byte{4, 0, 0, 0}).
AddFork("ELECTRA", 364032, []byte{5, 0, 0, 0})
SepoliaLightConfig = (&ChainConfig{ SepoliaLightConfig = (&ChainConfig{
GenesisValidatorsRoot: common.HexToHash("0xd8ea171f3c94aea21ebc42a1ed61052acf3f9209c00e4efbaaddac09ed9b8078"), GenesisValidatorsRoot: common.HexToHash("0xd8ea171f3c94aea21ebc42a1ed61052acf3f9209c00e4efbaaddac09ed9b8078"),

View file

@ -5,54 +5,54 @@
# https://github.com/ethereum/execution-spec-tests/releases/download/pectra-devnet-6%40v1.0.0/ # https://github.com/ethereum/execution-spec-tests/releases/download/pectra-devnet-6%40v1.0.0/
b69211752a3029083c020dc635fe12156ca1a6725a08559da540a0337586a77e fixtures_pectra-devnet-6.tar.gz b69211752a3029083c020dc635fe12156ca1a6725a08559da540a0337586a77e fixtures_pectra-devnet-6.tar.gz
# version:golang 1.24.2 # version:golang 1.24.3
# https://go.dev/dl/ # https://go.dev/dl/
9dc77ffadc16d837a1bf32d99c624cb4df0647cee7b119edd9e7b1bcc05f2e00 go1.24.2.src.tar.gz 229c08b600b1446798109fae1f569228102c8473caba8104b6418cb5bc032878 go1.24.3.src.tar.gz
427b373540d8fd51dbcc46bdecd340af109cd41514443c000d3dcde72b2c65a3 go1.24.2.aix-ppc64.tar.gz 6f6901497547db3b77c14f7f953fbcef9fa5fb84199ee2ee14a5686e66bed5a6 go1.24.3.aix-ppc64.tar.gz
238d9c065d09ff6af229d2e3b8b5e85e688318d69f4006fb85a96e41c216ea83 go1.24.2.darwin-amd64.tar.gz a05fa7e4043a4fec66897135219e3b8ab2202b5ef351c60c2fbb531dfb8f2900 go1.24.3.darwin-amd64.pkg
535ed9ff283fee39575a7fb9b6d8b1901b6dc640d06dc71fd7d3faeefdaf8030 go1.24.2.darwin-amd64.pkg 13e6fe3fcf65689d77d40e633de1e31c6febbdbcb846eb05fc2434ed2213e92b go1.24.3.darwin-amd64.tar.gz
b70f8b3c5b4ccb0ad4ffa5ee91cd38075df20fdbd953a1daedd47f50fbcff47a go1.24.2.darwin-arm64.tar.gz 97055ff4214043b39dc32e043fdd5c565df7c0a4e2fc0174e779a134c347ae0e go1.24.3.darwin-arm64.pkg
4732f607a47ce4d898c0af01ff68f07e0820a6b50603aef5d5c777d1102505e2 go1.24.2.darwin-arm64.pkg 64a3fa22142f627e78fac3018ce3d4aeace68b743eff0afda8aae0411df5e4fb go1.24.3.darwin-arm64.tar.gz
c17686b5fd61a663fbfafccfa177961be59386cf294e935ce35866b9dcb8e78a go1.24.2.dragonfly-amd64.tar.gz 32de3fd44d5055973978436a7f1f0ffbaae85c1b603ec6105e5c38d8a674c721 go1.24.3.dragonfly-amd64.tar.gz
026f1dd906189acff714c7625686bbc4ed91042618ba010d45b671461acc9e63 go1.24.2.freebsd-386.tar.gz 9fe6101b3797919bd7337ee5ce591954f85d59db7ae88983904db29fd64c3dd1 go1.24.3.freebsd-386.tar.gz
49399ba759b570a8f87d12179133403da6c2dd296d63a8830dee309161b9c40c go1.24.2.freebsd-amd64.tar.gz 6ccf4cca287e90cc28cd7954b6172f5d177a17e20b072b65f7f39636c325e2fb go1.24.3.freebsd-amd64.tar.gz
1f48f47183794d97c29736004247ab541177cf984ac6322c78bc43828daa1172 go1.24.2.freebsd-arm.tar.gz ce45ebf389066f82a7b056b66dd650efb51fde6f8bf92a2a3ab6990f02788ebf go1.24.3.freebsd-arm.tar.gz
ef856428b60a8c0bd9a2cba596e83024be6f1c2d5574e89cb1ff2262b08df8b9 go1.24.2.freebsd-arm64.tar.gz 8f6494a12a874d0ea57c67987829359e016960ce3ba0673273609d6ac2af589a go1.24.3.freebsd-arm64.tar.gz
ec2088823e16df00600a6d0f72e9a7dc6d2f80c9c140c2043c0cf20e1404d1a9 go1.24.2.freebsd-riscv64.tar.gz f9db392560cf0851f0bc8f2190e1978e01b4603038c27fecfc8658a695b71616 go1.24.3.freebsd-riscv64.tar.gz
e030e7cedbb8688f1d75cb80f3de6ee2e6617a67d34051e794e5992b53462147 go1.24.2.illumos-amd64.tar.gz 01717fff64c5d98457272002fa825d0a15e307bf6e189f2b0c23817fa033b61c go1.24.3.illumos-amd64.tar.gz
4c382776d52313266f3026236297a224a6688751256a2dffa3f524d8d6f6c0ba go1.24.2.linux-386.tar.gz 41b1051063e68cbd2b919bf12326764fe33937cf1d32b5c529dd1a4f43dce578 go1.24.3.linux-386.tar.gz
68097bd680839cbc9d464a0edce4f7c333975e27a90246890e9f1078c7e702ad go1.24.2.linux-amd64.tar.gz 3333f6ea53afa971e9078895eaa4ac7204a8c6b5c68c10e6bc9a33e8e391bdd8 go1.24.3.linux-amd64.tar.gz
756274ea4b68fa5535eb9fe2559889287d725a8da63c6aae4d5f23778c229f4b go1.24.2.linux-arm64.tar.gz a463cb59382bd7ae7d8f4c68846e73c4d589f223c589ac76871b66811ded7836 go1.24.3.linux-arm64.tar.gz
438d5d3d7dcb239b58d893a715672eabe670b9730b1fd1c8fc858a46722a598a go1.24.2.linux-armv6l.tar.gz 17a392d7e826625dd12a32099df0b00b85c32d8132ed86fe917183ee5c3f88ed go1.24.3.linux-armv6l.tar.gz
6aefd3bf59c3c5592eda4fb287322207f119c2210f3795afa9be48d3ccb73e1b go1.24.2.linux-loong64.tar.gz e4b003c04c902edc140153d279b42167f1ad7c229f48f1f729bbef5e65e88d1f go1.24.3.linux-loong64.tar.gz
93e49bb4692783b0e9a2deab9558c6e8d2867f35592aeff285adda60924167f3 go1.24.2.linux-mips.tar.gz 1c79d89edf835edf9d4336ccea7cb89bc5c0ca82b12b36b218d599a5400d60fe go1.24.3.linux-mips.tar.gz
6e86e703675016f3faf6604b8f68f20dc1bba75849136e6dd4f43f69c8a4a9d9 go1.24.2.linux-mips64.tar.gz 0b64fe147d69f4d681d8e8a035c760477531432f83d831f18d37cb9bf3652488 go1.24.3.linux-mips64.tar.gz
f233d237538ca1559a7d7cf519a29f0147923a951377bc4e467af4c059e68851 go1.24.2.linux-mips64le.tar.gz 396b784c255b64512dc00c302c053e43a3cbfc77518664c6ac5569aafad4d1e6 go1.24.3.linux-mips64le.tar.gz
545e1b9a7939f923fd53bde98334b987ef42eb353ee3e0bfede8aa06079d6b24 go1.24.2.linux-mipsle.tar.gz 93898313887f14e8efbe9d7386d5da4792b2d6c492bee562993fd4c9daa75c6d go1.24.3.linux-mipsle.tar.gz
6eab31481f2f46187bc1b6c887662eef06fc9d7271a8390854072cdb387c8d74 go1.24.2.linux-ppc64.tar.gz 873ae3a6a6655a7b6f820e095d9965507e8dfd3cf76bc92d75c564ecbca385f6 go1.24.3.linux-ppc64.tar.gz
5fff857791d541c71d8ea0171c73f6f99770d15ff7e2ad979104856d01f36563 go1.24.2.linux-ppc64le.tar.gz 341a749d168f47b1d4dad25e32cae70849b7ceed7c290823b853c9e6b0df0856 go1.24.3.linux-ppc64le.tar.gz
91bda1558fcbd1c92769ad86c8f5cf796f8c67b0d9d9c19f76eecfc75ce71527 go1.24.2.linux-riscv64.tar.gz fa482f53ccb4ba280316b8c5751ea67291507280d9166f2a38fe4d9b5d5fb64b go1.24.3.linux-riscv64.tar.gz
1cb3448166d6abb515a85a3ee5afbdf932081fb58ad7143a8fb666fbc06146d9 go1.24.2.linux-s390x.tar.gz a87b0c2a079a0bece1620fb29a00e02b4dba17507850f837e754af7d57cda282 go1.24.3.linux-s390x.tar.gz
a9a2c0db2e826f20f00b02bee01dfdaeb49591c2f6ffacb78dc64a950894f7ff go1.24.2.netbsd-386.tar.gz 63155382308db1306200aff7821aa26bf2a2dda23537dd637a9704b485b6ddf0 go1.24.3.netbsd-386.tar.gz
cd1a35b76ed9c7b6c0c1616741bd319699a77867ade0be9924f32496c0a87a3f go1.24.2.netbsd-amd64.tar.gz fe2c5c79482958b867c08a4fc2a10a998de9c0206b08d5b3ebcb2232e8d2777c go1.24.3.netbsd-amd64.tar.gz
8c666388d066e479155cc5116950eeb435df28087ef277c18f1dc7479f836e60 go1.24.2.netbsd-arm.tar.gz e8ff77aef21521b5dd94e44282a3243309b80717414cf12f72835a45886a049f go1.24.3.netbsd-arm.tar.gz
5d42f0be04f58da5be788a1e260f8747c316b8ce182bf0b273c2e4c691feaa1a go1.24.2.netbsd-arm64.tar.gz b337fbaf82822685940ffaa76fbcf4be5d2f0258bc819cd80bc408b491f45c04 go1.24.3.netbsd-arm64.tar.gz
688effa23ea3973cc8b0fdf4246712cbeef55ff20c45f3a9e28b0c2db04246cf go1.24.2.openbsd-386.tar.gz c1bb9dd8418480aa7f65452b08de3759da3bf89702be71b5a9fc084836b24ad5 go1.24.3.openbsd-386.tar.gz
e5daf95f1048d8026b1366450a3f8044d668b0639db6422ad9a83755c6745cf7 go1.24.2.openbsd-amd64.tar.gz 531218de748b0caaf6d1ad18921206fc12baaa89bf483a0a5e60a571c206fe6f go1.24.3.openbsd-amd64.tar.gz
aeadaf74bd544d1a12ba9b14c0e7cdb1964de3ba9a52acb4619e91dbae7def7b go1.24.2.openbsd-arm.tar.gz bcd0dc959986fc346969b5d4111c3c8031882d8bf8d87a2c2ecf1328962a91f2 go1.24.3.openbsd-arm.tar.gz
9e222d9adb0ce836a5b3c8d5aadbd167c8869c030b113f4a81aa88e9a200f279 go1.24.2.openbsd-arm64.tar.gz 00ee6f8f1c41fd2e28ad386bd7e39acce7cab84af6de835855b29d1c597335c4 go1.24.3.openbsd-arm64.tar.gz
192fffa34536adc3cd1bb7c1ee785b8bc156ae7afd10bbf5db99ec8f2e93066e go1.24.2.openbsd-ppc64.tar.gz 9f4ec0a9203ed3c54ce1a2a390ad3d45838cdb7efd85baeff857e37dfde04edd go1.24.3.openbsd-ppc64.tar.gz
a23e90b451a390549042c2a7efbec6f29ed98b2d5618c8d2a35704e21be96e09 go1.24.2.openbsd-riscv64.tar.gz da4d6f80e2373250d8c31c32dcd1e08775c327c0d610923604660cc0e07e8cba go1.24.3.openbsd-riscv64.tar.gz
5cdcafe455d859b02779611a5a1e1d63e498b922e05818fb3debe410a5959e9e go1.24.2.plan9-386.tar.gz f5d02149132eedda6c2d46b360d7da462b8a5f9e3f8567db100c2d7bff0ddcd7 go1.24.3.plan9-386.tar.gz
81351659804fa505c1b3ec6fdf9599f7f88df08614307eeb96071bf5e2e74beb go1.24.2.plan9-amd64.tar.gz 175f3d79f4762a3c545d2c6393bf6b8bac24e838026869dafab06b930735c94f go1.24.3.plan9-amd64.tar.gz
6e337d5def14ed0123423c1c32e2e6d8b19161e5d5ffaa7356dad48ee0fd80b4 go1.24.2.plan9-arm.tar.gz d1e4ac15095da1611659261c2228c2058756cf87d61d9fad262f76755ef26849 go1.24.3.plan9-arm.tar.gz
07e6926ebc476c044d7d5b17706abfc52be52bccc2073d1734174efe63c6b35e go1.24.2.solaris-amd64.tar.gz e644220a6ced3c07a7acc1364193cb709a97737dd8b6792a07a8ec6d9996713e go1.24.3.solaris-amd64.tar.gz
13d86cb818bba331da75fcd18246ab31a1067b44fb4a243b6dfd93097eda7f37 go1.24.2.windows-386.zip 0d7e7dc0a31ba0cdd487415709d03b02fc9490ef111e8dfd22788a6d63316f37 go1.24.3.windows-386.msi
8a702d9f7104a15bd935f4191c58c24c0b6389e066b9d5661b93915114a2bef0 go1.24.2.windows-386.msi c27c463a61ab849266baa0c17a6c5c4256a574ab642f609ba25c96ec965dc184 go1.24.3.windows-386.zip
29c553aabee0743e2ffa3e9fa0cda00ef3b3cc4ff0bc92007f31f80fd69892e1 go1.24.2.windows-amd64.zip d5b7637e7e138be877d96a4501709d480e050d86a8f402bc950e72112b5aedc5 go1.24.3.windows-amd64.msi
acefb191e72fea0bdb1a3f5f8f6f5ab18b42b3bbce0c7183f189f25953aff275 go1.24.2.windows-amd64.msi be9787cb08998b1860fe3513e48a5fe5b96302d358a321b58e651184fa9638b3 go1.24.3.windows-amd64.zip
ab267f7f9a3366d48d7664be9e627ce3e63273231430cce5f7783fb910f14148 go1.24.2.windows-arm64.zip 7efde2e5e8468e9caf2c7fc94f4da78a726a5031a1ed63acff7899527cdddff6 go1.24.3.windows-arm64.msi
d187bfe539356c39573d2f46766d1d08122b4f33da00fd14d12485fa9e241ff5 go1.24.2.windows-arm64.msi eec9fa736056b54dd88ecb669db2bfad39b0c48f6f9080f036dfa1ca42dc4bb5 go1.24.3.windows-arm64.zip
# version:golangci 2.0.2 # version:golangci 2.0.2
# https://github.com/golangci/golangci-lint/releases/ # https://github.com/golangci/golangci-lint/releases/

View file

@ -59,6 +59,7 @@ import (
"github.com/cespare/cp" "github.com/cespare/cp"
"github.com/ethereum/go-ethereum/crypto/signify" "github.com/ethereum/go-ethereum/crypto/signify"
"github.com/ethereum/go-ethereum/internal/build" "github.com/ethereum/go-ethereum/internal/build"
"github.com/ethereum/go-ethereum/internal/download"
"github.com/ethereum/go-ethereum/internal/version" "github.com/ethereum/go-ethereum/internal/version"
) )
@ -190,7 +191,7 @@ func doInstall(cmdline []string) {
// Configure the toolchain. // Configure the toolchain.
tc := build.GoToolchain{GOARCH: *arch, CC: *cc} tc := build.GoToolchain{GOARCH: *arch, CC: *cc}
if *dlgo { if *dlgo {
csdb := build.MustLoadChecksums("build/checksums.txt") csdb := download.MustLoadChecksums("build/checksums.txt")
tc.Root = build.DownloadGo(csdb) tc.Root = build.DownloadGo(csdb)
} }
// Disable CLI markdown doc generation in release builds. // Disable CLI markdown doc generation in release builds.
@ -285,7 +286,7 @@ func doTest(cmdline []string) {
flag.CommandLine.Parse(cmdline) flag.CommandLine.Parse(cmdline)
// Get test fixtures. // Get test fixtures.
csdb := build.MustLoadChecksums("build/checksums.txt") csdb := download.MustLoadChecksums("build/checksums.txt")
downloadSpecTestFixtures(csdb, *cachedir) downloadSpecTestFixtures(csdb, *cachedir)
// Configure the toolchain. // Configure the toolchain.
@ -329,16 +330,11 @@ func doTest(cmdline []string) {
} }
// downloadSpecTestFixtures downloads and extracts the execution-spec-tests fixtures. // downloadSpecTestFixtures downloads and extracts the execution-spec-tests fixtures.
func downloadSpecTestFixtures(csdb *build.ChecksumDB, cachedir string) string { func downloadSpecTestFixtures(csdb *download.ChecksumDB, cachedir string) string {
executionSpecTestsVersion, err := build.Version(csdb, "spec-tests")
if err != nil {
log.Fatal(err)
}
ext := ".tar.gz" ext := ".tar.gz"
base := "fixtures_pectra-devnet-6" // TODO(s1na) rename once the version becomes part of the filename base := "fixtures_pectra-devnet-6" // TODO(s1na) rename once the version becomes part of the filename
url := fmt.Sprintf("https://github.com/ethereum/execution-spec-tests/releases/download/%s/%s%s", executionSpecTestsVersion, base, ext)
archivePath := filepath.Join(cachedir, base+ext) archivePath := filepath.Join(cachedir, base+ext)
if err := csdb.DownloadFile(url, archivePath); err != nil { if err := csdb.DownloadFileFromKnownURL(archivePath); err != nil {
log.Fatal(err) log.Fatal(err)
} }
if err := build.ExtractArchive(archivePath, executionSpecTestsDir); err != nil { if err := build.ExtractArchive(archivePath, executionSpecTestsDir); err != nil {
@ -444,14 +440,13 @@ func doLint(cmdline []string) {
// downloadLinter downloads and unpacks golangci-lint. // downloadLinter downloads and unpacks golangci-lint.
func downloadLinter(cachedir string) string { func downloadLinter(cachedir string) string {
csdb := build.MustLoadChecksums("build/checksums.txt") csdb := download.MustLoadChecksums("build/checksums.txt")
version, err := build.Version(csdb, "golangci") version, err := csdb.FindVersion("golangci")
if err != nil { if err != nil {
log.Fatal(err) log.Fatal(err)
} }
arch := runtime.GOARCH arch := runtime.GOARCH
ext := ".tar.gz" ext := ".tar.gz"
if runtime.GOOS == "windows" { if runtime.GOOS == "windows" {
ext = ".zip" ext = ".zip"
} }
@ -459,9 +454,8 @@ func downloadLinter(cachedir string) string {
arch += "v" + os.Getenv("GOARM") arch += "v" + os.Getenv("GOARM")
} }
base := fmt.Sprintf("golangci-lint-%s-%s-%s", version, runtime.GOOS, arch) base := fmt.Sprintf("golangci-lint-%s-%s-%s", version, runtime.GOOS, arch)
url := fmt.Sprintf("https://github.com/golangci/golangci-lint/releases/download/v%s/%s%s", version, base, ext)
archivePath := filepath.Join(cachedir, base+ext) archivePath := filepath.Join(cachedir, base+ext)
if err := csdb.DownloadFile(url, archivePath); err != nil { if err := csdb.DownloadFileFromKnownURL(archivePath); err != nil {
log.Fatal(err) log.Fatal(err)
} }
if err := build.ExtractArchive(archivePath, cachedir); err != nil { if err := build.ExtractArchive(archivePath, cachedir); err != nil {
@ -497,8 +491,8 @@ func protocArchiveBaseName() (string, error) {
// in the generate command. It returns the full path of the directory // in the generate command. It returns the full path of the directory
// containing the 'protoc-gen-go' executable. // containing the 'protoc-gen-go' executable.
func downloadProtocGenGo(cachedir string) string { func downloadProtocGenGo(cachedir string) string {
csdb := build.MustLoadChecksums("build/checksums.txt") csdb := download.MustLoadChecksums("build/checksums.txt")
version, err := build.Version(csdb, "protoc-gen-go") version, err := csdb.FindVersion("protoc-gen-go")
if err != nil { if err != nil {
log.Fatal(err) log.Fatal(err)
} }
@ -510,10 +504,8 @@ func downloadProtocGenGo(cachedir string) string {
archiveName += ".tar.gz" archiveName += ".tar.gz"
} }
url := fmt.Sprintf("https://github.com/protocolbuffers/protobuf-go/releases/download/v%s/%s", version, archiveName)
archivePath := path.Join(cachedir, archiveName) archivePath := path.Join(cachedir, archiveName)
if err := csdb.DownloadFile(url, archivePath); err != nil { if err := csdb.DownloadFileFromKnownURL(archivePath); err != nil {
log.Fatal(err) log.Fatal(err)
} }
extractDest := filepath.Join(cachedir, baseName) extractDest := filepath.Join(cachedir, baseName)
@ -531,8 +523,8 @@ func downloadProtocGenGo(cachedir string) string {
// files as a CI step. It returns the full path to the directory containing // files as a CI step. It returns the full path to the directory containing
// the protoc executable. // the protoc executable.
func downloadProtoc(cachedir string) string { func downloadProtoc(cachedir string) string {
csdb := build.MustLoadChecksums("build/checksums.txt") csdb := download.MustLoadChecksums("build/checksums.txt")
version, err := build.Version(csdb, "protoc") version, err := csdb.FindVersion("protoc")
if err != nil { if err != nil {
log.Fatal(err) log.Fatal(err)
} }
@ -543,10 +535,8 @@ func downloadProtoc(cachedir string) string {
fileName := fmt.Sprintf("protoc-%s-%s", version, baseName) fileName := fmt.Sprintf("protoc-%s-%s", version, baseName)
archiveFileName := fileName + ".zip" archiveFileName := fileName + ".zip"
url := fmt.Sprintf("https://github.com/protocolbuffers/protobuf/releases/download/v%s/%s", version, archiveFileName)
archivePath := filepath.Join(cachedir, archiveFileName) archivePath := filepath.Join(cachedir, archiveFileName)
if err := csdb.DownloadFileFromKnownURL(archivePath); err != nil {
if err := csdb.DownloadFile(url, archivePath); err != nil {
log.Fatal(err) log.Fatal(err)
} }
extractDest := filepath.Join(cachedir, fileName) extractDest := filepath.Join(cachedir, fileName)
@ -826,18 +816,17 @@ func doDebianSource(cmdline []string) {
// downloadGoBootstrapSources downloads the Go source tarball(s) that will be used // downloadGoBootstrapSources downloads the Go source tarball(s) that will be used
// to bootstrap the builder Go. // to bootstrap the builder Go.
func downloadGoBootstrapSources(cachedir string) []string { func downloadGoBootstrapSources(cachedir string) []string {
csdb := build.MustLoadChecksums("build/checksums.txt") csdb := download.MustLoadChecksums("build/checksums.txt")
var bundles []string var bundles []string
for _, booter := range []string{"ppa-builder-1.19", "ppa-builder-1.21", "ppa-builder-1.23"} { for _, booter := range []string{"ppa-builder-1.19", "ppa-builder-1.21", "ppa-builder-1.23"} {
gobootVersion, err := build.Version(csdb, booter) gobootVersion, err := csdb.FindVersion(booter)
if err != nil { if err != nil {
log.Fatal(err) log.Fatal(err)
} }
file := fmt.Sprintf("go%s.src.tar.gz", gobootVersion) file := fmt.Sprintf("go%s.src.tar.gz", gobootVersion)
url := "https://dl.google.com/go/" + file
dst := filepath.Join(cachedir, file) dst := filepath.Join(cachedir, file)
if err := csdb.DownloadFile(url, dst); err != nil { if err := csdb.DownloadFileFromKnownURL(dst); err != nil {
log.Fatal(err) log.Fatal(err)
} }
bundles = append(bundles, dst) bundles = append(bundles, dst)
@ -847,15 +836,14 @@ func downloadGoBootstrapSources(cachedir string) []string {
// downloadGoSources downloads the Go source tarball. // downloadGoSources downloads the Go source tarball.
func downloadGoSources(cachedir string) string { func downloadGoSources(cachedir string) string {
csdb := build.MustLoadChecksums("build/checksums.txt") csdb := download.MustLoadChecksums("build/checksums.txt")
dlgoVersion, err := build.Version(csdb, "golang") dlgoVersion, err := csdb.FindVersion("golang")
if err != nil { if err != nil {
log.Fatal(err) log.Fatal(err)
} }
file := fmt.Sprintf("go%s.src.tar.gz", dlgoVersion) file := fmt.Sprintf("go%s.src.tar.gz", dlgoVersion)
url := "https://dl.google.com/go/" + file
dst := filepath.Join(cachedir, file) dst := filepath.Join(cachedir, file)
if err := csdb.DownloadFile(url, dst); err != nil { if err := csdb.DownloadFileFromKnownURL(dst); err != nil {
log.Fatal(err) log.Fatal(err)
} }
return dst return dst
@ -1181,5 +1169,6 @@ func doPurge(cmdline []string) {
} }
func doSanityCheck() { func doSanityCheck() {
build.DownloadAndVerifyChecksums(build.MustLoadChecksums("build/checksums.txt")) csdb := download.MustLoadChecksums("build/checksums.txt")
csdb.DownloadAndVerifyAll()
} }

View file

@ -66,10 +66,9 @@ func (s *Suite) dialAs(key *ecdsa.PrivateKey) (*Conn, error) {
return nil, err return nil, err
} }
conn.caps = []p2p.Cap{ conn.caps = []p2p.Cap{
{Name: "eth", Version: 67}, {Name: "eth", Version: 69},
{Name: "eth", Version: 68},
} }
conn.ourHighestProtoVersion = 68 conn.ourHighestProtoVersion = 69
return &conn, nil return &conn, nil
} }
@ -156,7 +155,7 @@ func (c *Conn) ReadEth() (any, error) {
var msg any var msg any
switch int(code) { switch int(code) {
case eth.StatusMsg: case eth.StatusMsg:
msg = new(eth.StatusPacket) msg = new(eth.StatusPacket69)
case eth.GetBlockHeadersMsg: case eth.GetBlockHeadersMsg:
msg = new(eth.GetBlockHeadersPacket) msg = new(eth.GetBlockHeadersPacket)
case eth.BlockHeadersMsg: case eth.BlockHeadersMsg:
@ -231,7 +230,7 @@ func (c *Conn) ReadSnap() (any, error) {
// peer performs both the protocol handshake and the status message // peer performs both the protocol handshake and the status message
// exchange with the node in order to peer with it. // exchange with the node in order to peer with it.
func (c *Conn) peer(chain *Chain, status *eth.StatusPacket) error { func (c *Conn) peer(chain *Chain, status *eth.StatusPacket69) error {
if err := c.handshake(); err != nil { if err := c.handshake(); err != nil {
return fmt.Errorf("handshake failed: %v", err) return fmt.Errorf("handshake failed: %v", err)
} }
@ -304,7 +303,7 @@ func (c *Conn) negotiateEthProtocol(caps []p2p.Cap) {
} }
// statusExchange performs a `Status` message exchange with the given node. // statusExchange performs a `Status` message exchange with the given node.
func (c *Conn) statusExchange(chain *Chain, status *eth.StatusPacket) error { func (c *Conn) statusExchange(chain *Chain, status *eth.StatusPacket69) error {
loop: loop:
for { for {
code, data, err := c.Read() code, data, err := c.Read()
@ -313,12 +312,16 @@ loop:
} }
switch code { switch code {
case eth.StatusMsg + protoOffset(ethProto): case eth.StatusMsg + protoOffset(ethProto):
msg := new(eth.StatusPacket) msg := new(eth.StatusPacket69)
if err := rlp.DecodeBytes(data, &msg); err != nil { if err := rlp.DecodeBytes(data, &msg); err != nil {
return fmt.Errorf("error decoding status packet: %w", err) return fmt.Errorf("error decoding status packet: %w", err)
} }
if have, want := msg.Head, chain.blocks[chain.Len()-1].Hash(); have != want { if have, want := msg.LatestBlock, chain.blocks[chain.Len()-1].NumberU64(); have != want {
return fmt.Errorf("wrong head block in status, want: %#x (block %d) have %#x", return fmt.Errorf("wrong head block in status, want: %d, have %d",
want, have)
}
if have, want := msg.LatestBlockHash, chain.blocks[chain.Len()-1].Hash(); have != want {
return fmt.Errorf("wrong head block in status, want: %#x (block %d) have %#x",
want, chain.blocks[chain.Len()-1].NumberU64(), have) want, chain.blocks[chain.Len()-1].NumberU64(), have)
} }
if have, want := msg.ForkID, chain.ForkID(); !reflect.DeepEqual(have, want) { if have, want := msg.ForkID, chain.ForkID(); !reflect.DeepEqual(have, want) {
@ -348,13 +351,14 @@ loop:
} }
if status == nil { if status == nil {
// default status message // default status message
status = &eth.StatusPacket{ status = &eth.StatusPacket69{
ProtocolVersion: uint32(c.negotiatedProtoVersion), ProtocolVersion: uint32(c.negotiatedProtoVersion),
NetworkID: chain.config.ChainID.Uint64(), NetworkID: chain.config.ChainID.Uint64(),
TD: chain.TD(),
Head: chain.blocks[chain.Len()-1].Hash(),
Genesis: chain.blocks[0].Hash(), Genesis: chain.blocks[0].Hash(),
ForkID: chain.ForkID(), ForkID: chain.ForkID(),
EarliestBlock: 0,
LatestBlock: chain.blocks[chain.Len()-1].NumberU64(),
LatestBlockHash: chain.blocks[chain.Len()-1].Hash(),
} }
} }
if err := c.Write(ethProto, eth.StatusMsg, status); err != nil { if err := c.Write(ethProto, eth.StatusMsg, status); err != nil {

View file

@ -32,7 +32,7 @@ const (
// Unexported devp2p protocol lengths from p2p package. // Unexported devp2p protocol lengths from p2p package.
const ( const (
baseProtoLen = 16 baseProtoLen = 16
ethProtoLen = 17 ethProtoLen = 18
snapProtoLen = 8 snapProtoLen = 8
) )

View file

@ -70,11 +70,13 @@ func (s *Suite) EthTests() []utesting.Test {
{Name: "Status", Fn: s.TestStatus}, {Name: "Status", Fn: s.TestStatus},
// get block headers // get block headers
{Name: "GetBlockHeaders", Fn: s.TestGetBlockHeaders}, {Name: "GetBlockHeaders", Fn: s.TestGetBlockHeaders},
{Name: "GetNonexistentBlockHeaders", Fn: s.TestGetNonexistentBlockHeaders},
{Name: "SimultaneousRequests", Fn: s.TestSimultaneousRequests}, {Name: "SimultaneousRequests", Fn: s.TestSimultaneousRequests},
{Name: "SameRequestID", Fn: s.TestSameRequestID}, {Name: "SameRequestID", Fn: s.TestSameRequestID},
{Name: "ZeroRequestID", Fn: s.TestZeroRequestID}, {Name: "ZeroRequestID", Fn: s.TestZeroRequestID},
// get block bodies // get history
{Name: "GetBlockBodies", Fn: s.TestGetBlockBodies}, {Name: "GetBlockBodies", Fn: s.TestGetBlockBodies},
{Name: "GetReceipts", Fn: s.TestGetReceipts},
// // malicious handshakes + status // // malicious handshakes + status
{Name: "MaliciousHandshake", Fn: s.TestMaliciousHandshake}, {Name: "MaliciousHandshake", Fn: s.TestMaliciousHandshake},
// test transactions // test transactions
@ -158,6 +160,48 @@ func (s *Suite) TestGetBlockHeaders(t *utesting.T) {
} }
} }
func (s *Suite) TestGetNonexistentBlockHeaders(t *utesting.T) {
t.Log(`This test sends GetBlockHeaders requests for nonexistent blocks (using max uint64 value)
to check if the node disconnects after receiving multiple invalid requests.`)
conn, err := s.dial()
if err != nil {
t.Fatalf("dial failed: %v", err)
}
defer conn.Close()
if err := conn.peer(s.chain, nil); err != nil {
t.Fatalf("peering failed: %v", err)
}
// Create request with max uint64 value for a nonexistent block
badReq := &eth.GetBlockHeadersPacket{
GetBlockHeadersRequest: &eth.GetBlockHeadersRequest{
Origin: eth.HashOrNumber{Number: ^uint64(0)},
Amount: 1,
Skip: 0,
Reverse: false,
},
}
// Send request 10 times. Some clients are lient on the first few invalids.
for i := 0; i < 10; i++ {
badReq.RequestId = uint64(i)
if err := conn.Write(ethProto, eth.GetBlockHeadersMsg, badReq); err != nil {
if err == errDisc {
t.Fatalf("peer disconnected after %d requests", i+1)
}
t.Fatalf("write failed: %v", err)
}
}
// Check if peer disconnects at the end.
code, _, err := conn.Read()
if err == errDisc || code == discMsg {
t.Fatal("peer improperly disconnected")
}
}
func (s *Suite) TestSimultaneousRequests(t *utesting.T) { func (s *Suite) TestSimultaneousRequests(t *utesting.T) {
t.Log(`This test requests blocks headers from the node, performing two requests t.Log(`This test requests blocks headers from the node, performing two requests
concurrently, with different request IDs.`) concurrently, with different request IDs.`)
@ -375,6 +419,51 @@ func (s *Suite) TestGetBlockBodies(t *utesting.T) {
} }
} }
func (s *Suite) TestGetReceipts(t *utesting.T) {
t.Log(`This test sends GetReceipts requests to the node for known blocks in the test chain.`)
conn, err := s.dial()
if err != nil {
t.Fatalf("dial failed: %v", err)
}
defer conn.Close()
if err := conn.peer(s.chain, nil); err != nil {
t.Fatalf("peering failed: %v", err)
}
// Find some blocks containing receipts.
var hashes = make([]common.Hash, 0, 3)
for i := range s.chain.Len() {
block := s.chain.GetBlock(i)
if len(block.Transactions()) > 0 {
hashes = append(hashes, block.Hash())
}
if len(hashes) == cap(hashes) {
break
}
}
// Create block bodies request.
req := &eth.GetReceiptsPacket{
RequestId: 66,
GetReceiptsRequest: (eth.GetReceiptsRequest)(hashes),
}
if err := conn.Write(ethProto, eth.GetReceiptsMsg, req); err != nil {
t.Fatalf("could not write to connection: %v", err)
}
// Wait for response.
resp := new(eth.ReceiptsPacket[*eth.ReceiptList69])
if err := conn.ReadMsg(ethProto, eth.ReceiptsMsg, &resp); err != nil {
t.Fatalf("error reading block bodies msg: %v", err)
}
if got, want := resp.RequestId, req.RequestId; got != want {
t.Fatalf("unexpected request id in respond", got, want)
}
if len(resp.List) != len(req.GetReceiptsRequest) {
t.Fatalf("wrong bodies in response: expected %d bodies, got %d", len(req.GetReceiptsRequest), len(resp.List))
}
}
// randBuf makes a random buffer size kilobytes large. // randBuf makes a random buffer size kilobytes large.
func randBuf(size int) []byte { func randBuf(size int) []byte {
buf := make([]byte, size*1024) buf := make([]byte, size*1024)
@ -457,6 +546,31 @@ func (s *Suite) TestMaliciousHandshake(t *utesting.T) {
} }
} }
func (s *Suite) TestInvalidBlockRangeUpdate(t *utesting.T) {
t.Log(`This test sends an invalid BlockRangeUpdate message to the node and expects to be disconnected.`)
conn, err := s.dial()
if err != nil {
t.Fatalf("dial failed: %v", err)
}
defer conn.Close()
if err := conn.peer(s.chain, nil); err != nil {
t.Fatalf("peering failed: %v", err)
}
conn.Write(ethProto, eth.BlockRangeUpdateMsg, &eth.BlockRangeUpdatePacket{
EarliestBlock: 10,
LatestBlock: 8,
LatestBlockHash: s.chain.GetBlock(8).Hash(),
})
if code, _, err := conn.Read(); err != nil {
t.Fatalf("expected disconnect, got err: %v", err)
} else if code != discMsg {
t.Fatalf("expected disconnect message, got msg code %d", code)
}
}
func (s *Suite) TestTransaction(t *utesting.T) { func (s *Suite) TestTransaction(t *utesting.T) {
t.Log(`This test sends a valid transaction to the node and checks if the t.Log(`This test sends a valid transaction to the node and checks if the
transaction gets propagated.`) transaction gets propagated.`)

View file

@ -363,9 +363,13 @@ func (pre *Prestate) Apply(vmConfig vm.Config, chainConfig *params.ChainConfig,
return nil, nil, nil, NewError(ErrorEVM, fmt.Errorf("could not parse requests logs: %v", err)) return nil, nil, nil, NewError(ErrorEVM, fmt.Errorf("could not parse requests logs: %v", err))
} }
// EIP-7002 // EIP-7002
core.ProcessWithdrawalQueue(&requests, evm) if err := core.ProcessWithdrawalQueue(&requests, evm); err != nil {
return nil, nil, nil, NewError(ErrorEVM, fmt.Errorf("could not process withdrawal requests: %v", err))
}
// EIP-7251 // EIP-7251
core.ProcessConsolidationQueue(&requests, evm) if err := core.ProcessConsolidationQueue(&requests, evm); err != nil {
return nil, nil, nil, NewError(ErrorEVM, fmt.Errorf("could not process consolidation requests: %v", err))
}
} }
// Commit block // Commit block

View file

@ -21,9 +21,12 @@ import (
"errors" "errors"
"fmt" "fmt"
"os" "os"
"path/filepath"
"regexp"
"runtime" "runtime"
"slices" "slices"
"strconv" "strconv"
"strings"
"sync/atomic" "sync/atomic"
"time" "time"
@ -39,6 +42,7 @@ import (
"github.com/ethereum/go-ethereum/ethdb" "github.com/ethereum/go-ethereum/ethdb"
"github.com/ethereum/go-ethereum/internal/debug" "github.com/ethereum/go-ethereum/internal/debug"
"github.com/ethereum/go-ethereum/internal/era" "github.com/ethereum/go-ethereum/internal/era"
"github.com/ethereum/go-ethereum/internal/era/eradl"
"github.com/ethereum/go-ethereum/internal/flags" "github.com/ethereum/go-ethereum/internal/flags"
"github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/log"
"github.com/ethereum/go-ethereum/params" "github.com/ethereum/go-ethereum/params"
@ -190,7 +194,7 @@ This command dumps out the state for a given block (or latest, if none provided)
`, `,
} }
pruneCommand = &cli.Command{ pruneHistoryCommand = &cli.Command{
Action: pruneHistory, Action: pruneHistory,
Name: "prune-history", Name: "prune-history",
Usage: "Prune blockchain history (block bodies and receipts) up to the merge block", Usage: "Prune blockchain history (block bodies and receipts) up to the merge block",
@ -201,6 +205,42 @@ The prune-history command removes historical block bodies and receipts from the
blockchain database up to the merge block, while preserving block headers. This blockchain database up to the merge block, while preserving block headers. This
helps reduce storage requirements for nodes that don't need full historical data.`, helps reduce storage requirements for nodes that don't need full historical data.`,
} }
downloadEraCommand = &cli.Command{
Action: downloadEra,
Name: "download-era",
Usage: "Fetches era1 files (pre-merge history) from an HTTP endpoint",
ArgsUsage: "",
Flags: slices.Concat(
utils.DatabaseFlags,
utils.NetworkFlags,
[]cli.Flag{
eraBlockFlag,
eraEpochFlag,
eraAllFlag,
eraServerFlag,
},
),
}
)
var (
eraBlockFlag = &cli.StringFlag{
Name: "block",
Usage: "Block number to fetch. (can also be a range <start>-<end>)",
}
eraEpochFlag = &cli.StringFlag{
Name: "epoch",
Usage: "Epoch number to fetch (can also be a range <start>-<end>)",
}
eraAllFlag = &cli.BoolFlag{
Name: "all",
Usage: "Download all available era1 files",
}
eraServerFlag = &cli.StringFlag{
Name: "server",
Usage: "era1 server URL",
}
) )
// initGenesis will initialise the given JSON format genesis file and writes it as // initGenesis will initialise the given JSON format genesis file and writes it as
@ -246,10 +286,13 @@ func initGenesis(ctx *cli.Context) error {
triedb := utils.MakeTrieDatabase(ctx, chaindb, ctx.Bool(utils.CachePreimagesFlag.Name), false, genesis.IsVerkle()) triedb := utils.MakeTrieDatabase(ctx, chaindb, ctx.Bool(utils.CachePreimagesFlag.Name), false, genesis.IsVerkle())
defer triedb.Close() defer triedb.Close()
_, hash, _, err := core.SetupGenesisBlockWithOverride(chaindb, triedb, genesis, &overrides) _, hash, compatErr, err := core.SetupGenesisBlockWithOverride(chaindb, triedb, genesis, &overrides)
if err != nil { if err != nil {
utils.Fatalf("Failed to write genesis block: %v", err) utils.Fatalf("Failed to write genesis block: %v", err)
} }
if compatErr != nil {
utils.Fatalf("Failed to write chain config: %v", compatErr)
}
log.Info("Successfully wrote genesis state", "database", "chaindata", "hash", hash) log.Info("Successfully wrote genesis state", "database", "chaindata", "hash", hash)
return nil return nil
@ -662,3 +705,83 @@ func pruneHistory(ctx *cli.Context) error {
return nil return nil
} }
// downladEra is the era1 file downloader tool.
func downloadEra(ctx *cli.Context) error {
flags.CheckExclusive(ctx, eraBlockFlag, eraEpochFlag, eraAllFlag)
// Resolve the network.
var network = "mainnet"
if utils.IsNetworkPreset(ctx) {
switch {
case ctx.IsSet(utils.MainnetFlag.Name):
case ctx.IsSet(utils.SepoliaFlag.Name):
network = "sepolia"
default:
return fmt.Errorf("unsupported network, no known era1 checksums")
}
}
// Resolve the destination directory.
stack, _ := makeConfigNode(ctx)
defer stack.Close()
ancients := stack.ResolveAncient("chaindata", "")
dir := filepath.Join(ancients, "era")
baseURL := ctx.String(eraServerFlag.Name)
if baseURL == "" {
return fmt.Errorf("need --%s flag to download", eraServerFlag.Name)
}
l, err := eradl.New(baseURL, network)
if err != nil {
return err
}
switch {
case ctx.IsSet(eraAllFlag.Name):
return l.DownloadAll(dir)
case ctx.IsSet(eraBlockFlag.Name):
s := ctx.String(eraBlockFlag.Name)
start, end, ok := parseRange(s)
if !ok {
return fmt.Errorf("invalid block range: %q", s)
}
return l.DownloadBlockRange(start, end, dir)
case ctx.IsSet(eraEpochFlag.Name):
s := ctx.String(eraEpochFlag.Name)
start, end, ok := parseRange(s)
if !ok {
return fmt.Errorf("invalid epoch range: %q", s)
}
return l.DownloadEpochRange(start, end, dir)
default:
return fmt.Errorf("specify one of --%s, --%s, or --%s to download", eraAllFlag.Name, eraBlockFlag.Name, eraEpochFlag.Name)
}
}
func parseRange(s string) (start uint64, end uint64, ok bool) {
if m, _ := regexp.MatchString("[0-9]+", s); m {
start, err := strconv.ParseUint(s, 10, 64)
if err != nil {
return 0, 0, false
}
end = start
return start, end, true
}
if m, _ := regexp.MatchString("[0-9]+-[0-9]+", s); m {
s1, s2, _ := strings.Cut(s, "-")
start, err := strconv.ParseUint(s1, 10, 64)
if err != nil {
return 0, 0, false
}
end, err = strconv.ParseUint(s2, 10, 64)
if err != nil {
return 0, 0, false
}
return start, end, true
}
return 0, 0, false
}

View file

@ -39,9 +39,8 @@ const (
// child g gets a temporary data directory. // child g gets a temporary data directory.
func runMinimalGeth(t *testing.T, args ...string) *testgeth { func runMinimalGeth(t *testing.T, args ...string) *testgeth {
// --holesky to make the 'writing genesis to disk' faster (no accounts) // --holesky to make the 'writing genesis to disk' faster (no accounts)
// --networkid=1337 to avoid cache bump
// --syncmode=full to avoid allocating fast sync bloom // --syncmode=full to avoid allocating fast sync bloom
allArgs := []string{"--holesky", "--networkid", "1337", "--authrpc.port", "0", "--syncmode=full", "--port", "0", allArgs := []string{"--holesky", "--authrpc.port", "0", "--syncmode=full", "--port", "0",
"--nat", "none", "--nodiscover", "--maxpeers", "0", "--cache", "64", "--nat", "none", "--nodiscover", "--maxpeers", "0", "--cache", "64",
"--datadir.minfreedisk", "0"} "--datadir.minfreedisk", "0"}
return runGeth(t, append(allArgs, args...)...) return runGeth(t, append(allArgs, args...)...)
@ -103,17 +102,17 @@ func TestAttachWelcome(t *testing.T) {
"--http", "--http.port", httpPort, "--http", "--http.port", httpPort,
"--ws", "--ws.port", wsPort) "--ws", "--ws.port", wsPort)
t.Run("ipc", func(t *testing.T) { t.Run("ipc", func(t *testing.T) {
waitForEndpoint(t, ipc, 4*time.Second) waitForEndpoint(t, ipc, 2*time.Minute)
testAttachWelcome(t, geth, "ipc:"+ipc, ipcAPIs) testAttachWelcome(t, geth, "ipc:"+ipc, ipcAPIs)
}) })
t.Run("http", func(t *testing.T) { t.Run("http", func(t *testing.T) {
endpoint := "http://127.0.0.1:" + httpPort endpoint := "http://127.0.0.1:" + httpPort
waitForEndpoint(t, endpoint, 4*time.Second) waitForEndpoint(t, endpoint, 2*time.Minute)
testAttachWelcome(t, geth, endpoint, httpAPIs) testAttachWelcome(t, geth, endpoint, httpAPIs)
}) })
t.Run("ws", func(t *testing.T) { t.Run("ws", func(t *testing.T) {
endpoint := "ws://127.0.0.1:" + wsPort endpoint := "ws://127.0.0.1:" + wsPort
waitForEndpoint(t, endpoint, 4*time.Second) waitForEndpoint(t, endpoint, 2*time.Minute)
testAttachWelcome(t, geth, endpoint, httpAPIs) testAttachWelcome(t, geth, endpoint, httpAPIs)
}) })
geth.Kill() geth.Kill()

View file

@ -91,13 +91,7 @@ var (
utils.LogNoHistoryFlag, utils.LogNoHistoryFlag,
utils.LogExportCheckpointsFlag, utils.LogExportCheckpointsFlag,
utils.StateHistoryFlag, utils.StateHistoryFlag,
utils.LightServeFlag, // deprecated
utils.LightIngressFlag, // deprecated
utils.LightEgressFlag, // deprecated
utils.LightMaxPeersFlag, // deprecated
utils.LightNoPruneFlag, // deprecated
utils.LightKDFFlag, utils.LightKDFFlag,
utils.LightNoSyncServeFlag, // deprecated
utils.EthRequiredBlocksFlag, utils.EthRequiredBlocksFlag,
utils.LegacyWhitelistFlag, // deprecated utils.LegacyWhitelistFlag, // deprecated
utils.CacheFlag, utils.CacheFlag,
@ -225,7 +219,8 @@ func init() {
removedbCommand, removedbCommand,
dumpCommand, dumpCommand,
dumpGenesisCommand, dumpGenesisCommand,
pruneCommand, pruneHistoryCommand,
downloadEraCommand,
// See accountcmd.go: // See accountcmd.go:
accountCommand, accountCommand,
walletCommand, walletCommand,

View file

@ -309,7 +309,8 @@ func ImportHistory(chain *core.BlockChain, dir string, network string) error {
if err != nil { if err != nil {
return fmt.Errorf("error reading receipts %d: %w", it.Number(), err) return fmt.Errorf("error reading receipts %d: %w", it.Number(), err)
} }
if _, err := chain.InsertReceiptChain([]*types.Block{block}, []types.Receipts{receipts}, 2^64-1); err != nil { encReceipts := types.EncodeBlockReceiptLists([]types.Receipts{receipts})
if _, err := chain.InsertReceiptChain([]*types.Block{block}, encReceipts, 2^64-1); err != nil {
return fmt.Errorf("error inserting body %d: %w", it.Number(), err) return fmt.Errorf("error inserting body %d: %w", it.Number(), err)
} }
imported += 1 imported += 1

View file

@ -1245,28 +1245,6 @@ func setIPC(ctx *cli.Context, cfg *node.Config) {
} }
} }
// setLes shows the deprecation warnings for LES flags.
func setLes(ctx *cli.Context, cfg *ethconfig.Config) {
if ctx.IsSet(LightServeFlag.Name) {
log.Warn("The light server has been deprecated, please remove this flag", "flag", LightServeFlag.Name)
}
if ctx.IsSet(LightIngressFlag.Name) {
log.Warn("The light server has been deprecated, please remove this flag", "flag", LightIngressFlag.Name)
}
if ctx.IsSet(LightEgressFlag.Name) {
log.Warn("The light server has been deprecated, please remove this flag", "flag", LightEgressFlag.Name)
}
if ctx.IsSet(LightMaxPeersFlag.Name) {
log.Warn("The light server has been deprecated, please remove this flag", "flag", LightMaxPeersFlag.Name)
}
if ctx.IsSet(LightNoPruneFlag.Name) {
log.Warn("The light server has been deprecated, please remove this flag", "flag", LightNoPruneFlag.Name)
}
if ctx.IsSet(LightNoSyncServeFlag.Name) {
log.Warn("The light server has been deprecated, please remove this flag", "flag", LightNoSyncServeFlag.Name)
}
}
// MakeDatabaseHandles raises out the number of allowed file handles per process // MakeDatabaseHandles raises out the number of allowed file handles per process
// for Geth and returns half of the allowance to assign to the database. // for Geth and returns half of the allowance to assign to the database.
func MakeDatabaseHandles(max int) int { func MakeDatabaseHandles(max int) int {
@ -1571,8 +1549,8 @@ func setRequiredBlocks(ctx *cli.Context, cfg *ethconfig.Config) {
// SetEthConfig applies eth-related command line flags to the config. // SetEthConfig applies eth-related command line flags to the config.
func SetEthConfig(ctx *cli.Context, stack *node.Node, cfg *ethconfig.Config) { func SetEthConfig(ctx *cli.Context, stack *node.Node, cfg *ethconfig.Config) {
// Avoid conflicting network flags // Avoid conflicting network flags, don't allow network id override on preset networks
flags.CheckExclusive(ctx, MainnetFlag, DeveloperFlag, SepoliaFlag, HoleskyFlag, HoodiFlag) flags.CheckExclusive(ctx, MainnetFlag, DeveloperFlag, SepoliaFlag, HoleskyFlag, HoodiFlag, NetworkIdFlag)
flags.CheckExclusive(ctx, DeveloperFlag, ExternalSignerFlag) // Can't use both ephemeral unlocked and external signer flags.CheckExclusive(ctx, DeveloperFlag, ExternalSignerFlag) // Can't use both ephemeral unlocked and external signer
// Set configurations from CLI flags // Set configurations from CLI flags
@ -1582,7 +1560,6 @@ func SetEthConfig(ctx *cli.Context, stack *node.Node, cfg *ethconfig.Config) {
setBlobPool(ctx, &cfg.BlobPool) setBlobPool(ctx, &cfg.BlobPool)
setMiner(ctx, &cfg.Miner) setMiner(ctx, &cfg.Miner)
setRequiredBlocks(ctx, cfg) setRequiredBlocks(ctx, cfg)
setLes(ctx, cfg)
// Cap the cache allowance and tune the garbage collector // Cap the cache allowance and tune the garbage collector
mem, err := gopsutil.VirtualMemory() mem, err := gopsutil.VirtualMemory()
@ -1712,7 +1689,6 @@ func SetEthConfig(ctx *cli.Context, stack *node.Node, cfg *ethconfig.Config) {
} }
} }
if ctx.IsSet(VMEnableDebugFlag.Name) { if ctx.IsSet(VMEnableDebugFlag.Name) {
// TODO(fjl): force-enable this in --dev mode
cfg.EnablePreimageRecording = ctx.Bool(VMEnableDebugFlag.Name) cfg.EnablePreimageRecording = ctx.Bool(VMEnableDebugFlag.Name)
} }
@ -1743,34 +1719,25 @@ func SetEthConfig(ctx *cli.Context, stack *node.Node, cfg *ethconfig.Config) {
// Override any default configs for hard coded networks. // Override any default configs for hard coded networks.
switch { switch {
case ctx.Bool(MainnetFlag.Name): case ctx.Bool(MainnetFlag.Name):
if !ctx.IsSet(NetworkIdFlag.Name) { cfg.NetworkId = 1
cfg.NetworkId = 1
}
cfg.Genesis = core.DefaultGenesisBlock() cfg.Genesis = core.DefaultGenesisBlock()
SetDNSDiscoveryDefaults(cfg, params.MainnetGenesisHash) SetDNSDiscoveryDefaults(cfg, params.MainnetGenesisHash)
case ctx.Bool(HoleskyFlag.Name): case ctx.Bool(HoleskyFlag.Name):
if !ctx.IsSet(NetworkIdFlag.Name) { cfg.NetworkId = 17000
cfg.NetworkId = 17000
}
cfg.Genesis = core.DefaultHoleskyGenesisBlock() cfg.Genesis = core.DefaultHoleskyGenesisBlock()
SetDNSDiscoveryDefaults(cfg, params.HoleskyGenesisHash) SetDNSDiscoveryDefaults(cfg, params.HoleskyGenesisHash)
case ctx.Bool(SepoliaFlag.Name): case ctx.Bool(SepoliaFlag.Name):
if !ctx.IsSet(NetworkIdFlag.Name) { cfg.NetworkId = 11155111
cfg.NetworkId = 11155111
}
cfg.Genesis = core.DefaultSepoliaGenesisBlock() cfg.Genesis = core.DefaultSepoliaGenesisBlock()
SetDNSDiscoveryDefaults(cfg, params.SepoliaGenesisHash) SetDNSDiscoveryDefaults(cfg, params.SepoliaGenesisHash)
case ctx.Bool(HoodiFlag.Name): case ctx.Bool(HoodiFlag.Name):
if !ctx.IsSet(NetworkIdFlag.Name) { cfg.NetworkId = 560048
cfg.NetworkId = 560048
}
cfg.Genesis = core.DefaultHoodiGenesisBlock() cfg.Genesis = core.DefaultHoodiGenesisBlock()
SetDNSDiscoveryDefaults(cfg, params.HoodiGenesisHash) SetDNSDiscoveryDefaults(cfg, params.HoodiGenesisHash)
case ctx.Bool(DeveloperFlag.Name): case ctx.Bool(DeveloperFlag.Name):
if !ctx.IsSet(NetworkIdFlag.Name) { cfg.NetworkId = 1337
cfg.NetworkId = 1337
}
cfg.SyncMode = ethconfig.FullSync cfg.SyncMode = ethconfig.FullSync
cfg.EnablePreimageRecording = true
// Create new developer account or reuse existing one // Create new developer account or reuse existing one
var ( var (
developer accounts.Account developer accounts.Account
@ -1854,10 +1821,16 @@ func SetEthConfig(ctx *cli.Context, stack *node.Node, cfg *ethconfig.Config) {
if ctx.String(CryptoKZGFlag.Name) != "gokzg" && ctx.String(CryptoKZGFlag.Name) != "ckzg" { if ctx.String(CryptoKZGFlag.Name) != "gokzg" && ctx.String(CryptoKZGFlag.Name) != "ckzg" {
Fatalf("--%s flag must be 'gokzg' or 'ckzg'", CryptoKZGFlag.Name) Fatalf("--%s flag must be 'gokzg' or 'ckzg'", CryptoKZGFlag.Name)
} }
log.Info("Initializing the KZG library", "backend", ctx.String(CryptoKZGFlag.Name)) // The initialization of the KZG library can take up to 2 seconds
if err := kzg4844.UseCKZG(ctx.String(CryptoKZGFlag.Name) == "ckzg"); err != nil { // We start this here in parallel, so it should be available
Fatalf("Failed to set KZG library implementation to %s: %v", ctx.String(CryptoKZGFlag.Name), err) // once we start executing blocks. It's threadsafe.
} go func() {
log.Info("Initializing the KZG library", "backend", ctx.String(CryptoKZGFlag.Name))
if err := kzg4844.UseCKZG(ctx.String(CryptoKZGFlag.Name) == "ckzg"); err != nil {
Fatalf("Failed to set KZG library implementation to %s: %v", ctx.String(CryptoKZGFlag.Name), err)
}
}()
// VM tracing config. // VM tracing config.
if ctx.IsSet(VMTraceFlag.Name) { if ctx.IsSet(VMTraceFlag.Name) {
if name := ctx.String(VMTraceFlag.Name); name != "" { if name := ctx.String(VMTraceFlag.Name); name != "" {

View file

@ -39,12 +39,6 @@ var DeprecatedFlags = []cli.Flag{
CacheTrieRejournalFlag, CacheTrieRejournalFlag,
LegacyDiscoveryV5Flag, LegacyDiscoveryV5Flag,
TxLookupLimitFlag, TxLookupLimitFlag,
LightServeFlag,
LightIngressFlag,
LightEgressFlag,
LightMaxPeersFlag,
LightNoPruneFlag,
LightNoSyncServeFlag,
LogBacktraceAtFlag, LogBacktraceAtFlag,
LogDebugFlag, LogDebugFlag,
MinerNewPayloadTimeoutFlag, MinerNewPayloadTimeoutFlag,
@ -88,37 +82,6 @@ var (
Value: ethconfig.Defaults.TransactionHistory, Value: ethconfig.Defaults.TransactionHistory,
Category: flags.DeprecatedCategory, Category: flags.DeprecatedCategory,
} }
// Light server and client settings, Deprecated November 2023
LightServeFlag = &cli.IntFlag{
Name: "light.serve",
Usage: "Maximum percentage of time allowed for serving LES requests (deprecated)",
Category: flags.DeprecatedCategory,
}
LightIngressFlag = &cli.IntFlag{
Name: "light.ingress",
Usage: "Incoming bandwidth limit for serving light clients (deprecated)",
Category: flags.DeprecatedCategory,
}
LightEgressFlag = &cli.IntFlag{
Name: "light.egress",
Usage: "Outgoing bandwidth limit for serving light clients (deprecated)",
Category: flags.DeprecatedCategory,
}
LightMaxPeersFlag = &cli.IntFlag{
Name: "light.maxpeers",
Usage: "Maximum number of light clients to serve, or light servers to attach to (deprecated)",
Category: flags.DeprecatedCategory,
}
LightNoPruneFlag = &cli.BoolFlag{
Name: "light.nopruning",
Usage: "Disable ancient light chain data pruning (deprecated)",
Category: flags.DeprecatedCategory,
}
LightNoSyncServeFlag = &cli.BoolFlag{
Name: "light.nosyncserve",
Usage: "Enables serving light clients before syncing (deprecated)",
Category: flags.DeprecatedCategory,
}
// Deprecated November 2023 // Deprecated November 2023
LogBacktraceAtFlag = &cli.StringFlag{ LogBacktraceAtFlag = &cli.StringFlag{
Name: "log.backtrace", Name: "log.backtrace",

View file

@ -183,7 +183,7 @@ func benchInsertChain(b *testing.B, disk bool, gen func(int, *BlockGen)) {
if !disk { if !disk {
db = rawdb.NewMemoryDatabase() db = rawdb.NewMemoryDatabase()
} else { } else {
pdb, err := pebble.New(b.TempDir(), 128, 128, "", false, true) pdb, err := pebble.New(b.TempDir(), 128, 128, "", false)
if err != nil { if err != nil {
b.Fatalf("cannot create temporary database: %v", err) b.Fatalf("cannot create temporary database: %v", err)
} }
@ -303,7 +303,7 @@ func makeChainForBench(db ethdb.Database, genesis *Genesis, full bool, count uin
func benchWriteChain(b *testing.B, full bool, count uint64) { func benchWriteChain(b *testing.B, full bool, count uint64) {
genesis := &Genesis{Config: params.AllEthashProtocolChanges} genesis := &Genesis{Config: params.AllEthashProtocolChanges}
for i := 0; i < b.N; i++ { for i := 0; i < b.N; i++ {
pdb, err := pebble.New(b.TempDir(), 1024, 128, "", false, true) pdb, err := pebble.New(b.TempDir(), 1024, 128, "", false)
if err != nil { if err != nil {
b.Fatalf("error opening database: %v", err) b.Fatalf("error opening database: %v", err)
} }
@ -316,7 +316,7 @@ func benchWriteChain(b *testing.B, full bool, count uint64) {
func benchReadChain(b *testing.B, full bool, count uint64) { func benchReadChain(b *testing.B, full bool, count uint64) {
dir := b.TempDir() dir := b.TempDir()
pdb, err := pebble.New(dir, 1024, 128, "", false, true) pdb, err := pebble.New(dir, 1024, 128, "", false)
if err != nil { if err != nil {
b.Fatalf("error opening database: %v", err) b.Fatalf("error opening database: %v", err)
} }
@ -332,7 +332,7 @@ func benchReadChain(b *testing.B, full bool, count uint64) {
b.ResetTimer() b.ResetTimer()
for i := 0; i < b.N; i++ { for i := 0; i < b.N; i++ {
pdb, err = pebble.New(dir, 1024, 128, "", false, true) pdb, err = pebble.New(dir, 1024, 128, "", false)
if err != nil { if err != nil {
b.Fatalf("error opening database: %v", err) b.Fatalf("error opening database: %v", err)
} }

View file

@ -65,7 +65,8 @@ var (
headFinalizedBlockGauge = metrics.NewRegisteredGauge("chain/head/finalized", nil) headFinalizedBlockGauge = metrics.NewRegisteredGauge("chain/head/finalized", nil)
headSafeBlockGauge = metrics.NewRegisteredGauge("chain/head/safe", nil) headSafeBlockGauge = metrics.NewRegisteredGauge("chain/head/safe", nil)
chainInfoGauge = metrics.NewRegisteredGaugeInfo("chain/info", nil) chainInfoGauge = metrics.NewRegisteredGaugeInfo("chain/info", nil)
chainMgaspsGauge = metrics.NewRegisteredGauge("chain/mgasps", nil)
accountReadTimer = metrics.NewRegisteredResettingTimer("chain/account/reads", nil) accountReadTimer = metrics.NewRegisteredResettingTimer("chain/account/reads", nil)
accountHashTimer = metrics.NewRegisteredResettingTimer("chain/account/hashes", nil) accountHashTimer = metrics.NewRegisteredResettingTimer("chain/account/hashes", nil)
@ -92,8 +93,10 @@ var (
blockReorgAddMeter = metrics.NewRegisteredMeter("chain/reorg/add", nil) blockReorgAddMeter = metrics.NewRegisteredMeter("chain/reorg/add", nil)
blockReorgDropMeter = metrics.NewRegisteredMeter("chain/reorg/drop", nil) blockReorgDropMeter = metrics.NewRegisteredMeter("chain/reorg/drop", nil)
blockPrefetchExecuteTimer = metrics.NewRegisteredTimer("chain/prefetch/executes", nil) blockPrefetchExecuteTimer = metrics.NewRegisteredResettingTimer("chain/prefetch/executes", nil)
blockPrefetchInterruptMeter = metrics.NewRegisteredMeter("chain/prefetch/interrupts", nil) blockPrefetchInterruptMeter = metrics.NewRegisteredMeter("chain/prefetch/interrupts", nil)
blockPrefetchTxsInvalidMeter = metrics.NewRegisteredMeter("chain/prefetch/txs/invalid", nil)
blockPrefetchTxsValidMeter = metrics.NewRegisteredMeter("chain/prefetch/txs/valid", nil)
errInsertionInterrupted = errors.New("insertion is interrupted") errInsertionInterrupted = errors.New("insertion is interrupted")
errChainStopped = errors.New("blockchain is stopped") errChainStopped = errors.New("blockchain is stopped")
@ -180,8 +183,13 @@ func (c *CacheConfig) triedbConfig(isVerkle bool) *triedb.Config {
} }
if c.StateScheme == rawdb.PathScheme { if c.StateScheme == rawdb.PathScheme {
config.PathDB = &pathdb.Config{ config.PathDB = &pathdb.Config{
StateHistory: c.StateHistory, StateHistory: c.StateHistory,
CleanCacheSize: c.TrieCleanLimit * 1024 * 1024, TrieCleanSize: c.TrieCleanLimit * 1024 * 1024,
StateCleanSize: c.SnapshotLimit * 1024 * 1024,
// TODO(rjl493456442): The write buffer represents the memory limit used
// for flushing both trie data and state data to disk. The config name
// should be updated to eliminate the confusion.
WriteBufferSize: c.TrieDirtyLimit * 1024 * 1024, WriteBufferSize: c.TrieDirtyLimit * 1024 * 1024,
} }
} }
@ -377,11 +385,14 @@ func NewBlockChain(db ethdb.Database, cacheConfig *CacheConfig, genesis *Genesis
// Do nothing here until the state syncer picks it up. // Do nothing here until the state syncer picks it up.
log.Info("Genesis state is missing, wait state sync") log.Info("Genesis state is missing, wait state sync")
} else { } else {
// Head state is missing, before the state recovery, find out the // Head state is missing, before the state recovery, find out the disk
// disk layer point of snapshot(if it's enabled). Make sure the // layer point of snapshot(if it's enabled). Make sure the rewound point
// rewound point is lower than disk layer. // is lower than disk layer.
//
// Note it's unnecessary in path mode which always keep trie data and
// state data consistent.
var diskRoot common.Hash var diskRoot common.Hash
if bc.cacheConfig.SnapshotLimit > 0 { if bc.cacheConfig.SnapshotLimit > 0 && bc.cacheConfig.StateScheme == rawdb.HashScheme {
diskRoot = rawdb.ReadSnapshotRoot(bc.db) diskRoot = rawdb.ReadSnapshotRoot(bc.db)
} }
if diskRoot != (common.Hash{}) { if diskRoot != (common.Hash{}) {
@ -454,7 +465,32 @@ func NewBlockChain(db ethdb.Database, cacheConfig *CacheConfig, genesis *Genesis
bc.logger.OnGenesisBlock(bc.genesisBlock, alloc) bc.logger.OnGenesisBlock(bc.genesisBlock, alloc)
} }
} }
bc.setupSnapshot()
// Rewind the chain in case of an incompatible config upgrade.
if compatErr != nil {
log.Warn("Rewinding chain to upgrade configuration", "err", compatErr)
if compatErr.RewindToTime > 0 {
bc.SetHeadWithTimestamp(compatErr.RewindToTime)
} else {
bc.SetHead(compatErr.RewindToBlock)
}
rawdb.WriteChainConfig(db, genesisHash, chainConfig)
}
// Start tx indexer if it's enabled.
if txLookupLimit != nil {
bc.txIndexer = newTxIndexer(*txLookupLimit, bc)
}
return bc, nil
}
func (bc *BlockChain) setupSnapshot() {
// Short circuit if the chain is established with path scheme, as the
// state snapshot has been integrated into path database natively.
if bc.cacheConfig.StateScheme == rawdb.PathScheme {
return
}
// Load any existing snapshot, regenerating it if loading failed // Load any existing snapshot, regenerating it if loading failed
if bc.cacheConfig.SnapshotLimit > 0 { if bc.cacheConfig.SnapshotLimit > 0 {
// If the chain was rewound past the snapshot persistent layer (causing // If the chain was rewound past the snapshot persistent layer (causing
@ -462,7 +498,6 @@ func NewBlockChain(db ethdb.Database, cacheConfig *CacheConfig, genesis *Genesis
// in recovery mode and in that case, don't invalidate the snapshot on a // in recovery mode and in that case, don't invalidate the snapshot on a
// head mismatch. // head mismatch.
var recover bool var recover bool
head := bc.CurrentBlock() head := bc.CurrentBlock()
if layer := rawdb.ReadSnapshotRecoveryNumber(bc.db); layer != nil && *layer >= head.Number.Uint64() { if layer := rawdb.ReadSnapshotRecoveryNumber(bc.db); layer != nil && *layer >= head.Number.Uint64() {
log.Warn("Enabling snapshot recovery", "chainhead", head.Number, "diskbase", *layer) log.Warn("Enabling snapshot recovery", "chainhead", head.Number, "diskbase", *layer)
@ -479,22 +514,6 @@ func NewBlockChain(db ethdb.Database, cacheConfig *CacheConfig, genesis *Genesis
// Re-initialize the state database with snapshot // Re-initialize the state database with snapshot
bc.statedb = state.NewDatabase(bc.triedb, bc.snaps) bc.statedb = state.NewDatabase(bc.triedb, bc.snaps)
} }
// Rewind the chain in case of an incompatible config upgrade.
if compatErr != nil {
log.Warn("Rewinding chain to upgrade configuration", "err", compatErr)
if compatErr.RewindToTime > 0 {
bc.SetHeadWithTimestamp(compatErr.RewindToTime)
} else {
bc.SetHead(compatErr.RewindToBlock)
}
rawdb.WriteChainConfig(db, genesisHash, chainConfig)
}
// Start tx indexer if it's enabled.
if txLookupLimit != nil {
bc.txIndexer = newTxIndexer(*txLookupLimit, bc)
}
return bc, nil
} }
// empty returns an indicator whether the blockchain is empty. // empty returns an indicator whether the blockchain is empty.
@ -979,17 +998,16 @@ func (bc *BlockChain) setHeadBeyondRoot(head uint64, time uint64, root common.Ha
// Ignore the error here since light client won't hit this path // Ignore the error here since light client won't hit this path
frozen, _ := bc.db.Ancients() frozen, _ := bc.db.Ancients()
if num+1 <= frozen { if num+1 <= frozen {
// Truncate all relative data(header, total difficulty, body, receipt // The chain segment, such as the block header, canonical hash,
// and canonical hash) from ancient store. // body, and receipt, will be removed from the ancient store
if _, err := bc.db.TruncateHead(num); err != nil { // in one go.
log.Crit("Failed to truncate ancient data", "number", num, "err", err) //
} // The hash-to-number mapping in the key-value store will be
// Remove the hash <-> number mapping from the active store. // removed by the hc.SetHead function.
rawdb.DeleteHeaderNumber(db, hash)
} else { } else {
// Remove relative body and receipts from the active store. // Remove the associated body and receipts from the key-value store.
// The header, total difficulty and canonical hash will be // The header, hash-to-number mapping, and canonical hash will be
// removed in the hc.SetHead function. // removed by the hc.SetHead function.
rawdb.DeleteBody(db, hash, num) rawdb.DeleteBody(db, hash, num)
rawdb.DeleteReceipts(db, hash, num) rawdb.DeleteReceipts(db, hash, num)
} }
@ -1291,12 +1309,11 @@ const (
// //
// The optional ancientLimit can also be specified and chain segment before that // The optional ancientLimit can also be specified and chain segment before that
// will be directly stored in the ancient, getting rid of the chain migration. // will be directly stored in the ancient, getting rid of the chain migration.
func (bc *BlockChain) InsertReceiptChain(blockChain types.Blocks, receiptChain []types.Receipts, ancientLimit uint64) (int, error) { func (bc *BlockChain) InsertReceiptChain(blockChain types.Blocks, receiptChain []rlp.RawValue, ancientLimit uint64) (int, error) {
// Verify the supplied headers before insertion without lock // Verify the supplied headers before insertion without lock
var headers []*types.Header var headers []*types.Header
for _, block := range blockChain { for _, block := range blockChain {
headers = append(headers, block.Header()) headers = append(headers, block.Header())
// Here we also validate that blob transactions in the block do not // Here we also validate that blob transactions in the block do not
// contain a sidecar. While the sidecar does not affect the block hash // contain a sidecar. While the sidecar does not affect the block hash
// or tx hash, sending blobs within a block is not allowed. // or tx hash, sending blobs within a block is not allowed.
@ -1339,11 +1356,11 @@ func (bc *BlockChain) InsertReceiptChain(blockChain types.Blocks, receiptChain [
// //
// this function only accepts canonical chain data. All side chain will be reverted // this function only accepts canonical chain data. All side chain will be reverted
// eventually. // eventually.
writeAncient := func(blockChain types.Blocks, receiptChain []types.Receipts) (int, error) { writeAncient := func(blockChain types.Blocks, receiptChain []rlp.RawValue) (int, error) {
// Ensure genesis is in the ancient store // Ensure genesis is in the ancient store
if blockChain[0].NumberU64() == 1 { if blockChain[0].NumberU64() == 1 {
if frozen, _ := bc.db.Ancients(); frozen == 0 { if frozen, _ := bc.db.Ancients(); frozen == 0 {
writeSize, err := rawdb.WriteAncientBlocks(bc.db, []*types.Block{bc.genesisBlock}, []types.Receipts{nil}) writeSize, err := rawdb.WriteAncientBlocks(bc.db, []*types.Block{bc.genesisBlock}, []rlp.RawValue{rlp.EmptyList})
if err != nil { if err != nil {
log.Error("Error writing genesis to ancients", "err", err) log.Error("Error writing genesis to ancients", "err", err)
return 0, err return 0, err
@ -1361,7 +1378,7 @@ func (bc *BlockChain) InsertReceiptChain(blockChain types.Blocks, receiptChain [
size += writeSize size += writeSize
// Sync the ancient store explicitly to ensure all data has been flushed to disk. // Sync the ancient store explicitly to ensure all data has been flushed to disk.
if err := bc.db.Sync(); err != nil { if err := bc.db.SyncAncient(); err != nil {
return 0, err return 0, err
} }
// Write hash to number mappings // Write hash to number mappings
@ -1386,7 +1403,7 @@ func (bc *BlockChain) InsertReceiptChain(blockChain types.Blocks, receiptChain [
// existing local chain segments (reorg around the chain tip). The reorganized part // existing local chain segments (reorg around the chain tip). The reorganized part
// will be included in the provided chain segment, and stale canonical markers will be // will be included in the provided chain segment, and stale canonical markers will be
// silently rewritten. Therefore, no explicit reorg logic is needed. // silently rewritten. Therefore, no explicit reorg logic is needed.
writeLive := func(blockChain types.Blocks, receiptChain []types.Receipts) (int, error) { writeLive := func(blockChain types.Blocks, receiptChain []rlp.RawValue) (int, error) {
var ( var (
skipPresenceCheck = false skipPresenceCheck = false
batch = bc.db.NewBatch() batch = bc.db.NewBatch()
@ -1411,7 +1428,7 @@ func (bc *BlockChain) InsertReceiptChain(blockChain types.Blocks, receiptChain [
// Write all the data out into the database // Write all the data out into the database
rawdb.WriteCanonicalHash(batch, block.Hash(), block.NumberU64()) rawdb.WriteCanonicalHash(batch, block.Hash(), block.NumberU64())
rawdb.WriteBlock(batch, block) rawdb.WriteBlock(batch, block)
rawdb.WriteReceipts(batch, block.Hash(), block.NumberU64(), receiptChain[i]) rawdb.WriteRawReceipts(batch, block.Hash(), block.NumberU64(), receiptChain[i])
// Write everything belongs to the blocks into the database. So that // Write everything belongs to the blocks into the database. So that
// we can ensure all components of body is completed(body, receipts) // we can ensure all components of body is completed(body, receipts)
@ -1759,18 +1776,6 @@ func (bc *BlockChain) insertChain(chain types.Blocks, setHead bool, makeWitness
bc.reportBlock(block, nil, err) bc.reportBlock(block, nil, err)
return nil, it.index, err return nil, it.index, err
} }
// No validation errors for the first block (or chain prefix skipped)
var activeState *state.StateDB
defer func() {
// The chain importer is starting and stopping trie prefetchers. If a bad
// block or other error is hit however, an early return may not properly
// terminate the background threads. This defer ensures that we clean up
// and dangling prefetcher, without deferring each and holding on live refs.
if activeState != nil {
activeState.StopPrefetcher()
}
}()
// Track the singleton witness from this chain insertion (if any) // Track the singleton witness from this chain insertion (if any)
var witness *stateless.Witness var witness *stateless.Witness
@ -1826,63 +1831,20 @@ func (bc *BlockChain) insertChain(chain types.Blocks, setHead bool, makeWitness
continue continue
} }
// Retrieve the parent block and it's state to execute on top // Retrieve the parent block and it's state to execute on top
start := time.Now()
parent := it.previous() parent := it.previous()
if parent == nil { if parent == nil {
parent = bc.GetHeader(block.ParentHash(), block.NumberU64()-1) parent = bc.GetHeader(block.ParentHash(), block.NumberU64()-1)
} }
statedb, err := state.New(parent.Root, bc.statedb)
if err != nil {
return nil, it.index, err
}
// If we are past Byzantium, enable prefetching to pull in trie node paths
// while processing transactions. Before Byzantium the prefetcher is mostly
// useless due to the intermediate root hashing after each transaction.
if bc.chainConfig.IsByzantium(block.Number()) {
// Generate witnesses either if we're self-testing, or if it's the
// only block being inserted. A bit crude, but witnesses are huge,
// so we refuse to make an entire chain of them.
if bc.vmConfig.StatelessSelfValidation || (makeWitness && len(chain) == 1) {
witness, err = stateless.NewWitness(block.Header(), bc)
if err != nil {
return nil, it.index, err
}
}
statedb.StartPrefetcher("chain", witness)
}
activeState = statedb
// If we have a followup block, run that against the current state to pre-cache
// transactions and probabilistically some of the account/storage trie nodes.
var followupInterrupt atomic.Bool
if !bc.cacheConfig.TrieCleanNoPrefetch {
if followup, err := it.peek(); followup != nil && err == nil {
throwaway, _ := state.New(parent.Root, bc.statedb)
go func(start time.Time, followup *types.Block, throwaway *state.StateDB) {
// Disable tracing for prefetcher executions.
vmCfg := bc.vmConfig
vmCfg.Tracer = nil
bc.prefetcher.Prefetch(followup, throwaway, vmCfg, &followupInterrupt)
blockPrefetchExecuteTimer.Update(time.Since(start))
if followupInterrupt.Load() {
blockPrefetchInterruptMeter.Mark(1)
}
}(time.Now(), followup, throwaway)
}
}
// The traced section of block import. // The traced section of block import.
res, err := bc.processBlock(block, statedb, start, setHead) start := time.Now()
followupInterrupt.Store(true) res, err := bc.processBlock(parent.Root, block, setHead, makeWitness && len(chain) == 1)
if err != nil { if err != nil {
return nil, it.index, err return nil, it.index, err
} }
// Report the import stats before returning the various results // Report the import stats before returning the various results
stats.processed++ stats.processed++
stats.usedGas += res.usedGas stats.usedGas += res.usedGas
witness = res.witness
var snapDiffItems, snapBufItems common.StorageSize var snapDiffItems, snapBufItems common.StorageSize
if bc.snaps != nil { if bc.snaps != nil {
@ -1938,11 +1900,74 @@ type blockProcessingResult struct {
usedGas uint64 usedGas uint64
procTime time.Duration procTime time.Duration
status WriteStatus status WriteStatus
witness *stateless.Witness
} }
// processBlock executes and validates the given block. If there was no error // processBlock executes and validates the given block. If there was no error
// it writes the block and associated state to database. // it writes the block and associated state to database.
func (bc *BlockChain) processBlock(block *types.Block, statedb *state.StateDB, start time.Time, setHead bool) (_ *blockProcessingResult, blockEndErr error) { func (bc *BlockChain) processBlock(parentRoot common.Hash, block *types.Block, setHead bool, makeWitness bool) (_ *blockProcessingResult, blockEndErr error) {
var (
err error
startTime = time.Now()
statedb *state.StateDB
interrupt atomic.Bool
)
defer interrupt.Store(true) // terminate the prefetch at the end
if bc.cacheConfig.TrieCleanNoPrefetch {
statedb, err = state.New(parentRoot, bc.statedb)
if err != nil {
return nil, err
}
} else {
// If prefetching is enabled, run that against the current state to pre-cache
// transactions and probabilistically some of the account/storage trie nodes.
//
// Note: the main processor and prefetcher share the same reader with a local
// cache for mitigating the overhead of state access.
reader, err := bc.statedb.ReaderWithCache(parentRoot)
if err != nil {
return nil, err
}
throwaway, err := state.NewWithReader(parentRoot, bc.statedb, reader)
if err != nil {
return nil, err
}
statedb, err = state.NewWithReader(parentRoot, bc.statedb, reader)
if err != nil {
return nil, err
}
go func(start time.Time, throwaway *state.StateDB, block *types.Block) {
// Disable tracing for prefetcher executions.
vmCfg := bc.vmConfig
vmCfg.Tracer = nil
bc.prefetcher.Prefetch(block, throwaway, vmCfg, &interrupt)
blockPrefetchExecuteTimer.Update(time.Since(start))
if interrupt.Load() {
blockPrefetchInterruptMeter.Mark(1)
}
}(time.Now(), throwaway, block)
}
// If we are past Byzantium, enable prefetching to pull in trie node paths
// while processing transactions. Before Byzantium the prefetcher is mostly
// useless due to the intermediate root hashing after each transaction.
var witness *stateless.Witness
if bc.chainConfig.IsByzantium(block.Number()) {
// Generate witnesses either if we're self-testing, or if it's the
// only block being inserted. A bit crude, but witnesses are huge,
// so we refuse to make an entire chain of them.
if bc.vmConfig.StatelessSelfValidation || makeWitness {
witness, err = stateless.NewWitness(block.Header(), bc)
if err != nil {
return nil, err
}
}
statedb.StartPrefetcher("chain", witness)
defer statedb.StopPrefetcher()
}
if bc.logger != nil && bc.logger.OnBlockStart != nil { if bc.logger != nil && bc.logger.OnBlockStart != nil {
bc.logger.OnBlockStart(tracing.BlockEvent{ bc.logger.OnBlockStart(tracing.BlockEvent{
Block: block, Block: block,
@ -2001,7 +2026,7 @@ func (bc *BlockChain) processBlock(block *types.Block, statedb *state.StateDB, s
} }
} }
xvtime := time.Since(xvstart) xvtime := time.Since(xvstart)
proctime := time.Since(start) // processing + validation + cross validation proctime := time.Since(startTime) // processing + validation + cross validation
// Update the metrics touched during block processing and validation // Update the metrics touched during block processing and validation
accountReadTimer.Update(statedb.AccountReads) // Account reads are complete(in processing) accountReadTimer.Update(statedb.AccountReads) // Account reads are complete(in processing)
@ -2042,9 +2067,14 @@ func (bc *BlockChain) processBlock(block *types.Block, statedb *state.StateDB, s
triedbCommitTimer.Update(statedb.TrieDBCommits) // Trie database commits are complete, we can mark them triedbCommitTimer.Update(statedb.TrieDBCommits) // Trie database commits are complete, we can mark them
blockWriteTimer.Update(time.Since(wstart) - max(statedb.AccountCommits, statedb.StorageCommits) /* concurrent */ - statedb.SnapshotCommits - statedb.TrieDBCommits) blockWriteTimer.Update(time.Since(wstart) - max(statedb.AccountCommits, statedb.StorageCommits) /* concurrent */ - statedb.SnapshotCommits - statedb.TrieDBCommits)
blockInsertTimer.UpdateSince(start) blockInsertTimer.UpdateSince(startTime)
return &blockProcessingResult{usedGas: res.GasUsed, procTime: proctime, status: status}, nil return &blockProcessingResult{
usedGas: res.GasUsed,
procTime: proctime,
status: status,
witness: witness,
}, nil
} }
// insertSideChain is called when an import batch hits upon a pruned ancestor // insertSideChain is called when an import batch hits upon a pruned ancestor
@ -2528,14 +2558,22 @@ func (bc *BlockChain) reportBlock(block *types.Block, res *ProcessResult, err er
// logForkReadiness will write a log when a future fork is scheduled, but not // logForkReadiness will write a log when a future fork is scheduled, but not
// active. This is useful so operators know their client is ready for the fork. // active. This is useful so operators know their client is ready for the fork.
func (bc *BlockChain) logForkReadiness(block *types.Block) { func (bc *BlockChain) logForkReadiness(block *types.Block) {
c := bc.Config() config := bc.Config()
current, last := c.LatestFork(block.Time()), c.LatestFork(math.MaxUint64) current, last := config.LatestFork(block.Time()), config.LatestFork(math.MaxUint64)
t := c.Timestamp(last)
if t == nil { // Short circuit if the timestamp of the last fork is undefined,
// or if the network has already passed the last configured fork.
t := config.Timestamp(last)
if t == nil || current >= last {
return return
} }
at := time.Unix(int64(*t), 0) at := time.Unix(int64(*t), 0)
if current < last && time.Now().After(bc.lastForkReadyAlert.Add(forkReadyInterval)) {
// Only log if:
// - Current time is before the fork activation time
// - Enough time has passed since last alert
now := time.Now()
if now.Before(at) && now.After(bc.lastForkReadyAlert.Add(forkReadyInterval)) {
log.Info("Ready for fork activation", "fork", last, "date", at.Format(time.RFC822), log.Info("Ready for fork activation", "fork", last, "date", at.Format(time.RFC822),
"remaining", time.Until(at).Round(time.Second), "timestamp", at.Unix()) "remaining", time.Until(at).Round(time.Second), "timestamp", at.Unix())
bc.lastForkReadyAlert = time.Now() bc.lastForkReadyAlert = time.Now()
@ -2611,7 +2649,7 @@ func (bc *BlockChain) InsertHeadersBeforeCutoff(headers []*types.Header) (int, e
first = headers[0].Number.Uint64() first = headers[0].Number.Uint64()
) )
if first == 1 && frozen == 0 { if first == 1 && frozen == 0 {
_, err := rawdb.WriteAncientBlocks(bc.db, []*types.Block{bc.genesisBlock}, []types.Receipts{nil}) _, err := rawdb.WriteAncientBlocks(bc.db, []*types.Block{bc.genesisBlock}, []rlp.RawValue{rlp.EmptyList})
if err != nil { if err != nil {
log.Error("Error writing genesis to ancients", "err", err) log.Error("Error writing genesis to ancients", "err", err)
return 0, err return 0, err
@ -2627,7 +2665,8 @@ func (bc *BlockChain) InsertHeadersBeforeCutoff(headers []*types.Header) (int, e
if err != nil { if err != nil {
return 0, err return 0, err
} }
if err := bc.db.Sync(); err != nil { // Sync the ancient store explicitly to ensure all data has been flushed to disk.
if err := bc.db.SyncAncient(); err != nil {
return 0, err return 0, err
} }
// Write hash to number mappings // Write hash to number mappings

View file

@ -43,8 +43,12 @@ func (st *insertStats) report(chain []*types.Block, index int, snapDiffItems, sn
// Fetch the timings for the batch // Fetch the timings for the batch
var ( var (
now = mclock.Now() now = mclock.Now()
elapsed = now.Sub(st.startTime) elapsed = now.Sub(st.startTime) + 1 // prevent zero division
mgasps = float64(st.usedGas) * 1000 / float64(elapsed)
) )
// Update the Mgas per second gauge
chainMgaspsGauge.Update(int64(mgasps))
// If we're at the last block of the batch or report period reached, log // If we're at the last block of the batch or report period reached, log
if index == len(chain)-1 || elapsed >= statsReportLimit { if index == len(chain)-1 || elapsed >= statsReportLimit {
// Count the number of transactions in this segment // Count the number of transactions in this segment
@ -58,7 +62,7 @@ func (st *insertStats) report(chain []*types.Block, index int, snapDiffItems, sn
context := []interface{}{ context := []interface{}{
"number", end.Number(), "hash", end.Hash(), "number", end.Number(), "hash", end.Hash(),
"blocks", st.processed, "txs", txs, "mgas", float64(st.usedGas) / 1000000, "blocks", st.processed, "txs", txs, "mgas", float64(st.usedGas) / 1000000,
"elapsed", common.PrettyDuration(elapsed), "mgasps", float64(st.usedGas) * 1000 / float64(elapsed), "elapsed", common.PrettyDuration(elapsed), "mgasps", mgasps,
} }
if timestamp := time.Unix(int64(end.Time()), 0); time.Since(timestamp) > time.Minute { if timestamp := time.Unix(int64(end.Time()), 0); time.Since(timestamp) > time.Minute {
context = append(context, []interface{}{"age", common.PrettyAge(timestamp)}...) context = append(context, []interface{}{"age", common.PrettyAge(timestamp)}...)
@ -138,6 +142,7 @@ func (it *insertIterator) next() (*types.Block, error) {
// //
// Both header and body validation errors (nil too) is cached into the iterator // Both header and body validation errors (nil too) is cached into the iterator
// to avoid duplicating work on the following next() call. // to avoid duplicating work on the following next() call.
// nolint:unused
func (it *insertIterator) peek() (*types.Block, error) { func (it *insertIterator) peek() (*types.Block, error) {
// If we reached the end of the chain, abort // If we reached the end of the chain, abort
if it.index+1 >= len(it.chain) { if it.index+1 >= len(it.chain) {

View file

@ -234,6 +234,24 @@ func (bc *BlockChain) GetReceiptsByHash(hash common.Hash) types.Receipts {
return receipts return receipts
} }
// GetRawReceipts retrieves the receipts for all transactions in a given block
// without deriving the internal fields and the Bloom.
func (bc *BlockChain) GetRawReceipts(hash common.Hash, number uint64) types.Receipts {
if receipts, ok := bc.receiptsCache.Get(hash); ok {
return receipts
}
return rawdb.ReadRawReceipts(bc.db, hash, number)
}
// GetReceiptsRLP retrieves the receipts of a block.
func (bc *BlockChain) GetReceiptsRLP(hash common.Hash) rlp.RawValue {
number := rawdb.ReadHeaderNumber(bc.db, hash)
if number == nil {
return nil
}
return rawdb.ReadReceiptsRLP(bc.db, hash, *number)
}
// GetUnclesInChain retrieves all the uncles from a given block backwards until // GetUnclesInChain retrieves all the uncles from a given block backwards until
// a specific distance is reached. // a specific distance is reached.
func (bc *BlockChain) GetUnclesInChain(block *types.Block, length int) []*types.Header { func (bc *BlockChain) GetUnclesInChain(block *types.Block, length int) []*types.Header {
@ -262,42 +280,20 @@ func (bc *BlockChain) GetAncestor(hash common.Hash, number, ancestor uint64, max
// GetTransactionLookup retrieves the lookup along with the transaction // GetTransactionLookup retrieves the lookup along with the transaction
// itself associate with the given transaction hash. // itself associate with the given transaction hash.
// //
// An error will be returned if the transaction is not found, and background // A null will be returned if the transaction is not found. This can be due to
// indexing for transactions is still in progress. The transaction might be // the transaction indexer not being finished. The caller must explicitly check
// reachable shortly once it's indexed. // the indexer progress.
// func (bc *BlockChain) GetTransactionLookup(hash common.Hash) (*rawdb.LegacyTxLookupEntry, *types.Transaction) {
// A null will be returned in the transaction is not found and background
// transaction indexing is already finished. The transaction is not existent
// from the node's perspective.
func (bc *BlockChain) GetTransactionLookup(hash common.Hash) (*rawdb.LegacyTxLookupEntry, *types.Transaction, error) {
bc.txLookupLock.RLock() bc.txLookupLock.RLock()
defer bc.txLookupLock.RUnlock() defer bc.txLookupLock.RUnlock()
// Short circuit if the txlookup already in the cache, retrieve otherwise // Short circuit if the txlookup already in the cache, retrieve otherwise
if item, exist := bc.txLookupCache.Get(hash); exist { if item, exist := bc.txLookupCache.Get(hash); exist {
return item.lookup, item.transaction, nil return item.lookup, item.transaction
} }
tx, blockHash, blockNumber, txIndex := rawdb.ReadTransaction(bc.db, hash) tx, blockHash, blockNumber, txIndex := rawdb.ReadTransaction(bc.db, hash)
if tx == nil { if tx == nil {
progress, err := bc.TxIndexProgress() return nil, nil
if err != nil {
// No error is returned if the transaction indexing progress is unreachable
// due to unexpected internal errors. In such cases, it is impossible to
// determine whether the transaction does not exist or has simply not been
// indexed yet without a progress marker.
//
// In such scenarios, the transaction is treated as unreachable, though
// this is clearly an unintended and unexpected situation.
return nil, nil, nil
}
// The transaction indexing is not finished yet, returning an
// error to explicitly indicate it.
if !progress.Done() {
return nil, nil, errors.New("transaction indexing still in progress")
}
// The transaction is already indexed, the transaction is either
// not existent or not in the range of index, returning null.
return nil, nil, nil
} }
lookup := &rawdb.LegacyTxLookupEntry{ lookup := &rawdb.LegacyTxLookupEntry{
BlockHash: blockHash, BlockHash: blockHash,
@ -308,7 +304,23 @@ func (bc *BlockChain) GetTransactionLookup(hash common.Hash) (*rawdb.LegacyTxLoo
lookup: lookup, lookup: lookup,
transaction: tx, transaction: tx,
}) })
return lookup, tx, nil return lookup, tx
}
// TxIndexDone returns true if the transaction indexer has finished indexing.
func (bc *BlockChain) TxIndexDone() bool {
progress, err := bc.TxIndexProgress()
if err != nil {
// No error is returned if the transaction indexing progress is unreachable
// due to unexpected internal errors. In such cases, it is impossible to
// determine whether the transaction does not exist or has simply not been
// indexed yet without a progress marker.
//
// In such scenarios, the transaction is treated as unreachable, though
// this is clearly an unintended and unexpected situation.
return true
}
return progress.Done()
} }
// HasState checks if state trie is fully present in the database or not. // HasState checks if state trie is fully present in the database or not.
@ -404,7 +416,7 @@ func (bc *BlockChain) TxIndexProgress() (TxIndexProgress, error) {
if bc.txIndexer == nil { if bc.txIndexer == nil {
return TxIndexProgress{}, errors.New("tx indexer is not enabled") return TxIndexProgress{}, errors.New("tx indexer is not enabled")
} }
return bc.txIndexer.txIndexProgress() return bc.txIndexer.txIndexProgress(), nil
} }
// HistoryPruningCutoff returns the configured history pruning point. // HistoryPruningCutoff returns the configured history pruning point.

View file

@ -1765,7 +1765,7 @@ func testRepairWithScheme(t *testing.T, tt *rewindTest, snapshots bool, scheme s
datadir := t.TempDir() datadir := t.TempDir()
ancient := filepath.Join(datadir, "ancient") ancient := filepath.Join(datadir, "ancient")
pdb, err := pebble.New(datadir, 0, 0, "", false, true) pdb, err := pebble.New(datadir, 0, 0, "", false)
if err != nil { if err != nil {
t.Fatalf("Failed to create persistent key-value database: %v", err) t.Fatalf("Failed to create persistent key-value database: %v", err)
} }
@ -1791,7 +1791,7 @@ func testRepairWithScheme(t *testing.T, tt *rewindTest, snapshots bool, scheme s
} }
) )
defer engine.Close() defer engine.Close()
if snapshots { if snapshots && scheme == rawdb.HashScheme {
config.SnapshotLimit = 256 config.SnapshotLimit = 256
config.SnapshotWait = true config.SnapshotWait = true
} }
@ -1820,7 +1820,7 @@ func testRepairWithScheme(t *testing.T, tt *rewindTest, snapshots bool, scheme s
if err := chain.triedb.Commit(canonblocks[tt.commitBlock-1].Root(), false); err != nil { if err := chain.triedb.Commit(canonblocks[tt.commitBlock-1].Root(), false); err != nil {
t.Fatalf("Failed to flush trie state: %v", err) t.Fatalf("Failed to flush trie state: %v", err)
} }
if snapshots { if snapshots && scheme == rawdb.HashScheme {
if err := chain.snaps.Cap(canonblocks[tt.commitBlock-1].Root(), 0); err != nil { if err := chain.snaps.Cap(canonblocks[tt.commitBlock-1].Root(), 0); err != nil {
t.Fatalf("Failed to flatten snapshots: %v", err) t.Fatalf("Failed to flatten snapshots: %v", err)
} }
@ -1850,7 +1850,7 @@ func testRepairWithScheme(t *testing.T, tt *rewindTest, snapshots bool, scheme s
chain.stopWithoutSaving() chain.stopWithoutSaving()
// Start a new blockchain back up and see where the repair leads us // Start a new blockchain back up and see where the repair leads us
pdb, err = pebble.New(datadir, 0, 0, "", false, true) pdb, err = pebble.New(datadir, 0, 0, "", false)
if err != nil { if err != nil {
t.Fatalf("Failed to reopen persistent key-value database: %v", err) t.Fatalf("Failed to reopen persistent key-value database: %v", err)
} }
@ -1915,7 +1915,7 @@ func testIssue23496(t *testing.T, scheme string) {
datadir := t.TempDir() datadir := t.TempDir()
ancient := filepath.Join(datadir, "ancient") ancient := filepath.Join(datadir, "ancient")
pdb, err := pebble.New(datadir, 0, 0, "", false, true) pdb, err := pebble.New(datadir, 0, 0, "", false)
if err != nil { if err != nil {
t.Fatalf("Failed to create persistent key-value database: %v", err) t.Fatalf("Failed to create persistent key-value database: %v", err)
} }
@ -1952,8 +1952,10 @@ func testIssue23496(t *testing.T, scheme string) {
if _, err := chain.InsertChain(blocks[1:2]); err != nil { if _, err := chain.InsertChain(blocks[1:2]); err != nil {
t.Fatalf("Failed to import canonical chain start: %v", err) t.Fatalf("Failed to import canonical chain start: %v", err)
} }
if err := chain.snaps.Cap(blocks[1].Root(), 0); err != nil { if scheme == rawdb.HashScheme {
t.Fatalf("Failed to flatten snapshots: %v", err) if err := chain.snaps.Cap(blocks[1].Root(), 0); err != nil {
t.Fatalf("Failed to flatten snapshots: %v", err)
}
} }
// Insert block B3 and commit the state into disk // Insert block B3 and commit the state into disk
@ -1973,7 +1975,7 @@ func testIssue23496(t *testing.T, scheme string) {
chain.stopWithoutSaving() chain.stopWithoutSaving()
// Start a new blockchain back up and see where the repair leads us // Start a new blockchain back up and see where the repair leads us
pdb, err = pebble.New(datadir, 0, 0, "", false, true) pdb, err = pebble.New(datadir, 0, 0, "", false)
if err != nil { if err != nil {
t.Fatalf("Failed to reopen persistent key-value database: %v", err) t.Fatalf("Failed to reopen persistent key-value database: %v", err)
} }
@ -1997,15 +1999,23 @@ func testIssue23496(t *testing.T, scheme string) {
} }
expHead := uint64(1) expHead := uint64(1)
if scheme == rawdb.PathScheme { if scheme == rawdb.PathScheme {
expHead = uint64(2) // The pathdb database makes sure that snapshot and trie are consistent,
// so only the last block is reverted in case of a crash.
expHead = uint64(3)
} }
if head := chain.CurrentBlock(); head.Number.Uint64() != expHead { if head := chain.CurrentBlock(); head.Number.Uint64() != expHead {
t.Errorf("Head block mismatch: have %d, want %d", head.Number, expHead) t.Errorf("Head block mismatch: have %d, want %d", head.Number, expHead)
} }
if scheme == rawdb.PathScheme {
// Reinsert B2-B4 // Reinsert B4
if _, err := chain.InsertChain(blocks[1:]); err != nil { if _, err := chain.InsertChain(blocks[3:]); err != nil {
t.Fatalf("Failed to import canonical chain tail: %v", err) t.Fatalf("Failed to import canonical chain tail: %v", err)
}
} else {
// Reinsert B2-B4
if _, err := chain.InsertChain(blocks[1:]); err != nil {
t.Fatalf("Failed to import canonical chain tail: %v", err)
}
} }
if head := chain.CurrentHeader(); head.Number.Uint64() != uint64(4) { if head := chain.CurrentHeader(); head.Number.Uint64() != uint64(4) {
t.Errorf("Head header mismatch: have %d, want %d", head.Number, 4) t.Errorf("Head header mismatch: have %d, want %d", head.Number, 4)
@ -2016,7 +2026,9 @@ func testIssue23496(t *testing.T, scheme string) {
if head := chain.CurrentBlock(); head.Number.Uint64() != uint64(4) { if head := chain.CurrentBlock(); head.Number.Uint64() != uint64(4) {
t.Errorf("Head block mismatch: have %d, want %d", head.Number, uint64(4)) t.Errorf("Head block mismatch: have %d, want %d", head.Number, uint64(4))
} }
if layer := chain.Snapshots().Snapshot(blocks[2].Root()); layer == nil { if scheme == rawdb.HashScheme {
t.Error("Failed to regenerate the snapshot of known state") if layer := chain.Snapshots().Snapshot(blocks[2].Root()); layer == nil {
t.Error("Failed to regenerate the snapshot of known state")
}
} }
} }

View file

@ -1969,7 +1969,7 @@ func testSetHeadWithScheme(t *testing.T, tt *rewindTest, snapshots bool, scheme
datadir := t.TempDir() datadir := t.TempDir()
ancient := filepath.Join(datadir, "ancient") ancient := filepath.Join(datadir, "ancient")
pdb, err := pebble.New(datadir, 0, 0, "", false, true) pdb, err := pebble.New(datadir, 0, 0, "", false)
if err != nil { if err != nil {
t.Fatalf("Failed to create persistent key-value database: %v", err) t.Fatalf("Failed to create persistent key-value database: %v", err)
} }
@ -2023,7 +2023,7 @@ func testSetHeadWithScheme(t *testing.T, tt *rewindTest, snapshots bool, scheme
} }
if tt.commitBlock > 0 { if tt.commitBlock > 0 {
chain.triedb.Commit(canonblocks[tt.commitBlock-1].Root(), false) chain.triedb.Commit(canonblocks[tt.commitBlock-1].Root(), false)
if snapshots { if snapshots && scheme == rawdb.HashScheme {
if err := chain.snaps.Cap(canonblocks[tt.commitBlock-1].Root(), 0); err != nil { if err := chain.snaps.Cap(canonblocks[tt.commitBlock-1].Root(), 0); err != nil {
t.Fatalf("Failed to flatten snapshots: %v", err) t.Fatalf("Failed to flatten snapshots: %v", err)
} }

View file

@ -66,7 +66,7 @@ func (basic *snapshotTestBasic) prepare(t *testing.T) (*BlockChain, []*types.Blo
datadir := t.TempDir() datadir := t.TempDir()
ancient := filepath.Join(datadir, "ancient") ancient := filepath.Join(datadir, "ancient")
pdb, err := pebble.New(datadir, 0, 0, "", false, true) pdb, err := pebble.New(datadir, 0, 0, "", false)
if err != nil { if err != nil {
t.Fatalf("Failed to create persistent key-value database: %v", err) t.Fatalf("Failed to create persistent key-value database: %v", err)
} }
@ -105,7 +105,7 @@ func (basic *snapshotTestBasic) prepare(t *testing.T) (*BlockChain, []*types.Blo
if basic.commitBlock > 0 && basic.commitBlock == point { if basic.commitBlock > 0 && basic.commitBlock == point {
chain.TrieDB().Commit(blocks[point-1].Root(), false) chain.TrieDB().Commit(blocks[point-1].Root(), false)
} }
if basic.snapshotBlock > 0 && basic.snapshotBlock == point { if basic.snapshotBlock > 0 && basic.snapshotBlock == point && basic.scheme == rawdb.HashScheme {
// Flushing the entire snap tree into the disk, the // Flushing the entire snap tree into the disk, the
// relevant (a) snapshot root and (b) snapshot generator // relevant (a) snapshot root and (b) snapshot generator
// will be persisted atomically. // will be persisted atomically.
@ -149,13 +149,17 @@ func (basic *snapshotTestBasic) verify(t *testing.T, chain *BlockChain, blocks [
block := chain.GetBlockByNumber(basic.expSnapshotBottom) block := chain.GetBlockByNumber(basic.expSnapshotBottom)
if block == nil { if block == nil {
t.Errorf("The corresponding block[%d] of snapshot disk layer is missing", basic.expSnapshotBottom) t.Errorf("The corresponding block[%d] of snapshot disk layer is missing", basic.expSnapshotBottom)
} else if !bytes.Equal(chain.snaps.DiskRoot().Bytes(), block.Root().Bytes()) { } else if basic.scheme == rawdb.HashScheme {
t.Errorf("The snapshot disk layer root is incorrect, want %x, get %x", block.Root(), chain.snaps.DiskRoot()) if !bytes.Equal(chain.snaps.DiskRoot().Bytes(), block.Root().Bytes()) {
t.Errorf("The snapshot disk layer root is incorrect, want %x, get %x", block.Root(), chain.snaps.DiskRoot())
}
} }
// Check the snapshot, ensure it's integrated // Check the snapshot, ensure it's integrated
if err := chain.snaps.Verify(block.Root()); err != nil { if basic.scheme == rawdb.HashScheme {
t.Errorf("The disk layer is not integrated %v", err) if err := chain.snaps.Verify(block.Root()); err != nil {
t.Errorf("The disk layer is not integrated %v", err)
}
} }
} }
@ -257,7 +261,7 @@ func (snaptest *crashSnapshotTest) test(t *testing.T) {
chain.triedb.Close() chain.triedb.Close()
// Start a new blockchain back up and see where the repair leads us // Start a new blockchain back up and see where the repair leads us
pdb, err := pebble.New(snaptest.datadir, 0, 0, "", false, true) pdb, err := pebble.New(snaptest.datadir, 0, 0, "", false)
if err != nil { if err != nil {
t.Fatalf("Failed to create persistent key-value database: %v", err) t.Fatalf("Failed to create persistent key-value database: %v", err)
} }
@ -565,12 +569,14 @@ func TestHighCommitCrashWithNewSnapshot(t *testing.T) {
// //
// Expected head header : C8 // Expected head header : C8
// Expected head fast block: C8 // Expected head fast block: C8
// Expected head block : G // Expected head block : G (Hash mode), C6 (Hash mode)
// Expected snapshot disk : C4 // Expected snapshot disk : C4 (Hash mode)
for _, scheme := range []string{rawdb.HashScheme, rawdb.PathScheme} { for _, scheme := range []string{rawdb.HashScheme, rawdb.PathScheme} {
expHead := uint64(0) expHead := uint64(0)
if scheme == rawdb.PathScheme { if scheme == rawdb.PathScheme {
expHead = uint64(4) // The pathdb database makes sure that snapshot and trie are consistent,
// so only the last two blocks are reverted in case of a crash.
expHead = uint64(6)
} }
test := &crashSnapshotTest{ test := &crashSnapshotTest{
snapshotTestBasic{ snapshotTestBasic{

View file

@ -734,7 +734,7 @@ func testFastVsFullChains(t *testing.T, scheme string) {
fast, _ := NewBlockChain(fastDb, DefaultCacheConfigWithScheme(scheme), gspec, nil, ethash.NewFaker(), vm.Config{}, nil) fast, _ := NewBlockChain(fastDb, DefaultCacheConfigWithScheme(scheme), gspec, nil, ethash.NewFaker(), vm.Config{}, nil)
defer fast.Stop() defer fast.Stop()
if n, err := fast.InsertReceiptChain(blocks, receipts, 0); err != nil { if n, err := fast.InsertReceiptChain(blocks, types.EncodeBlockReceiptLists(receipts), 0); err != nil {
t.Fatalf("failed to insert receipt %d: %v", n, err) t.Fatalf("failed to insert receipt %d: %v", n, err)
} }
// Freezer style fast import the chain. // Freezer style fast import the chain.
@ -747,7 +747,7 @@ func testFastVsFullChains(t *testing.T, scheme string) {
ancient, _ := NewBlockChain(ancientDb, DefaultCacheConfigWithScheme(scheme), gspec, nil, ethash.NewFaker(), vm.Config{}, nil) ancient, _ := NewBlockChain(ancientDb, DefaultCacheConfigWithScheme(scheme), gspec, nil, ethash.NewFaker(), vm.Config{}, nil)
defer ancient.Stop() defer ancient.Stop()
if n, err := ancient.InsertReceiptChain(blocks, receipts, uint64(len(blocks)/2)); err != nil { if n, err := ancient.InsertReceiptChain(blocks, types.EncodeBlockReceiptLists(receipts), uint64(len(blocks)/2)); err != nil {
t.Fatalf("failed to insert receipt %d: %v", n, err) t.Fatalf("failed to insert receipt %d: %v", n, err)
} }
@ -871,7 +871,7 @@ func testLightVsFastVsFullChainHeads(t *testing.T, scheme string) {
fast, _ := NewBlockChain(fastDb, DefaultCacheConfigWithScheme(scheme), gspec, nil, ethash.NewFaker(), vm.Config{}, nil) fast, _ := NewBlockChain(fastDb, DefaultCacheConfigWithScheme(scheme), gspec, nil, ethash.NewFaker(), vm.Config{}, nil)
defer fast.Stop() defer fast.Stop()
if n, err := fast.InsertReceiptChain(blocks, receipts, 0); err != nil { if n, err := fast.InsertReceiptChain(blocks, types.EncodeBlockReceiptLists(receipts), 0); err != nil {
t.Fatalf("failed to insert receipt %d: %v", n, err) t.Fatalf("failed to insert receipt %d: %v", n, err)
} }
assert(t, "fast", fast, height, height, 0) assert(t, "fast", fast, height, height, 0)
@ -884,7 +884,7 @@ func testLightVsFastVsFullChainHeads(t *testing.T, scheme string) {
ancient, _ := NewBlockChain(ancientDb, DefaultCacheConfigWithScheme(scheme), gspec, nil, ethash.NewFaker(), vm.Config{}, nil) ancient, _ := NewBlockChain(ancientDb, DefaultCacheConfigWithScheme(scheme), gspec, nil, ethash.NewFaker(), vm.Config{}, nil)
defer ancient.Stop() defer ancient.Stop()
if n, err := ancient.InsertReceiptChain(blocks, receipts, uint64(3*len(blocks)/4)); err != nil { if n, err := ancient.InsertReceiptChain(blocks, types.EncodeBlockReceiptLists(receipts), uint64(3*len(blocks)/4)); err != nil {
t.Fatalf("failed to insert receipt %d: %v", n, err) t.Fatalf("failed to insert receipt %d: %v", n, err)
} }
assert(t, "ancient", ancient, height, height, 0) assert(t, "ancient", ancient, height, height, 0)
@ -1696,7 +1696,7 @@ func testBlockchainRecovery(t *testing.T, scheme string) {
defer ancientDb.Close() defer ancientDb.Close()
ancient, _ := NewBlockChain(ancientDb, DefaultCacheConfigWithScheme(scheme), gspec, nil, ethash.NewFaker(), vm.Config{}, nil) ancient, _ := NewBlockChain(ancientDb, DefaultCacheConfigWithScheme(scheme), gspec, nil, ethash.NewFaker(), vm.Config{}, nil)
if n, err := ancient.InsertReceiptChain(blocks, receipts, uint64(3*len(blocks)/4)); err != nil { if n, err := ancient.InsertReceiptChain(blocks, types.EncodeBlockReceiptLists(receipts), uint64(3*len(blocks)/4)); err != nil {
t.Fatalf("failed to insert receipt %d: %v", n, err) t.Fatalf("failed to insert receipt %d: %v", n, err)
} }
rawdb.WriteLastPivotNumber(ancientDb, blocks[len(blocks)-1].NumberU64()) // Force fast sync behavior rawdb.WriteLastPivotNumber(ancientDb, blocks[len(blocks)-1].NumberU64()) // Force fast sync behavior
@ -1991,7 +1991,7 @@ func testInsertKnownChainData(t *testing.T, typ string, scheme string) {
} }
} else if typ == "receipts" { } else if typ == "receipts" {
inserter = func(blocks []*types.Block, receipts []types.Receipts) error { inserter = func(blocks []*types.Block, receipts []types.Receipts) error {
_, err = chain.InsertReceiptChain(blocks, receipts, 0) _, err = chain.InsertReceiptChain(blocks, types.EncodeBlockReceiptLists(receipts), 0)
return err return err
} }
asserter = func(t *testing.T, block *types.Block) { asserter = func(t *testing.T, block *types.Block) {
@ -2157,7 +2157,7 @@ func testInsertKnownChainDataWithMerging(t *testing.T, typ string, mergeHeight i
} }
} else if typ == "receipts" { } else if typ == "receipts" {
inserter = func(blocks []*types.Block, receipts []types.Receipts) error { inserter = func(blocks []*types.Block, receipts []types.Receipts) error {
_, err = chain.InsertReceiptChain(blocks, receipts, 0) _, err = chain.InsertReceiptChain(blocks, types.EncodeBlockReceiptLists(receipts), 0)
return err return err
} }
asserter = func(t *testing.T, block *types.Block) { asserter = func(t *testing.T, block *types.Block) {
@ -2492,7 +2492,7 @@ func testSideImportPrunedBlocks(t *testing.T, scheme string) {
datadir := t.TempDir() datadir := t.TempDir()
ancient := path.Join(datadir, "ancient") ancient := path.Join(datadir, "ancient")
pdb, err := pebble.New(datadir, 0, 0, "", false, true) pdb, err := pebble.New(datadir, 0, 0, "", false)
if err != nil { if err != nil {
t.Fatalf("Failed to create persistent key-value database: %v", err) t.Fatalf("Failed to create persistent key-value database: %v", err)
} }
@ -4099,7 +4099,7 @@ func TestEIP7702(t *testing.T) {
// The way the auths are combined, it becomes // The way the auths are combined, it becomes
// 1. tx -> addr1 which is delegated to 0xaaaa // 1. tx -> addr1 which is delegated to 0xaaaa
// 2. addr1:0xaaaa calls into addr2:0xbbbb // 2. addr1:0xaaaa calls into addr2:0xbbbb
// 3. addr2:0xbbbb writes to storage // 3. addr2:0xbbbb writes to storage
auth1, _ := types.SignSetCode(key1, types.SetCodeAuthorization{ auth1, _ := types.SignSetCode(key1, types.SetCodeAuthorization{
ChainID: *uint256.MustFromBig(gspec.Config.ChainID), ChainID: *uint256.MustFromBig(gspec.Config.ChainID),
Address: aa, Address: aa,
@ -4205,10 +4205,10 @@ func testChainReorgSnapSync(t *testing.T, ancientLimit uint64) {
chain, _ := NewBlockChain(db, DefaultCacheConfigWithScheme(rawdb.PathScheme), gspec, nil, beacon.New(ethash.NewFaker()), vm.Config{}, nil) chain, _ := NewBlockChain(db, DefaultCacheConfigWithScheme(rawdb.PathScheme), gspec, nil, beacon.New(ethash.NewFaker()), vm.Config{}, nil)
defer chain.Stop() defer chain.Stop()
if n, err := chain.InsertReceiptChain(blocks, receipts, ancientLimit); err != nil { if n, err := chain.InsertReceiptChain(blocks, types.EncodeBlockReceiptLists(receipts), ancientLimit); err != nil {
t.Fatalf("failed to insert receipt %d: %v", n, err) t.Fatalf("failed to insert receipt %d: %v", n, err)
} }
if n, err := chain.InsertReceiptChain(chainA, receiptsA, ancientLimit); err != nil { if n, err := chain.InsertReceiptChain(chainA, types.EncodeBlockReceiptLists(receiptsA), ancientLimit); err != nil {
t.Fatalf("failed to insert receipt %d: %v", n, err) t.Fatalf("failed to insert receipt %d: %v", n, err)
} }
// If the common ancestor is below the ancient limit, rewind the chain head. // If the common ancestor is below the ancient limit, rewind the chain head.
@ -4218,7 +4218,7 @@ func testChainReorgSnapSync(t *testing.T, ancientLimit uint64) {
rawdb.WriteLastPivotNumber(db, ancestor) rawdb.WriteLastPivotNumber(db, ancestor)
chain.SetHead(ancestor) chain.SetHead(ancestor)
} }
if n, err := chain.InsertReceiptChain(chainB, receiptsB, ancientLimit); err != nil { if n, err := chain.InsertReceiptChain(chainB, types.EncodeBlockReceiptLists(receiptsB), ancientLimit); err != nil {
t.Fatalf("failed to insert receipt %d: %v", n, err) t.Fatalf("failed to insert receipt %d: %v", n, err)
} }
head := chain.CurrentSnapBlock() head := chain.CurrentSnapBlock()
@ -4336,7 +4336,7 @@ func testInsertChainWithCutoff(t *testing.T, cutoff uint64, ancientLimit uint64,
if n, err := chain.InsertHeadersBeforeCutoff(headersBefore); err != nil { if n, err := chain.InsertHeadersBeforeCutoff(headersBefore); err != nil {
t.Fatalf("failed to insert headers before cutoff %d: %v", n, err) t.Fatalf("failed to insert headers before cutoff %d: %v", n, err)
} }
if n, err := chain.InsertReceiptChain(blocksAfter, receiptsAfter, ancientLimit); err != nil { if n, err := chain.InsertReceiptChain(blocksAfter, types.EncodeBlockReceiptLists(receiptsAfter), ancientLimit); err != nil {
t.Fatalf("failed to insert receipt %d: %v", n, err) t.Fatalf("failed to insert receipt %d: %v", n, err)
} }
headSnap := chain.CurrentSnapBlock() headSnap := chain.CurrentSnapBlock()

View file

@ -328,9 +328,13 @@ func (b *BlockGen) collectRequests(readonly bool) (requests [][]byte) {
blockContext := NewEVMBlockContext(b.header, b.cm, &b.header.Coinbase) blockContext := NewEVMBlockContext(b.header, b.cm, &b.header.Coinbase)
evm := vm.NewEVM(blockContext, statedb, b.cm.config, vm.Config{}) evm := vm.NewEVM(blockContext, statedb, b.cm.config, vm.Config{})
// EIP-7002 // EIP-7002
ProcessWithdrawalQueue(&requests, evm) if err := ProcessWithdrawalQueue(&requests, evm); err != nil {
panic(fmt.Sprintf("could not process withdrawal requests: %v", err))
}
// EIP-7251 // EIP-7251
ProcessConsolidationQueue(&requests, evm) if err := ProcessConsolidationQueue(&requests, evm); err != nil {
panic(fmt.Sprintf("could not process consolidation requests: %v", err))
}
} }
return requests return requests
} }

View file

@ -29,6 +29,7 @@ type blockchain interface {
GetHeader(hash common.Hash, number uint64) *types.Header GetHeader(hash common.Hash, number uint64) *types.Header
GetCanonicalHash(number uint64) common.Hash GetCanonicalHash(number uint64) common.Hash
GetReceiptsByHash(hash common.Hash) types.Receipts GetReceiptsByHash(hash common.Hash) types.Receipts
GetRawReceipts(hash common.Hash, number uint64) types.Receipts
} }
// ChainView represents an immutable view of a chain with a block id and a set // ChainView represents an immutable view of a chain with a block id and a set
@ -58,47 +59,80 @@ func NewChainView(chain blockchain, number uint64, hash common.Hash) *ChainView
return cv return cv
} }
// getBlockHash returns the block hash belonging to the given block number. // HeadNumber returns the head block number of the chain view.
func (cv *ChainView) HeadNumber() uint64 {
return cv.headNumber
}
// BlockHash returns the block hash belonging to the given block number.
// Note that the hash of the head block is not returned because ChainView might // Note that the hash of the head block is not returned because ChainView might
// represent a view where the head block is currently being created. // represent a view where the head block is currently being created.
func (cv *ChainView) getBlockHash(number uint64) common.Hash { func (cv *ChainView) BlockHash(number uint64) common.Hash {
if number >= cv.headNumber { cv.lock.Lock()
defer cv.lock.Unlock()
if number > cv.headNumber {
panic("invalid block number") panic("invalid block number")
} }
return cv.blockHash(number) return cv.blockHash(number)
} }
// getBlockId returns the unique block id belonging to the given block number. // BlockId returns the unique block id belonging to the given block number.
// Note that it is currently equal to the block hash. In the future it might // Note that it is currently equal to the block hash. In the future it might
// be a different id for future blocks if the log index root becomes part of // be a different id for future blocks if the log index root becomes part of
// consensus and therefore rendering the index with the new head will happen // consensus and therefore rendering the index with the new head will happen
// before the hash of that new head is available. // before the hash of that new head is available.
func (cv *ChainView) getBlockId(number uint64) common.Hash { func (cv *ChainView) BlockId(number uint64) common.Hash {
cv.lock.Lock()
defer cv.lock.Unlock()
if number > cv.headNumber { if number > cv.headNumber {
panic("invalid block number") panic("invalid block number")
} }
return cv.blockHash(number) return cv.blockHash(number)
} }
// getReceipts returns the set of receipts belonging to the block at the given // Header returns the block header at the given block number.
func (cv *ChainView) Header(number uint64) *types.Header {
return cv.chain.GetHeader(cv.BlockHash(number), number)
}
// Receipts returns the set of receipts belonging to the block at the given
// block number. // block number.
func (cv *ChainView) getReceipts(number uint64) types.Receipts { func (cv *ChainView) Receipts(number uint64) types.Receipts {
if number > cv.headNumber { blockHash := cv.BlockHash(number)
panic("invalid block number")
}
blockHash := cv.blockHash(number)
if blockHash == (common.Hash{}) { if blockHash == (common.Hash{}) {
log.Error("Chain view: block hash unavailable", "number", number, "head", cv.headNumber) log.Error("Chain view: block hash unavailable", "number", number, "head", cv.headNumber)
return nil
} }
return cv.chain.GetReceiptsByHash(blockHash) return cv.chain.GetReceiptsByHash(blockHash)
} }
// limitedView returns a new chain view that is a truncated version of the parent view. // RawReceipts returns the set of receipts belonging to the block at the given
func (cv *ChainView) limitedView(newHead uint64) *ChainView { // block number. Does not derive the fields of the receipts, should only be
if newHead >= cv.headNumber { // used during creation of the filter maps, please use cv.Receipts during querying.
return cv func (cv *ChainView) RawReceipts(number uint64) types.Receipts {
blockHash := cv.BlockHash(number)
if blockHash == (common.Hash{}) {
log.Error("Chain view: block hash unavailable", "number", number, "head", cv.headNumber)
return nil
} }
return NewChainView(cv.chain, newHead, cv.blockHash(newHead)) return cv.chain.GetRawReceipts(blockHash, number)
}
// SharedRange returns the block range shared by two chain views.
func (cv *ChainView) SharedRange(cv2 *ChainView) common.Range[uint64] {
cv.lock.Lock()
defer cv.lock.Unlock()
if cv == nil || cv2 == nil || !cv.extendNonCanonical() || !cv2.extendNonCanonical() {
return common.Range[uint64]{}
}
var sharedLen uint64
for n := min(cv.headNumber+1-uint64(len(cv.hashes)), cv2.headNumber+1-uint64(len(cv2.hashes))); n <= cv.headNumber && n <= cv2.headNumber && cv.blockHash(n) == cv2.blockHash(n); n++ {
sharedLen = n + 1
}
return common.NewRange(0, sharedLen)
} }
// equalViews returns true if the two chain views are equivalent. // equalViews returns true if the two chain views are equivalent.
@ -106,7 +140,7 @@ func equalViews(cv1, cv2 *ChainView) bool {
if cv1 == nil || cv2 == nil { if cv1 == nil || cv2 == nil {
return false return false
} }
return cv1.headNumber == cv2.headNumber && cv1.getBlockId(cv1.headNumber) == cv2.getBlockId(cv2.headNumber) return cv1.headNumber == cv2.headNumber && cv1.BlockId(cv1.headNumber) == cv2.BlockId(cv2.headNumber)
} }
// matchViews returns true if the two chain views are equivalent up until the // matchViews returns true if the two chain views are equivalent up until the
@ -120,9 +154,9 @@ func matchViews(cv1, cv2 *ChainView, number uint64) bool {
return false return false
} }
if number == cv1.headNumber || number == cv2.headNumber { if number == cv1.headNumber || number == cv2.headNumber {
return cv1.getBlockId(number) == cv2.getBlockId(number) return cv1.BlockId(number) == cv2.BlockId(number)
} }
return cv1.getBlockHash(number) == cv2.getBlockHash(number) return cv1.BlockHash(number) == cv2.BlockHash(number)
} }
// extendNonCanonical checks whether the previously known reverse list of head // extendNonCanonical checks whether the previously known reverse list of head
@ -150,9 +184,6 @@ func (cv *ChainView) extendNonCanonical() bool {
// blockHash returns the given block hash without doing the head number check. // blockHash returns the given block hash without doing the head number check.
func (cv *ChainView) blockHash(number uint64) common.Hash { func (cv *ChainView) blockHash(number uint64) common.Hash {
cv.lock.Lock()
defer cv.lock.Unlock()
if number+uint64(len(cv.hashes)) <= cv.headNumber { if number+uint64(len(cv.hashes)) <= cv.headNumber {
hash := cv.chain.GetCanonicalHash(number) hash := cv.chain.GetCanonicalHash(number)
if !cv.extendNonCanonical() { if !cv.extendNonCanonical() {

View file

@ -17,5 +17,6 @@
{"blockNumber": 3101669, "blockId": "0xa6e66a18fed64d33178c7088f273c404be597662c34f6e6884cf2e24f3ea4ace", "firstIndex": 1073741353}, {"blockNumber": 3101669, "blockId": "0xa6e66a18fed64d33178c7088f273c404be597662c34f6e6884cf2e24f3ea4ace", "firstIndex": 1073741353},
{"blockNumber": 3221725, "blockId": "0xe771f897dece48b1583cc1d1d10de8015da57407eb1fdf239fdbe46eaab85143", "firstIndex": 1140850137}, {"blockNumber": 3221725, "blockId": "0xe771f897dece48b1583cc1d1d10de8015da57407eb1fdf239fdbe46eaab85143", "firstIndex": 1140850137},
{"blockNumber": 3357164, "blockId": "0x6252d0aa54c79623b0680069c88d7b5c47983f0d5c4845b6c811b8d9b5e8ff3c", "firstIndex": 1207959453}, {"blockNumber": 3357164, "blockId": "0x6252d0aa54c79623b0680069c88d7b5c47983f0d5c4845b6c811b8d9b5e8ff3c", "firstIndex": 1207959453},
{"blockNumber": 3447019, "blockId": "0xeb7d585e1e063f3cc05ed399fbf6c2df63c271f62f030acb804e9fb1e74b6dc1", "firstIndex": 1275067542} {"blockNumber": 3447019, "blockId": "0xeb7d585e1e063f3cc05ed399fbf6c2df63c271f62f030acb804e9fb1e74b6dc1", "firstIndex": 1275067542},
{"blockNumber": 3546397, "blockId": "0xdabdef7defa4281180a57c5af121877b82274f15ccf074ea0096146f4c246df2", "firstIndex": 1342176778}
] ]

View file

@ -260,5 +260,12 @@
{"blockNumber": 21959188, "blockId": "0x6b9c482cc4822af2ad62a359b923062556ab6a046d1a39a8243b764848690601", "firstIndex": 17381193886}, {"blockNumber": 21959188, "blockId": "0x6b9c482cc4822af2ad62a359b923062556ab6a046d1a39a8243b764848690601", "firstIndex": 17381193886},
{"blockNumber": 21994911, "blockId": "0x0d99ef9dd42dd1c62fc5249eb4f618e7672ab93fa0ba7545c77360371cf972e5", "firstIndex": 17448302795}, {"blockNumber": 21994911, "blockId": "0x0d99ef9dd42dd1c62fc5249eb4f618e7672ab93fa0ba7545c77360371cf972e5", "firstIndex": 17448302795},
{"blockNumber": 22026007, "blockId": "0xf7cdc7f6694f2c2ed31fa8a607f65cfa59d0dd7d7424ab5c017f959ae2982c71", "firstIndex": 17515413036}, {"blockNumber": 22026007, "blockId": "0xf7cdc7f6694f2c2ed31fa8a607f65cfa59d0dd7d7424ab5c017f959ae2982c71", "firstIndex": 17515413036},
{"blockNumber": 22059890, "blockId": "0x82b768a0dddefda2eefd3a33596ea2be075312f1dd4b01f6b0d377faca2af98b", "firstIndex": 17582521768} {"blockNumber": 22059890, "blockId": "0x82b768a0dddefda2eefd3a33596ea2be075312f1dd4b01f6b0d377faca2af98b", "firstIndex": 17582521768},
{"blockNumber": 22090784, "blockId": "0xf97c2eaf9a550360ac24000c0ff17ffa388a2bdd6f73f2f36718e332edfa107a", "firstIndex": 17649630983},
{"blockNumber": 22121157, "blockId": "0xa790025235db782e899f23d8b09663ec2d74ec149e4125d62989f98829b08e2d", "firstIndex": 17716724973},
{"blockNumber": 22148056, "blockId": "0xbe25ac4f1bdd89a7db5782bf1157e6c4378d80220d67a029397853ef16cd1a4c", "firstIndex": 17783838366},
{"blockNumber": 22168652, "blockId": "0x6ae43618c915e636794e2cc2d75dde9992766881c7405fe6479c045ed4bee57e", "firstIndex": 17850956277},
{"blockNumber": 22190975, "blockId": "0x9437121647899a4b7b84d67fbea7cc6ff967481c2eab4328ccd86e2cefe19420", "firstIndex": 17918066140},
{"blockNumber": 22234357, "blockId": "0x036030830134f9224160d5a0b62da35ec7813dc8855d554bd22e9d38545243ed", "firstIndex": 17985175075},
{"blockNumber": 22276736, "blockId": "0x5ceb96d98aa1b4c1c2f2fa253ae9cdb1b04e0420c11bf795400e8762c0a1635c", "firstIndex": 18052284344}
] ]

View file

@ -64,5 +64,9 @@
{"blockNumber": 7730079, "blockId": "0x08e0d511a93e2a9c004b3456d77d687ae86247417ada1cdd1f85332a62dfed1d", "firstIndex": 4227846795}, {"blockNumber": 7730079, "blockId": "0x08e0d511a93e2a9c004b3456d77d687ae86247417ada1cdd1f85332a62dfed1d", "firstIndex": 4227846795},
{"blockNumber": 7777515, "blockId": "0xeba8faed2e12bb9ed5e7fad19dd82662f15f6f4f512a0d232eedf1e676712428", "firstIndex": 4294966082}, {"blockNumber": 7777515, "blockId": "0xeba8faed2e12bb9ed5e7fad19dd82662f15f6f4f512a0d232eedf1e676712428", "firstIndex": 4294966082},
{"blockNumber": 7828860, "blockId": "0x62c054bd24be351dc4777460ee922a75fdcea5c9830455cbac6fa29552719c85", "firstIndex": 4362076087}, {"blockNumber": 7828860, "blockId": "0x62c054bd24be351dc4777460ee922a75fdcea5c9830455cbac6fa29552719c85", "firstIndex": 4362076087},
{"blockNumber": 7869890, "blockId": "0x5664bcfa6151f798781e22bea4f9a2c796d097402350cb131e4e3c78e13e461a", "firstIndex": 4429183267} {"blockNumber": 7869890, "blockId": "0x5664bcfa6151f798781e22bea4f9a2c796d097402350cb131e4e3c78e13e461a", "firstIndex": 4429183267},
{"blockNumber": 7911722, "blockId": "0x9a85e48e3135c97c51fc1786f2af0596c802e021b6c53cfca65a129cafcd23ed", "firstIndex": 4496287265},
{"blockNumber": 7960147, "blockId": "0xc9359cc76d7090e1c8a031108f0ab7a8935d971efd4325fe53612a1d99562f6f", "firstIndex": 4563402388},
{"blockNumber": 8030418, "blockId": "0x21867e68cd8327aed2da2601399d60f7f9e41dca4a4f2f9be982e5a2b9304a88", "firstIndex": 4630511616},
{"blockNumber": 8087701, "blockId": "0x0fa8c8d7549cc9a8d308262706fe248efe759f8b63511efb1e7f3926e9af2dcb", "firstIndex": 4697614758}
] ]

View file

@ -50,7 +50,7 @@ var (
) )
const ( const (
databaseVersion = 1 // reindexed if database version does not match databaseVersion = 2 // reindexed if database version does not match
cachedLastBlocks = 1000 // last block of map pointers cachedLastBlocks = 1000 // last block of map pointers
cachedLvPointers = 1000 // first log value pointer of block pointers cachedLvPointers = 1000 // first log value pointer of block pointers
cachedBaseRows = 100 // groups of base layer filter row data cachedBaseRows = 100 // groups of base layer filter row data
@ -85,11 +85,17 @@ type FilterMaps struct {
// fields written by the indexer and read by matcher backend. Indexer can // fields written by the indexer and read by matcher backend. Indexer can
// read them without a lock and write them under indexLock write lock. // read them without a lock and write them under indexLock write lock.
// Matcher backend can read them under indexLock read lock. // Matcher backend can read them under indexLock read lock.
indexLock sync.RWMutex indexLock sync.RWMutex
indexedRange filterMapsRange indexedRange filterMapsRange
cleanedEpochsBefore uint32 // all unindexed data cleaned before this point indexedView *ChainView // always consistent with the log index
indexedView *ChainView // always consistent with the log index hasTempRange bool
hasTempRange bool
// cleanedEpochsBefore indicates that all unindexed data before this point
// has been cleaned.
//
// This field is only accessed and modified within tryUnindexTail, so no
// explicit locking is required.
cleanedEpochsBefore uint32
// also accessed by indexer and matcher backend but no locking needed. // also accessed by indexer and matcher backend but no locking needed.
filterMapCache *lru.Cache[uint32, filterMap] filterMapCache *lru.Cache[uint32, filterMap]
@ -128,6 +134,7 @@ type FilterMaps struct {
// test hooks // test hooks
testDisableSnapshots, testSnapshotUsed bool testDisableSnapshots, testSnapshotUsed bool
testProcessEventsHook func()
} }
// filterMap is a full or partial in-memory representation of a filter map where // filterMap is a full or partial in-memory representation of a filter map where
@ -237,6 +244,8 @@ func NewFilterMaps(db ethdb.KeyValueStore, initView *ChainView, historyCutoff, f
disabledCh: make(chan struct{}), disabledCh: make(chan struct{}),
exportFileName: config.ExportFileName, exportFileName: config.ExportFileName,
Params: params, Params: params,
targetView: initView,
indexedView: initView,
indexedRange: filterMapsRange{ indexedRange: filterMapsRange{
initialized: initialized, initialized: initialized,
headIndexed: rs.HeadIndexed, headIndexed: rs.HeadIndexed,
@ -247,26 +256,19 @@ func NewFilterMaps(db ethdb.KeyValueStore, initView *ChainView, historyCutoff, f
}, },
// deleting last unindexed epoch might have been interrupted by shutdown // deleting last unindexed epoch might have been interrupted by shutdown
cleanedEpochsBefore: max(rs.MapsFirst>>params.logMapsPerEpoch, 1) - 1, cleanedEpochsBefore: max(rs.MapsFirst>>params.logMapsPerEpoch, 1) - 1,
historyCutoff: historyCutoff,
finalBlock: finalBlock,
matcherSyncCh: make(chan *FilterMapsMatcherBackend),
matchers: make(map[*FilterMapsMatcherBackend]struct{}),
filterMapCache: lru.NewCache[uint32, filterMap](cachedFilterMaps),
lastBlockCache: lru.NewCache[uint32, lastBlockOfMap](cachedLastBlocks),
lvPointerCache: lru.NewCache[uint64, uint64](cachedLvPointers),
baseRowsCache: lru.NewCache[uint64, [][]uint32](cachedBaseRows),
renderSnapshots: lru.NewCache[uint64, *renderedMap](cachedRenderSnapshots),
}
// Set initial indexer target. historyCutoff: historyCutoff,
f.targetView = initView finalBlock: finalBlock,
if f.indexedRange.initialized { matcherSyncCh: make(chan *FilterMapsMatcherBackend),
f.indexedView = f.initChainView(f.targetView) matchers: make(map[*FilterMapsMatcherBackend]struct{}),
f.indexedRange.headIndexed = f.indexedRange.blocks.AfterLast() == f.indexedView.headNumber+1 filterMapCache: lru.NewCache[uint32, filterMap](cachedFilterMaps),
if !f.indexedRange.headIndexed { lastBlockCache: lru.NewCache[uint32, lastBlockOfMap](cachedLastBlocks),
f.indexedRange.headDelimiter = 0 lvPointerCache: lru.NewCache[uint64, uint64](cachedLvPointers),
} baseRowsCache: lru.NewCache[uint64, [][]uint32](cachedBaseRows),
renderSnapshots: lru.NewCache[uint64, *renderedMap](cachedRenderSnapshots),
} }
f.checkRevertRange() // revert maps that are inconsistent with the current chain view
if f.indexedRange.hasIndexedBlocks() { if f.indexedRange.hasIndexedBlocks() {
log.Info("Initialized log indexer", log.Info("Initialized log indexer",
"first block", f.indexedRange.blocks.First(), "last block", f.indexedRange.blocks.Last(), "first block", f.indexedRange.blocks.First(), "last block", f.indexedRange.blocks.Last(),
@ -295,29 +297,40 @@ func (f *FilterMaps) Stop() {
f.closeWg.Wait() f.closeWg.Wait()
} }
// initChainView returns a chain view consistent with both the current target // checkRevertRange checks whether the existing index is consistent with the
// view and the current state of the log index as found in the database, based // current indexed view and reverts inconsistent maps if necessary.
// on the last block of stored maps. func (f *FilterMaps) checkRevertRange() {
// Note that the returned view might be shorter than the existing index if if f.indexedRange.maps.Count() == 0 {
// the latest maps are not consistent with targetView. return
func (f *FilterMaps) initChainView(chainView *ChainView) *ChainView { }
mapIndex := f.indexedRange.maps.AfterLast() lastMap := f.indexedRange.maps.Last()
for { lastBlockNumber, lastBlockId, err := f.getLastBlockOfMap(lastMap)
var ok bool if err != nil {
mapIndex, ok = f.lastMapBoundaryBefore(mapIndex) log.Error("Error initializing log index database; resetting log index", "error", err)
if !ok { f.reset()
break return
} }
lastBlockNumber, lastBlockId, err := f.getLastBlockOfMap(mapIndex) for lastBlockNumber > f.indexedView.HeadNumber() || f.indexedView.BlockId(lastBlockNumber) != lastBlockId {
if err != nil { // revert last map
log.Error("Could not initialize indexed chain view", "error", err) if f.indexedRange.maps.Count() == 1 {
break f.reset() // reset database if no rendered maps remained
} return
if lastBlockNumber <= chainView.headNumber && chainView.getBlockId(lastBlockNumber) == lastBlockId { }
return chainView.limitedView(lastBlockNumber) lastMap--
} newRange := f.indexedRange
newRange.maps.SetLast(lastMap)
lastBlockNumber, lastBlockId, err = f.getLastBlockOfMap(lastMap)
if err != nil {
log.Error("Error initializing log index database; resetting log index", "error", err)
f.reset()
return
}
newRange.blocks.SetAfterLast(lastBlockNumber) // lastBlockNumber is probably partially indexed
newRange.headIndexed = false
newRange.headDelimiter = 0
// only shorten range and leave map data; next head render will overwrite it
f.setRange(f.db, f.indexedView, newRange, false)
} }
return chainView.limitedView(0)
} }
// reset un-initializes the FilterMaps structure and removes all related data from // reset un-initializes the FilterMaps structure and removes all related data from
@ -370,7 +383,7 @@ func (f *FilterMaps) init() error {
for min < max { for min < max {
mid := (min + max + 1) / 2 mid := (min + max + 1) / 2
cp := checkpointList[mid-1] cp := checkpointList[mid-1]
if cp.BlockNumber <= f.targetView.headNumber && f.targetView.getBlockId(cp.BlockNumber) == cp.BlockId { if cp.BlockNumber <= f.targetView.HeadNumber() && f.targetView.BlockId(cp.BlockNumber) == cp.BlockId {
min = mid min = mid
} else { } else {
max = mid - 1 max = mid - 1
@ -443,6 +456,7 @@ func (f *FilterMaps) safeDeleteWithLogs(deleteFn func(db ethdb.KeyValueStore, ha
// setRange updates the indexed chain view and covered range and also adds the // setRange updates the indexed chain view and covered range and also adds the
// changes to the given batch. // changes to the given batch.
//
// Note that this function assumes that the index write lock is being held. // Note that this function assumes that the index write lock is being held.
func (f *FilterMaps) setRange(batch ethdb.KeyValueWriter, newView *ChainView, newRange filterMapsRange, isTempRange bool) { func (f *FilterMaps) setRange(batch ethdb.KeyValueWriter, newView *ChainView, newRange filterMapsRange, isTempRange bool) {
f.indexedView = newView f.indexedView = newView
@ -476,6 +490,7 @@ func (f *FilterMaps) setRange(batch ethdb.KeyValueWriter, newView *ChainView, ne
// Note that this function assumes that the log index structure is consistent // Note that this function assumes that the log index structure is consistent
// with the canonical chain at the point where the given log value index points. // with the canonical chain at the point where the given log value index points.
// If this is not the case then an invalid result or an error may be returned. // If this is not the case then an invalid result or an error may be returned.
//
// Note that this function assumes that the indexer read lock is being held when // Note that this function assumes that the indexer read lock is being held when
// called from outside the indexerLoop goroutine. // called from outside the indexerLoop goroutine.
func (f *FilterMaps) getLogByLvIndex(lvIndex uint64) (*types.Log, error) { func (f *FilterMaps) getLogByLvIndex(lvIndex uint64) (*types.Log, error) {
@ -512,7 +527,7 @@ func (f *FilterMaps) getLogByLvIndex(lvIndex uint64) (*types.Log, error) {
} }
} }
// get block receipts // get block receipts
receipts := f.indexedView.getReceipts(firstBlockNumber) receipts := f.indexedView.Receipts(firstBlockNumber)
if receipts == nil { if receipts == nil {
return nil, fmt.Errorf("failed to retrieve receipts for block %d containing searched log value index %d: %v", firstBlockNumber, lvIndex, err) return nil, fmt.Errorf("failed to retrieve receipts for block %d containing searched log value index %d: %v", firstBlockNumber, lvIndex, err)
} }
@ -573,7 +588,7 @@ func (f *FilterMaps) getFilterMapRow(mapIndex, rowIndex uint32, baseLayerOnly bo
} }
f.baseRowsCache.Add(baseMapRowIndex, baseRows) f.baseRowsCache.Add(baseMapRowIndex, baseRows)
} }
baseRow := baseRows[mapIndex&(f.baseRowGroupLength-1)] baseRow := slices.Clone(baseRows[mapIndex&(f.baseRowGroupLength-1)])
if baseLayerOnly { if baseLayerOnly {
return baseRow, nil return baseRow, nil
} }
@ -610,7 +625,9 @@ func (f *FilterMaps) storeFilterMapRowsOfGroup(batch ethdb.Batch, mapIndices []u
if uint32(len(mapIndices)) != f.baseRowGroupLength { // skip base rows read if all rows are replaced if uint32(len(mapIndices)) != f.baseRowGroupLength { // skip base rows read if all rows are replaced
var ok bool var ok bool
baseRows, ok = f.baseRowsCache.Get(baseMapRowIndex) baseRows, ok = f.baseRowsCache.Get(baseMapRowIndex)
if !ok { if ok {
baseRows = slices.Clone(baseRows)
} else {
var err error var err error
baseRows, err = rawdb.ReadFilterMapBaseRows(f.db, baseMapRowIndex, f.baseRowGroupLength, f.logMapWidth) baseRows, err = rawdb.ReadFilterMapBaseRows(f.db, baseMapRowIndex, f.baseRowGroupLength, f.logMapWidth)
if err != nil { if err != nil {
@ -650,14 +667,11 @@ func (f *FilterMaps) mapRowIndex(mapIndex, rowIndex uint32) uint64 {
} }
// getBlockLvPointer returns the starting log value index where the log values // getBlockLvPointer returns the starting log value index where the log values
// generated by the given block are located. If blockNumber is beyond the current // generated by the given block are located.
// head then the first unoccupied log value index is returned. //
// Note that this function assumes that the indexer read lock is being held when // Note that this function assumes that the indexer read lock is being held when
// called from outside the indexerLoop goroutine. // called from outside the indexerLoop goroutine.
func (f *FilterMaps) getBlockLvPointer(blockNumber uint64) (uint64, error) { func (f *FilterMaps) getBlockLvPointer(blockNumber uint64) (uint64, error) {
if blockNumber >= f.indexedRange.blocks.AfterLast() && f.indexedRange.headIndexed {
return f.indexedRange.headDelimiter, nil
}
if lvPointer, ok := f.lvPointerCache.Get(blockNumber); ok { if lvPointer, ok := f.lvPointerCache.Get(blockNumber); ok {
return lvPointer, nil return lvPointer, nil
} }
@ -759,7 +773,7 @@ func (f *FilterMaps) deleteTailEpoch(epoch uint32) (bool, error) {
return false, errors.New("invalid tail epoch number") return false, errors.New("invalid tail epoch number")
} }
// remove index data // remove index data
if err := f.safeDeleteWithLogs(func(db ethdb.KeyValueStore, hashScheme bool, stopCb func(bool) bool) error { deleteFn := func(db ethdb.KeyValueStore, hashScheme bool, stopCb func(bool) bool) error {
first := f.mapRowIndex(firstMap, 0) first := f.mapRowIndex(firstMap, 0)
count := f.mapRowIndex(firstMap+f.mapsPerEpoch, 0) - first count := f.mapRowIndex(firstMap+f.mapsPerEpoch, 0) - first
if err := rawdb.DeleteFilterMapRows(f.db, common.NewRange(first, count), hashScheme, stopCb); err != nil { if err := rawdb.DeleteFilterMapRows(f.db, common.NewRange(first, count), hashScheme, stopCb); err != nil {
@ -783,10 +797,13 @@ func (f *FilterMaps) deleteTailEpoch(epoch uint32) (bool, error) {
f.lvPointerCache.Remove(blockNumber) f.lvPointerCache.Remove(blockNumber)
} }
return nil return nil
}, fmt.Sprintf("Deleting tail epoch #%d", epoch), func() bool { }
action := fmt.Sprintf("Deleting tail epoch #%d", epoch)
stopFn := func() bool {
f.processEvents() f.processEvents()
return f.stop || !f.targetHeadIndexed() return f.stop || !f.targetHeadIndexed()
}); err == nil { }
if err := f.safeDeleteWithLogs(deleteFn, action, stopFn); err == nil {
// everything removed; mark as cleaned and report success // everything removed; mark as cleaned and report success
if f.cleanedEpochsBefore == epoch { if f.cleanedEpochsBefore == epoch {
f.cleanedEpochsBefore = epoch + 1 f.cleanedEpochsBefore = epoch + 1
@ -805,6 +822,9 @@ func (f *FilterMaps) deleteTailEpoch(epoch uint32) (bool, error) {
} }
// exportCheckpoints exports epoch checkpoints in the format used by checkpoints.go. // exportCheckpoints exports epoch checkpoints in the format used by checkpoints.go.
//
// Note: acquiring the indexLock read lock is unnecessary here, as this function
// is always called within the indexLoop.
func (f *FilterMaps) exportCheckpoints() { func (f *FilterMaps) exportCheckpoints() {
finalLvPtr, err := f.getBlockLvPointer(f.finalBlock + 1) finalLvPtr, err := f.getBlockLvPointer(f.finalBlock + 1)
if err != nil { if err != nil {

View file

@ -43,8 +43,10 @@ func (f *FilterMaps) indexerLoop() {
log.Info("Started log indexer") log.Info("Started log indexer")
for !f.stop { for !f.stop {
// Note: acquiring the indexLock read lock is unnecessary here,
// as the `indexedRange` is accessed within the indexerLoop.
if !f.indexedRange.initialized { if !f.indexedRange.initialized {
if f.targetView.headNumber == 0 { if f.targetView.HeadNumber() == 0 {
// initialize when chain head is available // initialize when chain head is available
f.processSingleEvent(true) f.processSingleEvent(true)
continue continue
@ -105,7 +107,7 @@ type targetUpdate struct {
historyCutoff, finalBlock uint64 historyCutoff, finalBlock uint64
} }
// SetTargetView sets a new target chain view for the indexer to render. // SetTarget sets a new target chain view for the indexer to render.
// Note that SetTargetView never blocks. // Note that SetTargetView never blocks.
func (f *FilterMaps) SetTarget(targetView *ChainView, historyCutoff, finalBlock uint64) { func (f *FilterMaps) SetTarget(targetView *ChainView, historyCutoff, finalBlock uint64) {
if targetView == nil { if targetView == nil {
@ -165,6 +167,9 @@ func (f *FilterMaps) waitForNewHead() {
// processEvents processes all events, blocking only if a block processing is // processEvents processes all events, blocking only if a block processing is
// happening and indexing should be suspended. // happening and indexing should be suspended.
func (f *FilterMaps) processEvents() { func (f *FilterMaps) processEvents() {
if f.testProcessEventsHook != nil {
f.testProcessEventsHook()
}
for f.processSingleEvent(f.blockProcessing) { for f.processSingleEvent(f.blockProcessing) {
} }
} }
@ -175,6 +180,8 @@ func (f *FilterMaps) processSingleEvent(blocking bool) bool {
if f.stop { if f.stop {
return false return false
} }
// Note: acquiring the indexLock read lock is unnecessary here,
// as this function is always called within the indexLoop.
if !f.hasTempRange { if !f.hasTempRange {
for _, mb := range f.matcherSyncRequests { for _, mb := range f.matcherSyncRequests {
mb.synced() mb.synced()
@ -247,9 +254,9 @@ func (f *FilterMaps) tryIndexHead() error {
((!f.loggedHeadIndex && time.Since(f.startedHeadIndexAt) > headLogDelay) || ((!f.loggedHeadIndex && time.Since(f.startedHeadIndexAt) > headLogDelay) ||
time.Since(f.lastLogHeadIndex) > logFrequency) { time.Since(f.lastLogHeadIndex) > logFrequency) {
log.Info("Log index head rendering in progress", log.Info("Log index head rendering in progress",
"first block", f.indexedRange.blocks.First(), "last block", f.indexedRange.blocks.Last(), "firstblock", f.indexedRange.blocks.First(), "lastblock", f.indexedRange.blocks.Last(),
"processed", f.indexedRange.blocks.AfterLast()-f.ptrHeadIndex, "processed", f.indexedRange.blocks.AfterLast()-f.ptrHeadIndex,
"remaining", f.indexedView.headNumber-f.indexedRange.blocks.Last(), "remaining", f.indexedView.HeadNumber()-f.indexedRange.blocks.Last(),
"elapsed", common.PrettyDuration(time.Since(f.startedHeadIndexAt))) "elapsed", common.PrettyDuration(time.Since(f.startedHeadIndexAt)))
f.loggedHeadIndex = true f.loggedHeadIndex = true
f.lastLogHeadIndex = time.Now() f.lastLogHeadIndex = time.Now()
@ -259,7 +266,7 @@ func (f *FilterMaps) tryIndexHead() error {
} }
if f.loggedHeadIndex && f.indexedRange.hasIndexedBlocks() { if f.loggedHeadIndex && f.indexedRange.hasIndexedBlocks() {
log.Info("Log index head rendering finished", log.Info("Log index head rendering finished",
"first block", f.indexedRange.blocks.First(), "last block", f.indexedRange.blocks.Last(), "firstblock", f.indexedRange.blocks.First(), "lastblock", f.indexedRange.blocks.Last(),
"processed", f.indexedRange.blocks.AfterLast()-f.ptrHeadIndex, "processed", f.indexedRange.blocks.AfterLast()-f.ptrHeadIndex,
"elapsed", common.PrettyDuration(time.Since(f.startedHeadIndexAt))) "elapsed", common.PrettyDuration(time.Since(f.startedHeadIndexAt)))
} }
@ -316,7 +323,7 @@ func (f *FilterMaps) tryIndexTail() (bool, error) {
if f.indexedRange.hasIndexedBlocks() && f.ptrTailIndex >= f.indexedRange.blocks.First() && if f.indexedRange.hasIndexedBlocks() && f.ptrTailIndex >= f.indexedRange.blocks.First() &&
(!f.loggedTailIndex || time.Since(f.lastLogTailIndex) > logFrequency) { (!f.loggedTailIndex || time.Since(f.lastLogTailIndex) > logFrequency) {
log.Info("Log index tail rendering in progress", log.Info("Log index tail rendering in progress",
"first block", f.indexedRange.blocks.First(), "last block", f.indexedRange.blocks.Last(), "firstblock", f.indexedRange.blocks.First(), "last block", f.indexedRange.blocks.Last(),
"processed", f.ptrTailIndex-f.indexedRange.blocks.First()+tpb, "processed", f.ptrTailIndex-f.indexedRange.blocks.First()+tpb,
"remaining", remaining, "remaining", remaining,
"next tail epoch percentage", f.indexedRange.tailPartialEpoch*100/f.mapsPerEpoch, "next tail epoch percentage", f.indexedRange.tailPartialEpoch*100/f.mapsPerEpoch,
@ -339,7 +346,7 @@ func (f *FilterMaps) tryIndexTail() (bool, error) {
} }
if f.loggedTailIndex && f.indexedRange.hasIndexedBlocks() { if f.loggedTailIndex && f.indexedRange.hasIndexedBlocks() {
log.Info("Log index tail rendering finished", log.Info("Log index tail rendering finished",
"first block", f.indexedRange.blocks.First(), "last block", f.indexedRange.blocks.Last(), "firstblock", f.indexedRange.blocks.First(), "lastblock", f.indexedRange.blocks.Last(),
"processed", f.ptrTailIndex-f.indexedRange.blocks.First(), "processed", f.ptrTailIndex-f.indexedRange.blocks.First(),
"elapsed", common.PrettyDuration(time.Since(f.startedTailIndexAt))) "elapsed", common.PrettyDuration(time.Since(f.startedTailIndexAt)))
f.loggedTailIndex = false f.loggedTailIndex = false
@ -418,10 +425,10 @@ func (f *FilterMaps) needTailEpoch(epoch uint32) bool {
// tailTargetBlock returns the target value for the tail block number according // tailTargetBlock returns the target value for the tail block number according
// to the log history parameter and the current index head. // to the log history parameter and the current index head.
func (f *FilterMaps) tailTargetBlock() uint64 { func (f *FilterMaps) tailTargetBlock() uint64 {
if f.history == 0 || f.indexedView.headNumber < f.history { if f.history == 0 || f.indexedView.HeadNumber() < f.history {
return 0 return 0
} }
return f.indexedView.headNumber + 1 - f.history return f.indexedView.HeadNumber() + 1 - f.history
} }
// tailPartialBlocks returns the number of rendered blocks in the partially // tailPartialBlocks returns the number of rendered blocks in the partially

View file

@ -219,6 +219,58 @@ func testIndexerMatcherView(t *testing.T, concurrentRead bool) {
} }
} }
func TestLogsByIndex(t *testing.T) {
ts := newTestSetup(t)
defer func() {
ts.fm.testProcessEventsHook = nil
ts.close()
}()
ts.chain.addBlocks(1000, 10, 3, 4, true)
ts.setHistory(0, false)
ts.fm.WaitIdle()
firstLog := make([]uint64, 1001) // first valid log position per block
lastLog := make([]uint64, 1001) // last valid log position per block
for i := uint64(0); i <= ts.fm.indexedRange.headDelimiter; i++ {
log, err := ts.fm.getLogByLvIndex(i)
if err != nil {
t.Fatalf("Error getting log by index %d: %v", i, err)
}
if log != nil {
if firstLog[log.BlockNumber] == 0 {
firstLog[log.BlockNumber] = i
}
lastLog[log.BlockNumber] = i
}
}
var failed bool
ts.fm.testProcessEventsHook = func() {
if ts.fm.indexedRange.blocks.IsEmpty() {
return
}
if lvi := firstLog[ts.fm.indexedRange.blocks.First()]; lvi != 0 {
log, err := ts.fm.getLogByLvIndex(lvi)
if log == nil || err != nil {
t.Errorf("Error getting first log of indexed block range: %v", err)
failed = true
}
}
if lvi := lastLog[ts.fm.indexedRange.blocks.Last()]; lvi != 0 {
log, err := ts.fm.getLogByLvIndex(lvi)
if log == nil || err != nil {
t.Errorf("Error getting last log of indexed block range: %v", err)
failed = true
}
}
}
chain := ts.chain.getCanonicalChain()
for i := 0; i < 1000 && !failed; i++ {
head := rand.Intn(len(chain))
ts.chain.setCanonicalChain(chain[:head+1])
ts.fm.WaitIdle()
}
}
func TestIndexerCompareDb(t *testing.T) { func TestIndexerCompareDb(t *testing.T) {
ts := newTestSetup(t) ts := newTestSetup(t)
defer ts.close() defer ts.close()
@ -463,6 +515,13 @@ func (tc *testChain) GetReceiptsByHash(hash common.Hash) types.Receipts {
return tc.receipts[hash] return tc.receipts[hash]
} }
func (tc *testChain) GetRawReceipts(hash common.Hash, number uint64) types.Receipts {
tc.lock.RLock()
defer tc.lock.RUnlock()
return tc.receipts[hash]
}
func (tc *testChain) addBlocks(count, maxTxPerBlock, maxLogsPerReceipt, maxTopicsPerLog int, random bool) { func (tc *testChain) addBlocks(count, maxTxPerBlock, maxLogsPerReceipt, maxTopicsPerLog int, random bool) {
tc.lock.Lock() tc.lock.Lock()
blockGen := func(i int, gen *core.BlockGen) { blockGen := func(i int, gen *core.BlockGen) {

View file

@ -20,6 +20,7 @@ import (
"errors" "errors"
"fmt" "fmt"
"math" "math"
"slices"
"sort" "sort"
"time" "time"
@ -107,7 +108,7 @@ func (f *FilterMaps) renderMapsFromSnapshot(cp *renderedMap) (*mapRenderer, erro
filterMap: cp.filterMap.fullCopy(), filterMap: cp.filterMap.fullCopy(),
mapIndex: cp.mapIndex, mapIndex: cp.mapIndex,
lastBlock: cp.lastBlock, lastBlock: cp.lastBlock,
blockLvPtrs: cp.blockLvPtrs, blockLvPtrs: slices.Clone(cp.blockLvPtrs),
}, },
finishedMaps: make(map[uint32]*renderedMap), finishedMaps: make(map[uint32]*renderedMap),
finished: common.NewRange(cp.mapIndex, 0), finished: common.NewRange(cp.mapIndex, 0),
@ -143,8 +144,8 @@ func (f *FilterMaps) lastCanonicalSnapshotOfMap(mapIndex uint32) *renderedMap {
var best *renderedMap var best *renderedMap
for _, blockNumber := range f.renderSnapshots.Keys() { for _, blockNumber := range f.renderSnapshots.Keys() {
if cp, _ := f.renderSnapshots.Get(blockNumber); cp != nil && blockNumber < f.indexedRange.blocks.AfterLast() && if cp, _ := f.renderSnapshots.Get(blockNumber); cp != nil && blockNumber < f.indexedRange.blocks.AfterLast() &&
blockNumber <= f.indexedView.headNumber && f.indexedView.getBlockId(blockNumber) == cp.lastBlockId && blockNumber <= f.indexedView.HeadNumber() && f.indexedView.BlockId(blockNumber) == cp.lastBlockId &&
blockNumber <= f.targetView.headNumber && f.targetView.getBlockId(blockNumber) == cp.lastBlockId && blockNumber <= f.targetView.HeadNumber() && f.targetView.BlockId(blockNumber) == cp.lastBlockId &&
cp.mapIndex == mapIndex && (best == nil || blockNumber > best.lastBlock) { cp.mapIndex == mapIndex && (best == nil || blockNumber > best.lastBlock) {
best = cp best = cp
} }
@ -173,7 +174,7 @@ func (f *FilterMaps) lastCanonicalMapBoundaryBefore(renderBefore uint32) (nextMa
return 0, 0, 0, fmt.Errorf("failed to retrieve last block of reverse iterated map %d: %v", mapIndex, err) return 0, 0, 0, fmt.Errorf("failed to retrieve last block of reverse iterated map %d: %v", mapIndex, err)
} }
if (f.indexedRange.headIndexed && mapIndex >= f.indexedRange.maps.Last()) || if (f.indexedRange.headIndexed && mapIndex >= f.indexedRange.maps.Last()) ||
lastBlock >= f.targetView.headNumber || lastBlockId != f.targetView.getBlockId(lastBlock) { lastBlock >= f.targetView.HeadNumber() || lastBlockId != f.targetView.BlockId(lastBlock) {
continue // map is not full or inconsistent with targetView; roll back continue // map is not full or inconsistent with targetView; roll back
} }
lvPtr, err := f.getBlockLvPointer(lastBlock) lvPtr, err := f.getBlockLvPointer(lastBlock)
@ -244,10 +245,10 @@ func (f *FilterMaps) loadHeadSnapshot() error {
} }
} }
f.renderSnapshots.Add(f.indexedRange.blocks.Last(), &renderedMap{ f.renderSnapshots.Add(f.indexedRange.blocks.Last(), &renderedMap{
filterMap: fm, filterMap: fm.fullCopy(),
mapIndex: f.indexedRange.maps.Last(), mapIndex: f.indexedRange.maps.Last(),
lastBlock: f.indexedRange.blocks.Last(), lastBlock: f.indexedRange.blocks.Last(),
lastBlockId: f.indexedView.getBlockId(f.indexedRange.blocks.Last()), lastBlockId: f.indexedView.BlockId(f.indexedRange.blocks.Last()),
blockLvPtrs: lvPtrs, blockLvPtrs: lvPtrs,
finished: true, finished: true,
headDelimiter: f.indexedRange.headDelimiter, headDelimiter: f.indexedRange.headDelimiter,
@ -264,7 +265,7 @@ func (r *mapRenderer) makeSnapshot() {
filterMap: r.currentMap.filterMap.fastCopy(), filterMap: r.currentMap.filterMap.fastCopy(),
mapIndex: r.currentMap.mapIndex, mapIndex: r.currentMap.mapIndex,
lastBlock: r.currentMap.lastBlock, lastBlock: r.currentMap.lastBlock,
lastBlockId: r.iterator.chainView.getBlockId(r.currentMap.lastBlock), lastBlockId: r.iterator.chainView.BlockId(r.currentMap.lastBlock),
blockLvPtrs: r.currentMap.blockLvPtrs, blockLvPtrs: r.currentMap.blockLvPtrs,
finished: true, finished: true,
headDelimiter: r.iterator.lvIndex, headDelimiter: r.iterator.lvIndex,
@ -370,7 +371,7 @@ func (r *mapRenderer) renderCurrentMap(stopCb func() bool) (bool, error) {
r.currentMap.finished = true r.currentMap.finished = true
r.currentMap.headDelimiter = r.iterator.lvIndex r.currentMap.headDelimiter = r.iterator.lvIndex
} }
r.currentMap.lastBlockId = r.f.targetView.getBlockId(r.currentMap.lastBlock) r.currentMap.lastBlockId = r.f.targetView.BlockId(r.currentMap.lastBlock)
totalTime += time.Since(start) totalTime += time.Since(start)
mapRenderTimer.Update(totalTime) mapRenderTimer.Update(totalTime)
mapLogValueMeter.Mark(logValuesProcessed) mapLogValueMeter.Mark(logValuesProcessed)
@ -467,15 +468,25 @@ func (r *mapRenderer) writeFinishedMaps(pauseCb func() bool) error {
r.f.filterMapCache.Remove(mapIndex) r.f.filterMapCache.Remove(mapIndex)
} }
} }
var blockNumber uint64
if r.finished.First() > 0 {
// in order to always ensure continuous block pointers, initialize
// blockNumber based on the last block of the previous map, then verify
// against the first block associated with each rendered map
lastBlock, _, err := r.f.getLastBlockOfMap(r.finished.First() - 1)
if err != nil {
return fmt.Errorf("failed to get last block of previous map %d: %v", r.finished.First()-1, err)
}
blockNumber = lastBlock + 1
}
// add or update block pointers // add or update block pointers
blockNumber := r.finishedMaps[r.finished.First()].firstBlock()
for mapIndex := range r.finished.Iter() { for mapIndex := range r.finished.Iter() {
renderedMap := r.finishedMaps[mapIndex] renderedMap := r.finishedMaps[mapIndex]
if blockNumber != renderedMap.firstBlock() {
return fmt.Errorf("non-continuous block numbers in rendered map %d (next expected: %d first rendered: %d)", mapIndex, blockNumber, renderedMap.firstBlock())
}
r.f.storeLastBlockOfMap(batch, mapIndex, renderedMap.lastBlock, renderedMap.lastBlockId) r.f.storeLastBlockOfMap(batch, mapIndex, renderedMap.lastBlock, renderedMap.lastBlockId)
checkWriteCnt() checkWriteCnt()
if blockNumber != renderedMap.firstBlock() {
panic("non-continuous block numbers")
}
for _, lvPtr := range renderedMap.blockLvPtrs { for _, lvPtr := range renderedMap.blockLvPtrs {
r.f.storeBlockLvPointer(batch, blockNumber, lvPtr) r.f.storeBlockLvPointer(batch, blockNumber, lvPtr)
checkWriteCnt() checkWriteCnt()
@ -536,6 +547,7 @@ func (r *mapRenderer) getTempRange() (filterMapsRange, error) {
} else { } else {
tempRange.blocks.SetAfterLast(0) tempRange.blocks.SetAfterLast(0)
} }
tempRange.headIndexed = false
tempRange.headDelimiter = 0 tempRange.headDelimiter = 0
} }
return tempRange, nil return tempRange, nil
@ -566,8 +578,8 @@ func (r *mapRenderer) getUpdatedRange() (filterMapsRange, error) {
lm := r.finishedMaps[r.finished.Last()] lm := r.finishedMaps[r.finished.Last()]
newRange.headIndexed = lm.finished newRange.headIndexed = lm.finished
if lm.finished { if lm.finished {
newRange.blocks.SetLast(r.f.targetView.headNumber) newRange.blocks.SetLast(r.f.targetView.HeadNumber())
if lm.lastBlock != r.f.targetView.headNumber { if lm.lastBlock != r.f.targetView.HeadNumber() {
panic("map rendering finished but last block != head block") panic("map rendering finished but last block != head block")
} }
newRange.headDelimiter = lm.headDelimiter newRange.headDelimiter = lm.headDelimiter
@ -665,13 +677,13 @@ var errUnindexedRange = errors.New("unindexed range")
// given block's first log value entry (the block delimiter), according to the // given block's first log value entry (the block delimiter), according to the
// current targetView. // current targetView.
func (f *FilterMaps) newLogIteratorFromBlockDelimiter(blockNumber, lvIndex uint64) (*logIterator, error) { func (f *FilterMaps) newLogIteratorFromBlockDelimiter(blockNumber, lvIndex uint64) (*logIterator, error) {
if blockNumber > f.targetView.headNumber { if blockNumber > f.targetView.HeadNumber() {
return nil, fmt.Errorf("iterator entry point %d after target chain head block %d", blockNumber, f.targetView.headNumber) return nil, fmt.Errorf("iterator entry point %d after target chain head block %d", blockNumber, f.targetView.HeadNumber())
} }
if !f.indexedRange.blocks.Includes(blockNumber) { if !f.indexedRange.blocks.Includes(blockNumber) {
return nil, errUnindexedRange return nil, errUnindexedRange
} }
finished := blockNumber == f.targetView.headNumber finished := blockNumber == f.targetView.HeadNumber()
l := &logIterator{ l := &logIterator{
chainView: f.targetView, chainView: f.targetView,
params: &f.Params, params: &f.Params,
@ -687,11 +699,11 @@ func (f *FilterMaps) newLogIteratorFromBlockDelimiter(blockNumber, lvIndex uint6
// newLogIteratorFromMapBoundary creates a logIterator starting at the given // newLogIteratorFromMapBoundary creates a logIterator starting at the given
// map boundary, according to the current targetView. // map boundary, according to the current targetView.
func (f *FilterMaps) newLogIteratorFromMapBoundary(mapIndex uint32, startBlock, startLvPtr uint64) (*logIterator, error) { func (f *FilterMaps) newLogIteratorFromMapBoundary(mapIndex uint32, startBlock, startLvPtr uint64) (*logIterator, error) {
if startBlock > f.targetView.headNumber { if startBlock > f.targetView.HeadNumber() {
return nil, fmt.Errorf("iterator entry point %d after target chain head block %d", startBlock, f.targetView.headNumber) return nil, fmt.Errorf("iterator entry point %d after target chain head block %d", startBlock, f.targetView.HeadNumber())
} }
// get block receipts // get block receipts
receipts := f.targetView.getReceipts(startBlock) receipts := f.targetView.RawReceipts(startBlock)
if receipts == nil { if receipts == nil {
return nil, fmt.Errorf("receipts not found for start block %d", startBlock) return nil, fmt.Errorf("receipts not found for start block %d", startBlock)
} }
@ -758,7 +770,7 @@ func (l *logIterator) next() error {
if l.delimiter { if l.delimiter {
l.delimiter = false l.delimiter = false
l.blockNumber++ l.blockNumber++
l.receipts = l.chainView.getReceipts(l.blockNumber) l.receipts = l.chainView.RawReceipts(l.blockNumber)
if l.receipts == nil { if l.receipts == nil {
return fmt.Errorf("receipts not found for block %d", l.blockNumber) return fmt.Errorf("receipts not found for block %d", l.blockNumber)
} }
@ -795,7 +807,7 @@ func (l *logIterator) enforceValidState() {
} }
l.logIndex = 0 l.logIndex = 0
} }
if l.blockNumber == l.chainView.headNumber { if l.blockNumber == l.chainView.HeadNumber() {
l.finished = true l.finished = true
} else { } else {
l.delimiter = true l.delimiter = true

View file

@ -57,7 +57,7 @@ type MatcherBackend interface {
// all states of the chain since the previous SyncLogIndex or the creation of // all states of the chain since the previous SyncLogIndex or the creation of
// the matcher backend. // the matcher backend.
type SyncRange struct { type SyncRange struct {
HeadNumber uint64 IndexedView *ChainView
// block range where the index has not changed since the last matcher sync // block range where the index has not changed since the last matcher sync
// and therefore the set of matches found in this region is guaranteed to // and therefore the set of matches found in this region is guaranteed to
// be valid and complete. // be valid and complete.

View file

@ -82,13 +82,26 @@ func (fm *FilterMapsMatcherBackend) GetFilterMapRow(ctx context.Context, mapInde
} }
// GetBlockLvPointer returns the starting log value index where the log values // GetBlockLvPointer returns the starting log value index where the log values
// generated by the given block are located. If blockNumber is beyond the current // generated by the given block are located. If blockNumber is beyond the last
// head then the first unoccupied log value index is returned. // indexed block then the pointer will point right after this block, ensuring
// that the matcher does not fail and can return a set of results where the
// valid range is correct.
// GetBlockLvPointer implements MatcherBackend. // GetBlockLvPointer implements MatcherBackend.
func (fm *FilterMapsMatcherBackend) GetBlockLvPointer(ctx context.Context, blockNumber uint64) (uint64, error) { func (fm *FilterMapsMatcherBackend) GetBlockLvPointer(ctx context.Context, blockNumber uint64) (uint64, error) {
fm.f.indexLock.RLock() fm.f.indexLock.RLock()
defer fm.f.indexLock.RUnlock() defer fm.f.indexLock.RUnlock()
if blockNumber >= fm.f.indexedRange.blocks.AfterLast() {
if fm.f.indexedRange.headIndexed {
// return index after head block
return fm.f.indexedRange.headDelimiter + 1, nil
}
if fm.f.indexedRange.blocks.Count() > 0 {
// return index at the beginning of the last, partially indexed
// block (after the last fully indexed one)
blockNumber = fm.f.indexedRange.blocks.Last()
}
}
return fm.f.getBlockLvPointer(blockNumber) return fm.f.getBlockLvPointer(blockNumber)
} }
@ -111,24 +124,24 @@ func (fm *FilterMapsMatcherBackend) GetLogByLvIndex(ctx context.Context, lvIndex
// synced signals to the matcher that has triggered a synchronisation that it // synced signals to the matcher that has triggered a synchronisation that it
// has been finished and the log index is consistent with the chain head passed // has been finished and the log index is consistent with the chain head passed
// as a parameter. // as a parameter.
//
// Note that if the log index head was far behind the chain head then it might not // Note that if the log index head was far behind the chain head then it might not
// be synced up to the given head in a single step. Still, the latest chain head // be synced up to the given head in a single step. Still, the latest chain head
// should be passed as a parameter and the existing log index should be consistent // should be passed as a parameter and the existing log index should be consistent
// with that chain. // with that chain.
//
// Note: acquiring the indexLock read lock is unnecessary here, as this function
// is always called within the indexLoop.
func (fm *FilterMapsMatcherBackend) synced() { func (fm *FilterMapsMatcherBackend) synced() {
fm.f.indexLock.RLock()
fm.f.matchersLock.Lock() fm.f.matchersLock.Lock()
defer func() { defer fm.f.matchersLock.Unlock()
fm.f.matchersLock.Unlock()
fm.f.indexLock.RUnlock()
}()
indexedBlocks := fm.f.indexedRange.blocks indexedBlocks := fm.f.indexedRange.blocks
if !fm.f.indexedRange.headIndexed && !indexedBlocks.IsEmpty() { if !fm.f.indexedRange.headIndexed && !indexedBlocks.IsEmpty() {
indexedBlocks.SetAfterLast(indexedBlocks.Last()) // remove partially indexed last block indexedBlocks.SetAfterLast(indexedBlocks.Last()) // remove partially indexed last block
} }
fm.syncCh <- SyncRange{ fm.syncCh <- SyncRange{
HeadNumber: fm.f.targetView.headNumber, IndexedView: fm.f.indexedView,
ValidBlocks: fm.validBlocks, ValidBlocks: fm.validBlocks,
IndexedBlocks: indexedBlocks, IndexedBlocks: indexedBlocks,
} }
@ -154,7 +167,9 @@ func (fm *FilterMapsMatcherBackend) SyncLogIndex(ctx context.Context) (SyncRange
case <-ctx.Done(): case <-ctx.Done():
return SyncRange{}, ctx.Err() return SyncRange{}, ctx.Err()
case <-fm.f.disabledCh: case <-fm.f.disabledCh:
return SyncRange{HeadNumber: fm.f.targetView.headNumber}, nil // Note: acquiring the indexLock read lock is unnecessary here,
// as the indexer has already been terminated.
return SyncRange{IndexedView: fm.f.indexedView}, nil
} }
select { select {
case vr := <-syncCh: case vr := <-syncCh:
@ -162,7 +177,9 @@ func (fm *FilterMapsMatcherBackend) SyncLogIndex(ctx context.Context) (SyncRange
case <-ctx.Done(): case <-ctx.Done():
return SyncRange{}, ctx.Err() return SyncRange{}, ctx.Err()
case <-fm.f.disabledCh: case <-fm.f.disabledCh:
return SyncRange{HeadNumber: fm.f.targetView.headNumber}, nil // Note: acquiring the indexLock read lock is unnecessary here,
// as the indexer has already been terminated.
return SyncRange{IndexedView: fm.f.indexedView}, nil
} }
} }
@ -170,7 +187,9 @@ func (fm *FilterMapsMatcherBackend) SyncLogIndex(ctx context.Context) (SyncRange
// valid range with the current indexed range. This function should be called // valid range with the current indexed range. This function should be called
// whenever a part of the log index has been removed, before adding new blocks // whenever a part of the log index has been removed, before adding new blocks
// to it. // to it.
// Note that this function assumes that the index read lock is being held. //
// Note: acquiring the indexLock read lock is unnecessary here, as this function
// is always called within the indexLoop.
func (f *FilterMaps) updateMatchersValidRange() { func (f *FilterMaps) updateMatchersValidRange() {
f.matchersLock.Lock() f.matchersLock.Lock()
defer f.matchersLock.Unlock() defer f.matchersLock.Unlock()

View file

@ -365,6 +365,9 @@ func SetupGenesisBlockWithOverride(db ethdb.Database, triedb *triedb.Database, g
return nil, common.Hash{}, nil, errors.New("missing head header") return nil, common.Hash{}, nil, errors.New("missing head header")
} }
newCfg := genesis.chainConfigOrDefault(ghash, storedCfg) newCfg := genesis.chainConfigOrDefault(ghash, storedCfg)
if err := overrides.apply(newCfg); err != nil {
return nil, common.Hash{}, nil, err
}
// Sanity-check the new configuration. // Sanity-check the new configuration.
if err := newCfg.CheckConfigForkOrder(); err != nil { if err := newCfg.CheckConfigForkOrder(); err != nil {

View file

@ -591,17 +591,50 @@ func (hc *HeaderChain) setHead(headBlock uint64, headTime uint64, updateFn Updat
hashes = append(hashes, hdr.Hash()) hashes = append(hashes, hdr.Hash())
} }
for _, hash := range hashes { for _, hash := range hashes {
// Remove the associated block body and receipts if required.
//
// If the block is in the chain freezer, then this delete operation
// is actually ineffective.
if delFn != nil { if delFn != nil {
delFn(batch, hash, num) delFn(batch, hash, num)
} }
// Remove the hash->number mapping along with the header itself
rawdb.DeleteHeader(batch, hash, num) rawdb.DeleteHeader(batch, hash, num)
} }
// Remove the number->hash mapping
rawdb.DeleteCanonicalHash(batch, num) rawdb.DeleteCanonicalHash(batch, num)
} }
} }
// Flush all accumulated deletions. // Flush all accumulated deletions.
if err := batch.Write(); err != nil { if err := batch.Write(); err != nil {
log.Crit("Failed to rewind block", "error", err) log.Crit("Failed to commit batch in setHead", "err", err)
}
// Explicitly flush the pending writes in the key-value store to disk, ensuring
// data durability of the previous deletions.
if err := hc.chainDb.SyncKeyValue(); err != nil {
log.Crit("Failed to sync the key-value store in setHead", "err", err)
}
// Truncate the excessive chain segments in the ancient store.
// These are actually deferred deletions from the loop above.
//
// This step must be performed after synchronizing the key-value store;
// otherwise, in the event of a panic, it's theoretically possible to
// lose recent key-value store writes while the ancient store deletions
// remain, leading to data inconsistency, e.g., the gap between the key
// value store and ancient can be created due to unclean shutdown.
if delFn != nil {
// Ignore the error here since light client won't hit this path
frozen, _ := hc.chainDb.Ancients()
header := hc.CurrentHeader()
// Truncate the excessive chain segment above the current chain head
// in the ancient store.
if header.Number.Uint64()+1 < frozen {
_, err := hc.chainDb.TruncateHead(header.Number.Uint64() + 1)
if err != nil {
log.Crit("Failed to truncate head block", "err", err)
}
}
} }
// Clear out any stale content from the caches // Clear out any stale content from the caches
hc.headerCache.Purge() hc.headerCache.Purge()

View file

@ -545,8 +545,8 @@ func ReadReceiptsRLP(db ethdb.Reader, hash common.Hash, number uint64) rlp.RawVa
} }
// ReadRawReceipts retrieves all the transaction receipts belonging to a block. // ReadRawReceipts retrieves all the transaction receipts belonging to a block.
// The receipt metadata fields are not guaranteed to be populated, so they // The receipt metadata fields and the Bloom are not guaranteed to be populated,
// should not be used. Use ReadReceipts instead if the metadata is needed. // so they should not be used. Use ReadReceipts instead if the metadata is needed.
func ReadRawReceipts(db ethdb.Reader, hash common.Hash, number uint64) types.Receipts { func ReadRawReceipts(db ethdb.Reader, hash common.Hash, number uint64) types.Receipts {
// Retrieve the flattened receipt slice // Retrieve the flattened receipt slice
data := ReadReceiptsRLP(db, hash, number) data := ReadReceiptsRLP(db, hash, number)
@ -621,6 +621,14 @@ func WriteReceipts(db ethdb.KeyValueWriter, hash common.Hash, number uint64, rec
} }
} }
// WriteRawReceipts stores all the transaction receipts belonging to a block.
func WriteRawReceipts(db ethdb.KeyValueWriter, hash common.Hash, number uint64, receipts rlp.RawValue) {
// Store the flattened receipt slice
if err := db.Put(blockReceiptsKey(number, hash), receipts); err != nil {
log.Crit("Failed to store block receipts", "err", err)
}
}
// DeleteReceipts removes all receipt data associated with a block hash. // DeleteReceipts removes all receipt data associated with a block hash.
func DeleteReceipts(db ethdb.KeyValueWriter, hash common.Hash, number uint64) { func DeleteReceipts(db ethdb.KeyValueWriter, hash common.Hash, number uint64) {
if err := db.Delete(blockReceiptsKey(number, hash)); err != nil { if err := db.Delete(blockReceiptsKey(number, hash)); err != nil {
@ -701,18 +709,11 @@ func WriteBlock(db ethdb.KeyValueWriter, block *types.Block) {
} }
// WriteAncientBlocks writes entire block data into ancient store and returns the total written size. // WriteAncientBlocks writes entire block data into ancient store and returns the total written size.
func WriteAncientBlocks(db ethdb.AncientWriter, blocks []*types.Block, receipts []types.Receipts) (int64, error) { func WriteAncientBlocks(db ethdb.AncientWriter, blocks []*types.Block, receipts []rlp.RawValue) (int64, error) {
var stReceipts []*types.ReceiptForStorage
return db.ModifyAncients(func(op ethdb.AncientWriteOp) error { return db.ModifyAncients(func(op ethdb.AncientWriteOp) error {
for i, block := range blocks { for i, block := range blocks {
// Convert receipts to storage format and sum up total difficulty.
stReceipts = stReceipts[:0]
for _, receipt := range receipts[i] {
stReceipts = append(stReceipts, (*types.ReceiptForStorage)(receipt))
}
header := block.Header() header := block.Header()
if err := writeAncientBlock(op, block, header, stReceipts); err != nil { if err := writeAncientBlock(op, block, header, receipts[i]); err != nil {
return err return err
} }
} }
@ -720,7 +721,7 @@ func WriteAncientBlocks(db ethdb.AncientWriter, blocks []*types.Block, receipts
}) })
} }
func writeAncientBlock(op ethdb.AncientWriteOp, block *types.Block, header *types.Header, receipts []*types.ReceiptForStorage) error { func writeAncientBlock(op ethdb.AncientWriteOp, block *types.Block, header *types.Header, receipts rlp.RawValue) error {
num := block.NumberU64() num := block.NumberU64()
if err := op.AppendRaw(ChainFreezerHashTable, num, block.Hash().Bytes()); err != nil { if err := op.AppendRaw(ChainFreezerHashTable, num, block.Hash().Bytes()); err != nil {
return fmt.Errorf("can't add block %d hash: %v", num, err) return fmt.Errorf("can't add block %d hash: %v", num, err)

View file

@ -377,7 +377,11 @@ func TestBlockReceiptStorage(t *testing.T) {
t.Fatalf("receipts returned when body was deleted: %v", rs) t.Fatalf("receipts returned when body was deleted: %v", rs)
} }
// Ensure that receipts without metadata can be returned without the block body too // Ensure that receipts without metadata can be returned without the block body too
if err := checkReceiptsRLP(ReadRawReceipts(db, hash, 0), receipts); err != nil { raw := ReadRawReceipts(db, hash, 0)
for _, r := range raw {
r.Bloom = types.CreateBloom(r)
}
if err := checkReceiptsRLP(raw, receipts); err != nil {
t.Fatal(err) t.Fatal(err)
} }
// Sanity check that body alone without the receipt is a full purge // Sanity check that body alone without the receipt is a full purge
@ -439,7 +443,7 @@ func TestAncientStorage(t *testing.T) {
} }
// Write and verify the header in the database // Write and verify the header in the database
WriteAncientBlocks(db, []*types.Block{block}, []types.Receipts{nil}) WriteAncientBlocks(db, []*types.Block{block}, types.EncodeBlockReceiptLists([]types.Receipts{nil}))
if blob := ReadHeaderRLP(db, hash, number); len(blob) == 0 { if blob := ReadHeaderRLP(db, hash, number); len(blob) == 0 {
t.Fatalf("no header returned") t.Fatalf("no header returned")
@ -609,7 +613,7 @@ func BenchmarkWriteAncientBlocks(b *testing.B) {
blocks := allBlocks[i : i+length] blocks := allBlocks[i : i+length]
receipts := batchReceipts[:length] receipts := batchReceipts[:length]
writeSize, err := WriteAncientBlocks(db, blocks, receipts) writeSize, err := WriteAncientBlocks(db, blocks, types.EncodeBlockReceiptLists(receipts))
if err != nil { if err != nil {
b.Fatal(err) b.Fatal(err)
} }
@ -909,7 +913,7 @@ func TestHeadersRLPStorage(t *testing.T) {
} }
receipts := make([]types.Receipts, 100) receipts := make([]types.Receipts, 100)
// Write first half to ancients // Write first half to ancients
WriteAncientBlocks(db, chain[:50], receipts[:50]) WriteAncientBlocks(db, chain[:50], types.EncodeBlockReceiptLists(receipts[:50]))
// Write second half to db // Write second half to db
for i := 50; i < 100; i++ { for i := 50; i < 100; i++ {
WriteCanonicalHash(db, chain[i].Hash(), chain[i].NumberU64()) WriteCanonicalHash(db, chain[i].Hash(), chain[i].NumberU64())

View file

@ -258,13 +258,21 @@ func ReadStateHistory(db ethdb.AncientReaderOp, id uint64) ([]byte, []byte, []by
// WriteStateHistory writes the provided state history to database. Compute the // WriteStateHistory writes the provided state history to database. Compute the
// position of state history in freezer by minus one since the id of first state // position of state history in freezer by minus one since the id of first state
// history starts from one(zero for initial state). // history starts from one(zero for initial state).
func WriteStateHistory(db ethdb.AncientWriter, id uint64, meta []byte, accountIndex []byte, storageIndex []byte, accounts []byte, storages []byte) { func WriteStateHistory(db ethdb.AncientWriter, id uint64, meta []byte, accountIndex []byte, storageIndex []byte, accounts []byte, storages []byte) error {
db.ModifyAncients(func(op ethdb.AncientWriteOp) error { _, err := db.ModifyAncients(func(op ethdb.AncientWriteOp) error {
op.AppendRaw(stateHistoryMeta, id-1, meta) if err := op.AppendRaw(stateHistoryMeta, id-1, meta); err != nil {
op.AppendRaw(stateHistoryAccountIndex, id-1, accountIndex) return err
op.AppendRaw(stateHistoryStorageIndex, id-1, storageIndex) }
op.AppendRaw(stateHistoryAccountData, id-1, accounts) if err := op.AppendRaw(stateHistoryAccountIndex, id-1, accountIndex); err != nil {
op.AppendRaw(stateHistoryStorageData, id-1, storages) return err
return nil }
if err := op.AppendRaw(stateHistoryStorageIndex, id-1, storageIndex); err != nil {
return err
}
if err := op.AppendRaw(stateHistoryAccountData, id-1, accounts); err != nil {
return err
}
return op.AppendRaw(stateHistoryStorageData, id-1, storages)
}) })
return err
} }

View file

@ -18,7 +18,6 @@ package rawdb
import ( import (
"fmt" "fmt"
"sync"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/crypto"
@ -45,25 +44,6 @@ const HashScheme = "hash"
// on extra state diffs to survive deep reorg. // on extra state diffs to survive deep reorg.
const PathScheme = "path" const PathScheme = "path"
// hasher is used to compute the sha256 hash of the provided data.
type hasher struct{ sha crypto.KeccakState }
var hasherPool = sync.Pool{
New: func() interface{} { return &hasher{sha: crypto.NewKeccakState()} },
}
func newHasher() *hasher {
return hasherPool.Get().(*hasher)
}
func (h *hasher) hash(data []byte) common.Hash {
return crypto.HashData(h.sha, data)
}
func (h *hasher) release() {
hasherPool.Put(h)
}
// ReadAccountTrieNode retrieves the account trie node with the specified node path. // ReadAccountTrieNode retrieves the account trie node with the specified node path.
func ReadAccountTrieNode(db ethdb.KeyValueReader, path []byte) []byte { func ReadAccountTrieNode(db ethdb.KeyValueReader, path []byte) []byte {
data, _ := db.Get(accountTrieNodeKey(path)) data, _ := db.Get(accountTrieNodeKey(path))
@ -170,9 +150,7 @@ func HasTrieNode(db ethdb.KeyValueReader, owner common.Hash, path []byte, hash c
if len(blob) == 0 { if len(blob) == 0 {
return false return false
} }
h := newHasher() return crypto.Keccak256Hash(blob) == hash // exists but not match
defer h.release()
return h.hash(blob) == hash // exists but not match
default: default:
panic(fmt.Sprintf("Unknown scheme %v", scheme)) panic(fmt.Sprintf("Unknown scheme %v", scheme))
} }
@ -194,9 +172,7 @@ func ReadTrieNode(db ethdb.KeyValueReader, owner common.Hash, path []byte, hash
if len(blob) == 0 { if len(blob) == 0 {
return nil return nil
} }
h := newHasher() if crypto.Keccak256Hash(blob) != hash {
defer h.release()
if h.hash(blob) != hash {
return nil // exists but not match return nil // exists but not match
} }
return blob return blob

View file

@ -205,7 +205,7 @@ func (f *chainFreezer) freeze(db ethdb.KeyValueStore) {
continue continue
} }
// Batch of blocks have been frozen, flush them before wiping from key-value store // Batch of blocks have been frozen, flush them before wiping from key-value store
if err := f.Sync(); err != nil { if err := f.SyncAncient(); err != nil {
log.Crit("Failed to flush frozen tables", "err", err) log.Crit("Failed to flush frozen tables", "err", err)
} }
// Wipe out all data from the active database // Wipe out all data from the active database

View file

@ -131,8 +131,8 @@ func (db *nofreezedb) TruncateTail(items uint64) (uint64, error) {
return 0, errNotSupported return 0, errNotSupported
} }
// Sync returns an error as we don't have a backing chain freezer. // SyncAncient returns an error as we don't have a backing chain freezer.
func (db *nofreezedb) Sync() error { func (db *nofreezedb) SyncAncient() error {
return errNotSupported return errNotSupported
} }

View file

@ -325,8 +325,8 @@ func (f *Freezer) TruncateTail(tail uint64) (uint64, error) {
return old, nil return old, nil
} }
// Sync flushes all data tables to disk. // SyncAncient flushes all data tables to disk.
func (f *Freezer) Sync() error { func (f *Freezer) SyncAncient() error {
var errs []error var errs []error
for _, table := range f.tables { for _, table := range f.tables {
if err := table.Sync(); err != nil { if err := table.Sync(); err != nil {

View file

@ -395,8 +395,8 @@ func (f *MemoryFreezer) TruncateTail(tail uint64) (uint64, error) {
return old, nil return old, nil
} }
// Sync flushes all data tables to disk. // SyncAncient flushes all data tables to disk.
func (f *MemoryFreezer) Sync() error { func (f *MemoryFreezer) SyncAncient() error {
return nil return nil
} }

View file

@ -194,12 +194,12 @@ func (f *resettableFreezer) TruncateTail(tail uint64) (uint64, error) {
return f.freezer.TruncateTail(tail) return f.freezer.TruncateTail(tail)
} }
// Sync flushes all data tables to disk. // SyncAncient flushes all data tables to disk.
func (f *resettableFreezer) Sync() error { func (f *resettableFreezer) SyncAncient() error {
f.lock.RLock() f.lock.RLock()
defer f.lock.RUnlock() defer f.lock.RUnlock()
return f.freezer.Sync() return f.freezer.SyncAncient()
} }
// AncientDatadir returns the path of the ancient store. // AncientDatadir returns the path of the ancient store.

View file

@ -392,7 +392,7 @@ func TestFreezerCloseSync(t *testing.T) {
if err := f.Close(); err != nil { if err := f.Close(); err != nil {
t.Fatal(err) t.Fatal(err)
} }
if err := f.Sync(); err == nil { if err := f.SyncAncient(); err == nil {
t.Fatalf("want error, have nil") t.Fatalf("want error, have nil")
} else if have, want := err.Error(), "[closed closed]"; have != want { } else if have, want := err.Error(), "[closed closed]"; have != want {
t.Fatalf("want %v, have %v", have, want) t.Fatalf("want %v, have %v", have, want)

View file

@ -107,10 +107,10 @@ func (t *table) TruncateTail(items uint64) (uint64, error) {
return t.db.TruncateTail(items) return t.db.TruncateTail(items)
} }
// Sync is a noop passthrough that just forwards the request to the underlying // SyncAncient is a noop passthrough that just forwards the request to the underlying
// database. // database.
func (t *table) Sync() error { func (t *table) SyncAncient() error {
return t.db.Sync() return t.db.SyncAncient()
} }
// AncientDatadir returns the ancient datadir of the underlying database. // AncientDatadir returns the ancient datadir of the underlying database.
@ -188,6 +188,12 @@ func (t *table) Compact(start []byte, limit []byte) error {
return t.db.Compact(start, limit) return t.db.Compact(start, limit)
} }
// SyncKeyValue ensures that all pending writes are flushed to disk,
// guaranteeing data durability up to the point.
func (t *table) SyncKeyValue() error {
return t.db.SyncKeyValue()
}
// NewBatch creates a write-only database that buffers changes to its host db // NewBatch creates a write-only database that buffers changes to its host db
// until a final write is called, each operation prefixing all keys with the // until a final write is called, each operation prefixing all keys with the
// pre-configured string. // pre-configured string.

View file

@ -18,6 +18,7 @@ package state
import ( import (
"maps" "maps"
gomath "math"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/math" "github.com/ethereum/go-ethereum/common/math"
@ -92,97 +93,94 @@ func (ae *AccessEvents) Copy() *AccessEvents {
// AddAccount returns the gas to be charged for each of the currently cold // AddAccount returns the gas to be charged for each of the currently cold
// member fields of an account. // member fields of an account.
func (ae *AccessEvents) AddAccount(addr common.Address, isWrite bool) uint64 { func (ae *AccessEvents) AddAccount(addr common.Address, isWrite bool, availableGas uint64) uint64 {
var gas uint64 var gas uint64 // accumulate the consumed gas
gas += ae.touchAddressAndChargeGas(addr, zeroTreeIndex, utils.BasicDataLeafKey, isWrite) consumed, expected := ae.touchAddressAndChargeGas(addr, zeroTreeIndex, utils.BasicDataLeafKey, isWrite, availableGas)
gas += ae.touchAddressAndChargeGas(addr, zeroTreeIndex, utils.CodeHashLeafKey, isWrite) if consumed < expected {
return expected
}
gas += consumed
consumed, expected = ae.touchAddressAndChargeGas(addr, zeroTreeIndex, utils.CodeHashLeafKey, isWrite, availableGas-consumed)
if consumed < expected {
return expected + gas
}
gas += expected
return gas return gas
} }
// MessageCallGas returns the gas to be charged for each of the currently // MessageCallGas returns the gas to be charged for each of the currently
// cold member fields of an account, that need to be touched when making a message // cold member fields of an account, that need to be touched when making a message
// call to that account. // call to that account.
func (ae *AccessEvents) MessageCallGas(destination common.Address) uint64 { func (ae *AccessEvents) MessageCallGas(destination common.Address, availableGas uint64) uint64 {
var gas uint64 _, expected := ae.touchAddressAndChargeGas(destination, zeroTreeIndex, utils.BasicDataLeafKey, false, availableGas)
gas += ae.touchAddressAndChargeGas(destination, zeroTreeIndex, utils.BasicDataLeafKey, false) if expected == 0 {
return gas expected = params.WarmStorageReadCostEIP2929
}
return expected
} }
// ValueTransferGas returns the gas to be charged for each of the currently // ValueTransferGas returns the gas to be charged for each of the currently
// cold balance member fields of the caller and the callee accounts. // cold balance member fields of the caller and the callee accounts.
func (ae *AccessEvents) ValueTransferGas(callerAddr, targetAddr common.Address) uint64 { func (ae *AccessEvents) ValueTransferGas(callerAddr, targetAddr common.Address, availableGas uint64) uint64 {
var gas uint64 _, expected1 := ae.touchAddressAndChargeGas(callerAddr, zeroTreeIndex, utils.BasicDataLeafKey, true, availableGas)
gas += ae.touchAddressAndChargeGas(callerAddr, zeroTreeIndex, utils.BasicDataLeafKey, true) if expected1 > availableGas {
gas += ae.touchAddressAndChargeGas(targetAddr, zeroTreeIndex, utils.BasicDataLeafKey, true) return expected1
return gas }
_, expected2 := ae.touchAddressAndChargeGas(targetAddr, zeroTreeIndex, utils.BasicDataLeafKey, true, availableGas-expected1)
if expected1+expected2 == 0 {
return params.WarmStorageReadCostEIP2929
}
return expected1 + expected2
} }
// ContractCreatePreCheckGas charges access costs before // ContractCreatePreCheckGas charges access costs before
// a contract creation is initiated. It is just reads, because the // a contract creation is initiated. It is just reads, because the
// address collision is done before the transfer, and so no write // address collision is done before the transfer, and so no write
// are guaranteed to happen at this point. // are guaranteed to happen at this point.
func (ae *AccessEvents) ContractCreatePreCheckGas(addr common.Address) uint64 { func (ae *AccessEvents) ContractCreatePreCheckGas(addr common.Address, availableGas uint64) uint64 {
var gas uint64 consumed, expected1 := ae.touchAddressAndChargeGas(addr, zeroTreeIndex, utils.BasicDataLeafKey, false, availableGas)
gas += ae.touchAddressAndChargeGas(addr, zeroTreeIndex, utils.BasicDataLeafKey, false) _, expected2 := ae.touchAddressAndChargeGas(addr, zeroTreeIndex, utils.CodeHashLeafKey, false, availableGas-consumed)
gas += ae.touchAddressAndChargeGas(addr, zeroTreeIndex, utils.CodeHashLeafKey, false) return expected1 + expected2
return gas
} }
// ContractCreateInitGas returns the access gas costs for the initialization of // ContractCreateInitGas returns the access gas costs for the initialization of
// a contract creation. // a contract creation.
func (ae *AccessEvents) ContractCreateInitGas(addr common.Address) uint64 { func (ae *AccessEvents) ContractCreateInitGas(addr common.Address, availableGas uint64) (uint64, uint64) {
var gas uint64 var gas uint64
gas += ae.touchAddressAndChargeGas(addr, zeroTreeIndex, utils.BasicDataLeafKey, true) consumed, expected1 := ae.touchAddressAndChargeGas(addr, zeroTreeIndex, utils.BasicDataLeafKey, true, availableGas)
gas += ae.touchAddressAndChargeGas(addr, zeroTreeIndex, utils.CodeHashLeafKey, true) gas += consumed
return gas consumed, expected2 := ae.touchAddressAndChargeGas(addr, zeroTreeIndex, utils.CodeHashLeafKey, true, availableGas-consumed)
gas += consumed
return gas, expected1 + expected2
} }
// AddTxOrigin adds the member fields of the sender account to the access event list, // AddTxOrigin adds the member fields of the sender account to the access event list,
// so that cold accesses are not charged, since they are covered by the 21000 gas. // so that cold accesses are not charged, since they are covered by the 21000 gas.
func (ae *AccessEvents) AddTxOrigin(originAddr common.Address) { func (ae *AccessEvents) AddTxOrigin(originAddr common.Address) {
ae.touchAddressAndChargeGas(originAddr, zeroTreeIndex, utils.BasicDataLeafKey, true) ae.touchAddressAndChargeGas(originAddr, zeroTreeIndex, utils.BasicDataLeafKey, true, gomath.MaxUint64)
ae.touchAddressAndChargeGas(originAddr, zeroTreeIndex, utils.CodeHashLeafKey, false) ae.touchAddressAndChargeGas(originAddr, zeroTreeIndex, utils.CodeHashLeafKey, false, gomath.MaxUint64)
} }
// AddTxDestination adds the member fields of the sender account to the access event list, // AddTxDestination adds the member fields of the sender account to the access event list,
// so that cold accesses are not charged, since they are covered by the 21000 gas. // so that cold accesses are not charged, since they are covered by the 21000 gas.
func (ae *AccessEvents) AddTxDestination(addr common.Address, sendsValue bool) { func (ae *AccessEvents) AddTxDestination(addr common.Address, sendsValue, doesntExist bool) {
ae.touchAddressAndChargeGas(addr, zeroTreeIndex, utils.BasicDataLeafKey, sendsValue) ae.touchAddressAndChargeGas(addr, zeroTreeIndex, utils.BasicDataLeafKey, sendsValue, gomath.MaxUint64)
ae.touchAddressAndChargeGas(addr, zeroTreeIndex, utils.CodeHashLeafKey, false) ae.touchAddressAndChargeGas(addr, zeroTreeIndex, utils.CodeHashLeafKey, doesntExist, gomath.MaxUint64)
} }
// SlotGas returns the amount of gas to be charged for a cold storage access. // SlotGas returns the amount of gas to be charged for a cold storage access.
func (ae *AccessEvents) SlotGas(addr common.Address, slot common.Hash, isWrite bool) uint64 { func (ae *AccessEvents) SlotGas(addr common.Address, slot common.Hash, isWrite bool, availableGas uint64, chargeWarmCosts bool) uint64 {
treeIndex, subIndex := utils.StorageIndex(slot.Bytes()) treeIndex, subIndex := utils.StorageIndex(slot.Bytes())
return ae.touchAddressAndChargeGas(addr, *treeIndex, subIndex, isWrite) _, expected := ae.touchAddressAndChargeGas(addr, *treeIndex, subIndex, isWrite, availableGas)
if expected == 0 && chargeWarmCosts {
expected = params.WarmStorageReadCostEIP2929
}
return expected
} }
// touchAddressAndChargeGas adds any missing access event to the access event list, and returns the cold // touchAddressAndChargeGas adds any missing access event to the access event list, and returns the
// access cost to be charged, if need be. // consumed and required gas.
func (ae *AccessEvents) touchAddressAndChargeGas(addr common.Address, treeIndex uint256.Int, subIndex byte, isWrite bool) uint64 { func (ae *AccessEvents) touchAddressAndChargeGas(addr common.Address, treeIndex uint256.Int, subIndex byte, isWrite bool, availableGas uint64) (uint64, uint64) {
stemRead, selectorRead, stemWrite, selectorWrite, selectorFill := ae.touchAddress(addr, treeIndex, subIndex, isWrite)
var gas uint64
if stemRead {
gas += params.WitnessBranchReadCost
}
if selectorRead {
gas += params.WitnessChunkReadCost
}
if stemWrite {
gas += params.WitnessBranchWriteCost
}
if selectorWrite {
gas += params.WitnessChunkWriteCost
}
if selectorFill {
gas += params.WitnessChunkFillCost
}
return gas
}
// touchAddress adds any missing access event to the access event list.
func (ae *AccessEvents) touchAddress(addr common.Address, treeIndex uint256.Int, subIndex byte, isWrite bool) (bool, bool, bool, bool, bool) {
branchKey := newBranchAccessKey(addr, treeIndex) branchKey := newBranchAccessKey(addr, treeIndex)
chunkKey := newChunkAccessKey(branchKey, subIndex) chunkKey := newChunkAccessKey(branchKey, subIndex)
@ -190,11 +188,9 @@ func (ae *AccessEvents) touchAddress(addr common.Address, treeIndex uint256.Int,
var branchRead, chunkRead bool var branchRead, chunkRead bool
if _, hasStem := ae.branches[branchKey]; !hasStem { if _, hasStem := ae.branches[branchKey]; !hasStem {
branchRead = true branchRead = true
ae.branches[branchKey] = AccessWitnessReadFlag
} }
if _, hasSelector := ae.chunks[chunkKey]; !hasSelector { if _, hasSelector := ae.chunks[chunkKey]; !hasSelector {
chunkRead = true chunkRead = true
ae.chunks[chunkKey] = AccessWitnessReadFlag
} }
// Write access. // Write access.
@ -202,17 +198,51 @@ func (ae *AccessEvents) touchAddress(addr common.Address, treeIndex uint256.Int,
if isWrite { if isWrite {
if (ae.branches[branchKey] & AccessWitnessWriteFlag) == 0 { if (ae.branches[branchKey] & AccessWitnessWriteFlag) == 0 {
branchWrite = true branchWrite = true
ae.branches[branchKey] |= AccessWitnessWriteFlag
} }
chunkValue := ae.chunks[chunkKey] chunkValue := ae.chunks[chunkKey]
if (chunkValue & AccessWitnessWriteFlag) == 0 { if (chunkValue & AccessWitnessWriteFlag) == 0 {
chunkWrite = true chunkWrite = true
ae.chunks[chunkKey] |= AccessWitnessWriteFlag
} }
// TODO: charge chunk filling costs if the leaf was previously empty in the state
} }
return branchRead, chunkRead, branchWrite, chunkWrite, chunkFill
var gas uint64
if branchRead {
gas += params.WitnessBranchReadCost
}
if chunkRead {
gas += params.WitnessChunkReadCost
}
if branchWrite {
gas += params.WitnessBranchWriteCost
}
if chunkWrite {
gas += params.WitnessChunkWriteCost
}
if chunkFill {
gas += params.WitnessChunkFillCost
}
if availableGas < gas {
// consumed != expected
return availableGas, gas
}
if branchRead {
ae.branches[branchKey] = AccessWitnessReadFlag
}
if branchWrite {
ae.branches[branchKey] |= AccessWitnessWriteFlag
}
if chunkRead {
ae.chunks[chunkKey] = AccessWitnessReadFlag
}
if chunkWrite {
ae.chunks[chunkKey] |= AccessWitnessWriteFlag
}
// consumed == expected
return gas, gas
} }
type branchAccessKey struct { type branchAccessKey struct {
@ -240,7 +270,7 @@ func newChunkAccessKey(branchKey branchAccessKey, leafKey byte) chunkAccessKey {
} }
// CodeChunksRangeGas is a helper function to touch every chunk in a code range and charge witness gas costs // CodeChunksRangeGas is a helper function to touch every chunk in a code range and charge witness gas costs
func (ae *AccessEvents) CodeChunksRangeGas(contractAddr common.Address, startPC, size uint64, codeLen uint64, isWrite bool) uint64 { func (ae *AccessEvents) CodeChunksRangeGas(contractAddr common.Address, startPC, size uint64, codeLen uint64, isWrite bool, availableGas uint64) (uint64, uint64) {
// note that in the case where the copied code is outside the range of the // note that in the case where the copied code is outside the range of the
// contract code but touches the last leaf with contract code in it, // contract code but touches the last leaf with contract code in it,
// we don't include the last leaf of code in the AccessWitness. The // we don't include the last leaf of code in the AccessWitness. The
@ -248,7 +278,7 @@ func (ae *AccessEvents) CodeChunksRangeGas(contractAddr common.Address, startPC,
// is already in the AccessWitness so a stateless verifier can see that // is already in the AccessWitness so a stateless verifier can see that
// the code from the last leaf is not needed. // the code from the last leaf is not needed.
if (codeLen == 0 && size == 0) || startPC > codeLen { if (codeLen == 0 && size == 0) || startPC > codeLen {
return 0 return 0, 0
} }
endPC := startPC + size endPC := startPC + size
@ -263,22 +293,34 @@ func (ae *AccessEvents) CodeChunksRangeGas(contractAddr common.Address, startPC,
for chunkNumber := startPC / 31; chunkNumber <= endPC/31; chunkNumber++ { for chunkNumber := startPC / 31; chunkNumber <= endPC/31; chunkNumber++ {
treeIndex := *uint256.NewInt((chunkNumber + 128) / 256) treeIndex := *uint256.NewInt((chunkNumber + 128) / 256)
subIndex := byte((chunkNumber + 128) % 256) subIndex := byte((chunkNumber + 128) % 256)
gas := ae.touchAddressAndChargeGas(contractAddr, treeIndex, subIndex, isWrite) consumed, expected := ae.touchAddressAndChargeGas(contractAddr, treeIndex, subIndex, isWrite, availableGas)
// did we OOG ?
if expected > consumed {
return statelessGasCharged + consumed, statelessGasCharged + expected
}
var overflow bool var overflow bool
statelessGasCharged, overflow = math.SafeAdd(statelessGasCharged, gas) statelessGasCharged, overflow = math.SafeAdd(statelessGasCharged, consumed)
if overflow { if overflow {
panic("overflow when adding gas") panic("overflow when adding gas")
} }
availableGas -= consumed
} }
return statelessGasCharged return statelessGasCharged, statelessGasCharged
} }
// BasicDataGas adds the account's basic data to the accessed data, and returns the // BasicDataGas adds the account's basic data to the accessed data, and returns the
// amount of gas that it costs. // amount of gas that it costs.
// Note that an access in write mode implies an access in read mode, whereas an // Note that an access in write mode implies an access in read mode, whereas an
// access in read mode does not imply an access in write mode. // access in read mode does not imply an access in write mode.
func (ae *AccessEvents) BasicDataGas(addr common.Address, isWrite bool) uint64 { func (ae *AccessEvents) BasicDataGas(addr common.Address, isWrite bool, availableGas uint64, chargeWarmCosts bool) uint64 {
return ae.touchAddressAndChargeGas(addr, zeroTreeIndex, utils.BasicDataLeafKey, isWrite) _, expected := ae.touchAddressAndChargeGas(addr, zeroTreeIndex, utils.BasicDataLeafKey, isWrite, availableGas)
if expected == 0 && chargeWarmCosts {
if availableGas < params.WarmStorageReadCostEIP2929 {
return availableGas
}
expected = params.WarmStorageReadCostEIP2929
}
return expected
} }
// CodeHashGas adds the account's code hash to the accessed data, and returns the // CodeHashGas adds the account's code hash to the accessed data, and returns the
@ -286,6 +328,13 @@ func (ae *AccessEvents) BasicDataGas(addr common.Address, isWrite bool) uint64 {
// in write mode. If false, the charged gas corresponds to an access in read mode. // in write mode. If false, the charged gas corresponds to an access in read mode.
// Note that an access in write mode implies an access in read mode, whereas an access in // Note that an access in write mode implies an access in read mode, whereas an access in
// read mode does not imply an access in write mode. // read mode does not imply an access in write mode.
func (ae *AccessEvents) CodeHashGas(addr common.Address, isWrite bool) uint64 { func (ae *AccessEvents) CodeHashGas(addr common.Address, isWrite bool, availableGas uint64, chargeWarmCosts bool) uint64 {
return ae.touchAddressAndChargeGas(addr, zeroTreeIndex, utils.CodeHashLeafKey, isWrite) _, expected := ae.touchAddressAndChargeGas(addr, zeroTreeIndex, utils.CodeHashLeafKey, isWrite, availableGas)
if expected == 0 && chargeWarmCosts {
if availableGas < params.WarmStorageReadCostEIP2929 {
return availableGas
}
expected = params.WarmStorageReadCostEIP2929
}
return expected
} }

View file

@ -17,6 +17,7 @@
package state package state
import ( import (
"math"
"testing" "testing"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
@ -40,50 +41,50 @@ func TestAccountHeaderGas(t *testing.T) {
ae := NewAccessEvents(utils.NewPointCache(1024)) ae := NewAccessEvents(utils.NewPointCache(1024))
// Check cold read cost // Check cold read cost
gas := ae.BasicDataGas(testAddr, false) gas := ae.BasicDataGas(testAddr, false, math.MaxUint64, false)
if want := params.WitnessBranchReadCost + params.WitnessChunkReadCost; gas != want { if want := params.WitnessBranchReadCost + params.WitnessChunkReadCost; gas != want {
t.Fatalf("incorrect gas computed, got %d, want %d", gas, want) t.Fatalf("incorrect gas computed, got %d, want %d", gas, want)
} }
// Check warm read cost // Check warm read cost
gas = ae.BasicDataGas(testAddr, false) gas = ae.BasicDataGas(testAddr, false, math.MaxUint64, false)
if gas != 0 { if gas != 0 {
t.Fatalf("incorrect gas computed, got %d, want %d", gas, 0) t.Fatalf("incorrect gas computed, got %d, want %d", gas, 0)
} }
// Check cold read costs in the same group no longer incur the branch read cost // Check cold read costs in the same group no longer incur the branch read cost
gas = ae.CodeHashGas(testAddr, false) gas = ae.CodeHashGas(testAddr, false, math.MaxUint64, false)
if gas != params.WitnessChunkReadCost { if gas != params.WitnessChunkReadCost {
t.Fatalf("incorrect gas computed, got %d, want %d", gas, params.WitnessChunkReadCost) t.Fatalf("incorrect gas computed, got %d, want %d", gas, params.WitnessChunkReadCost)
} }
// Check cold write cost // Check cold write cost
gas = ae.BasicDataGas(testAddr, true) gas = ae.BasicDataGas(testAddr, true, math.MaxUint64, false)
if want := params.WitnessBranchWriteCost + params.WitnessChunkWriteCost; gas != want { if want := params.WitnessBranchWriteCost + params.WitnessChunkWriteCost; gas != want {
t.Fatalf("incorrect gas computed, got %d, want %d", gas, want) t.Fatalf("incorrect gas computed, got %d, want %d", gas, want)
} }
// Check warm write cost // Check warm write cost
gas = ae.BasicDataGas(testAddr, true) gas = ae.BasicDataGas(testAddr, true, math.MaxUint64, false)
if gas != 0 { if gas != 0 {
t.Fatalf("incorrect gas computed, got %d, want %d", gas, 0) t.Fatalf("incorrect gas computed, got %d, want %d", gas, 0)
} }
// Check a write without a read charges both read and write costs // Check a write without a read charges both read and write costs
gas = ae.BasicDataGas(testAddr2, true) gas = ae.BasicDataGas(testAddr2, true, math.MaxUint64, false)
if want := params.WitnessBranchReadCost + params.WitnessBranchWriteCost + params.WitnessChunkWriteCost + params.WitnessChunkReadCost; gas != want { if want := params.WitnessBranchReadCost + params.WitnessBranchWriteCost + params.WitnessChunkWriteCost + params.WitnessChunkReadCost; gas != want {
t.Fatalf("incorrect gas computed, got %d, want %d", gas, want) t.Fatalf("incorrect gas computed, got %d, want %d", gas, want)
} }
// Check that a write followed by a read charges nothing // Check that a write followed by a read charges nothing
gas = ae.BasicDataGas(testAddr2, false) gas = ae.BasicDataGas(testAddr2, false, math.MaxUint64, false)
if gas != 0 { if gas != 0 {
t.Fatalf("incorrect gas computed, got %d, want %d", gas, 0) t.Fatalf("incorrect gas computed, got %d, want %d", gas, 0)
} }
// Check that reading a slot from the account header only charges the // Check that reading a slot from the account header only charges the
// chunk read cost. // chunk read cost.
gas = ae.SlotGas(testAddr, common.Hash{}, false) gas = ae.SlotGas(testAddr, common.Hash{}, false, math.MaxUint64, false)
if gas != params.WitnessChunkReadCost { if gas != params.WitnessChunkReadCost {
t.Fatalf("incorrect gas computed, got %d, want %d", gas, params.WitnessChunkReadCost) t.Fatalf("incorrect gas computed, got %d, want %d", gas, params.WitnessChunkReadCost)
} }
@ -100,13 +101,13 @@ func TestContractCreateInitGas(t *testing.T) {
} }
// Check cold read cost, without a value // Check cold read cost, without a value
gas := ae.ContractCreateInitGas(testAddr) gas, _ := ae.ContractCreateInitGas(testAddr, math.MaxUint64)
if want := params.WitnessBranchWriteCost + params.WitnessBranchReadCost + 2*params.WitnessChunkWriteCost + 2*params.WitnessChunkReadCost; gas != want { if want := params.WitnessBranchWriteCost + params.WitnessBranchReadCost + 2*params.WitnessChunkWriteCost + 2*params.WitnessChunkReadCost; gas != want {
t.Fatalf("incorrect gas computed, got %d, want %d", gas, want) t.Fatalf("incorrect gas computed, got %d, want %d", gas, want)
} }
// Check warm read cost // Check warm read cost
gas = ae.ContractCreateInitGas(testAddr) gas, _ = ae.ContractCreateInitGas(testAddr, math.MaxUint64)
if gas != 0 { if gas != 0 {
t.Fatalf("incorrect gas computed, got %d, want %d", gas, 0) t.Fatalf("incorrect gas computed, got %d, want %d", gas, 0)
} }
@ -118,24 +119,24 @@ func TestMessageCallGas(t *testing.T) {
ae := NewAccessEvents(utils.NewPointCache(1024)) ae := NewAccessEvents(utils.NewPointCache(1024))
// Check cold read cost, without a value // Check cold read cost, without a value
gas := ae.MessageCallGas(testAddr) gas := ae.MessageCallGas(testAddr, math.MaxUint64)
if want := params.WitnessBranchReadCost + params.WitnessChunkReadCost; gas != want { if want := params.WitnessBranchReadCost + params.WitnessChunkReadCost; gas != want {
t.Fatalf("incorrect gas computed, got %d, want %d", gas, want) t.Fatalf("incorrect gas computed, got %d, want %d", gas, want)
} }
// Check that reading the basic data and code hash of the same account does not incur the branch read cost // Check that reading the basic data and code hash of the same account does not incur the branch read cost
gas = ae.BasicDataGas(testAddr, false) gas = ae.BasicDataGas(testAddr, false, math.MaxUint64, false)
if gas != 0 { if gas != 0 {
t.Fatalf("incorrect gas computed, got %d, want %d", gas, 0) t.Fatalf("incorrect gas computed, got %d, want %d", gas, 0)
} }
gas = ae.CodeHashGas(testAddr, false) gas = ae.CodeHashGas(testAddr, false, math.MaxUint64, false)
if gas != params.WitnessChunkReadCost { if gas != params.WitnessChunkReadCost {
t.Fatalf("incorrect gas computed, got %d, want %d", gas, 0) t.Fatalf("incorrect gas computed, got %d, want %d", gas, 0)
} }
// Check warm read cost // Check warm read cost
gas = ae.MessageCallGas(testAddr) gas = ae.MessageCallGas(testAddr, math.MaxUint64)
if gas != 0 { if gas != params.WarmStorageReadCostEIP2929 {
t.Fatalf("incorrect gas computed, got %d, want %d", gas, 0) t.Fatalf("incorrect gas computed, got %d, want %d", gas, params.WarmStorageReadCostEIP2929)
} }
} }

View file

@ -34,10 +34,10 @@ import (
const ( const (
// Number of codehash->size associations to keep. // Number of codehash->size associations to keep.
codeSizeCacheSize = 100000 codeSizeCacheSize = 1_000_000 // 4 megabytes in total
// Cache size granted for caching clean code. // Cache size granted for caching clean code.
codeCacheSize = 64 * 1024 * 1024 codeCacheSize = 256 * 1024 * 1024
// Number of address->curve point associations to keep. // Number of address->curve point associations to keep.
pointCacheSize = 4096 pointCacheSize = 4096
@ -175,26 +175,27 @@ func NewDatabaseForTesting() *CachingDB {
func (db *CachingDB) Reader(stateRoot common.Hash) (Reader, error) { func (db *CachingDB) Reader(stateRoot common.Hash) (Reader, error) {
var readers []StateReader var readers []StateReader
// Set up the state snapshot reader if available. This feature // Configure the state reader using the standalone snapshot in hash mode.
// is optional and may be partially useful if it's not fully // This reader offers improved performance but is optional and only
// generated. // partially useful if the snapshot is not fully generated.
if db.snap != nil { if db.TrieDB().Scheme() == rawdb.HashScheme && db.snap != nil {
// If standalone state snapshot is available (hash scheme),
// then construct the legacy snap reader.
snap := db.snap.Snapshot(stateRoot) snap := db.snap.Snapshot(stateRoot)
if snap != nil { if snap != nil {
readers = append(readers, newFlatReader(snap)) readers = append(readers, newFlatReader(snap))
} }
} else { }
// If standalone state snapshot is not available, try to construct // Configure the state reader using the path database in path mode.
// the state reader with database. // This reader offers improved performance but is optional and only
// partially useful if the snapshot data in path database is not
// fully generated.
if db.TrieDB().Scheme() == rawdb.PathScheme {
reader, err := db.triedb.StateReader(stateRoot) reader, err := db.triedb.StateReader(stateRoot)
if err == nil { if err == nil {
readers = append(readers, newFlatReader(reader)) // state reader is optional readers = append(readers, newFlatReader(reader))
} }
} }
// Set up the trie reader, which is expected to always be available // Configure the trie reader, which is expected to be available as the
// as the gatekeeper unless the state is corrupted. // gatekeeper unless the state is corrupted.
tr, err := newTrieReader(stateRoot, db.triedb, db.pointCache) tr, err := newTrieReader(stateRoot, db.triedb, db.pointCache)
if err != nil { if err != nil {
return nil, err return nil, err
@ -208,6 +209,15 @@ func (db *CachingDB) Reader(stateRoot common.Hash) (Reader, error) {
return newReader(newCachingCodeReader(db.disk, db.codeCache, db.codeSizeCache), combined), nil return newReader(newCachingCodeReader(db.disk, db.codeCache, db.codeSizeCache), combined), nil
} }
// ReaderWithCache creates a state reader with internal local cache.
func (db *CachingDB) ReaderWithCache(stateRoot common.Hash) (Reader, error) {
reader, err := db.Reader(stateRoot)
if err != nil {
return nil, err
}
return newReaderWithCache(reader), nil
}
// OpenTrie opens the main account trie at a specific root hash. // OpenTrie opens the main account trie at a specific root hash.
func (db *CachingDB) OpenTrie(root common.Hash) (Trie, error) { func (db *CachingDB) OpenTrie(root common.Hash) (Trie, error) {
if db.triedb.IsVerkle() { if db.triedb.IsVerkle() {

View file

@ -18,6 +18,7 @@ package state
import ( import (
"errors" "errors"
"sync"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/lru" "github.com/ethereum/go-ethereum/common/lru"
@ -51,6 +52,9 @@ type ContractCodeReader interface {
// StateReader defines the interface for accessing accounts and storage slots // StateReader defines the interface for accessing accounts and storage slots
// associated with a specific state. // associated with a specific state.
//
// StateReader is assumed to be thread-safe and implementation must take care
// of the concurrency issue by themselves.
type StateReader interface { type StateReader interface {
// Account retrieves the account associated with a particular address. // Account retrieves the account associated with a particular address.
// //
@ -70,6 +74,9 @@ type StateReader interface {
// Reader defines the interface for accessing accounts, storage slots and contract // Reader defines the interface for accessing accounts, storage slots and contract
// code associated with a specific state. // code associated with a specific state.
//
// Reader is assumed to be thread-safe and implementation must take care of the
// concurrency issue by themselves.
type Reader interface { type Reader interface {
ContractCodeReader ContractCodeReader
StateReader StateReader
@ -77,6 +84,8 @@ type Reader interface {
// cachingCodeReader implements ContractCodeReader, accessing contract code either in // cachingCodeReader implements ContractCodeReader, accessing contract code either in
// local key-value store or the shared code cache. // local key-value store or the shared code cache.
//
// cachingCodeReader is safe for concurrent access.
type cachingCodeReader struct { type cachingCodeReader struct {
db ethdb.KeyValueReader db ethdb.KeyValueReader
@ -123,18 +132,14 @@ func (r *cachingCodeReader) CodeSize(addr common.Address, codeHash common.Hash)
return len(code), nil return len(code), nil
} }
// flatReader wraps a database state reader. // flatReader wraps a database state reader and is safe for concurrent access.
type flatReader struct { type flatReader struct {
reader database.StateReader reader database.StateReader
buff crypto.KeccakState
} }
// newFlatReader constructs a state reader with on the given state root. // newFlatReader constructs a state reader with on the given state root.
func newFlatReader(reader database.StateReader) *flatReader { func newFlatReader(reader database.StateReader) *flatReader {
return &flatReader{ return &flatReader{reader: reader}
reader: reader,
buff: crypto.NewKeccakState(),
}
} }
// Account implements StateReader, retrieving the account specified by the address. // Account implements StateReader, retrieving the account specified by the address.
@ -144,7 +149,7 @@ func newFlatReader(reader database.StateReader) *flatReader {
// //
// The returned account might be nil if it's not existent. // The returned account might be nil if it's not existent.
func (r *flatReader) Account(addr common.Address) (*types.StateAccount, error) { func (r *flatReader) Account(addr common.Address) (*types.StateAccount, error) {
account, err := r.reader.Account(crypto.HashData(r.buff, addr.Bytes())) account, err := r.reader.Account(crypto.Keccak256Hash(addr.Bytes()))
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -174,8 +179,8 @@ func (r *flatReader) Account(addr common.Address) (*types.StateAccount, error) {
// //
// The returned storage slot might be empty if it's not existent. // The returned storage slot might be empty if it's not existent.
func (r *flatReader) Storage(addr common.Address, key common.Hash) (common.Hash, error) { func (r *flatReader) Storage(addr common.Address, key common.Hash) (common.Hash, error) {
addrHash := crypto.HashData(r.buff, addr.Bytes()) addrHash := crypto.Keccak256Hash(addr.Bytes())
slotHash := crypto.HashData(r.buff, key.Bytes()) slotHash := crypto.Keccak256Hash(key.Bytes())
ret, err := r.reader.Storage(addrHash, slotHash) ret, err := r.reader.Storage(addrHash, slotHash)
if err != nil { if err != nil {
return common.Hash{}, err return common.Hash{}, err
@ -196,13 +201,20 @@ func (r *flatReader) Storage(addr common.Address, key common.Hash) (common.Hash,
// trieReader implements the StateReader interface, providing functions to access // trieReader implements the StateReader interface, providing functions to access
// state from the referenced trie. // state from the referenced trie.
//
// trieReader is safe for concurrent read.
type trieReader struct { type trieReader struct {
root common.Hash // State root which uniquely represent a state root common.Hash // State root which uniquely represent a state
db *triedb.Database // Database for loading trie db *triedb.Database // Database for loading trie
buff crypto.KeccakState // Buffer for keccak256 hashing buff crypto.KeccakState // Buffer for keccak256 hashing
mainTrie Trie // Main trie, resolved in constructor
// Main trie, resolved in constructor. Note either the Merkle-Patricia-tree
// or Verkle-tree is not safe for concurrent read.
mainTrie Trie
subRoots map[common.Address]common.Hash // Set of storage roots, cached when the account is resolved subRoots map[common.Address]common.Hash // Set of storage roots, cached when the account is resolved
subTries map[common.Address]Trie // Group of storage tries, cached when it's resolved subTries map[common.Address]Trie // Group of storage tries, cached when it's resolved
lock sync.Mutex // Lock for protecting concurrent read
} }
// trieReader constructs a trie reader of the specific state. An error will be // trieReader constructs a trie reader of the specific state. An error will be
@ -230,11 +242,8 @@ func newTrieReader(root common.Hash, db *triedb.Database, cache *utils.PointCach
}, nil }, nil
} }
// Account implements StateReader, retrieving the account specified by the address. // account is the inner version of Account and assumes the r.lock is already held.
// func (r *trieReader) account(addr common.Address) (*types.StateAccount, error) {
// An error will be returned if the trie state is corrupted. An nil account
// will be returned if it's not existent in the trie.
func (r *trieReader) Account(addr common.Address) (*types.StateAccount, error) {
account, err := r.mainTrie.GetAccount(addr) account, err := r.mainTrie.GetAccount(addr)
if err != nil { if err != nil {
return nil, err return nil, err
@ -247,12 +256,26 @@ func (r *trieReader) Account(addr common.Address) (*types.StateAccount, error) {
return account, nil return account, nil
} }
// Account implements StateReader, retrieving the account specified by the address.
//
// An error will be returned if the trie state is corrupted. An nil account
// will be returned if it's not existent in the trie.
func (r *trieReader) Account(addr common.Address) (*types.StateAccount, error) {
r.lock.Lock()
defer r.lock.Unlock()
return r.account(addr)
}
// Storage implements StateReader, retrieving the storage slot specified by the // Storage implements StateReader, retrieving the storage slot specified by the
// address and slot key. // address and slot key.
// //
// An error will be returned if the trie state is corrupted. An empty storage // An error will be returned if the trie state is corrupted. An empty storage
// slot will be returned if it's not existent in the trie. // slot will be returned if it's not existent in the trie.
func (r *trieReader) Storage(addr common.Address, key common.Hash) (common.Hash, error) { func (r *trieReader) Storage(addr common.Address, key common.Hash) (common.Hash, error) {
r.lock.Lock()
defer r.lock.Unlock()
var ( var (
tr Trie tr Trie
found bool found bool
@ -268,7 +291,7 @@ func (r *trieReader) Storage(addr common.Address, key common.Hash) (common.Hash,
// The storage slot is accessed without account caching. It's unexpected // The storage slot is accessed without account caching. It's unexpected
// behavior but try to resolve the account first anyway. // behavior but try to resolve the account first anyway.
if !ok { if !ok {
_, err := r.Account(addr) _, err := r.account(addr)
if err != nil { if err != nil {
return common.Hash{}, err return common.Hash{}, err
} }
@ -293,6 +316,9 @@ func (r *trieReader) Storage(addr common.Address, key common.Hash) (common.Hash,
// multiStateReader is the aggregation of a list of StateReader interface, // multiStateReader is the aggregation of a list of StateReader interface,
// providing state access by leveraging all readers. The checking priority // providing state access by leveraging all readers. The checking priority
// is determined by the position in the reader list. // is determined by the position in the reader list.
//
// multiStateReader is safe for concurrent read and assumes all underlying
// readers are thread-safe as well.
type multiStateReader struct { type multiStateReader struct {
readers []StateReader // List of state readers, sorted by checking priority readers []StateReader // List of state readers, sorted by checking priority
} }
@ -358,3 +384,95 @@ func newReader(codeReader ContractCodeReader, stateReader StateReader) *reader {
StateReader: stateReader, StateReader: stateReader,
} }
} }
// readerWithCache is a wrapper around Reader that maintains additional state caches
// to support concurrent state access.
type readerWithCache struct {
Reader // safe for concurrent read
// Previously resolved state entries.
accounts map[common.Address]*types.StateAccount
accountLock sync.RWMutex
// List of storage buckets, each of which is thread-safe.
// This reader is typically used in scenarios requiring concurrent
// access to storage. Using multiple buckets helps mitigate
// the overhead caused by locking.
storageBuckets [16]struct {
lock sync.RWMutex
storages map[common.Address]map[common.Hash]common.Hash
}
}
// newReaderWithCache constructs the reader with local cache.
func newReaderWithCache(reader Reader) *readerWithCache {
r := &readerWithCache{
Reader: reader,
accounts: make(map[common.Address]*types.StateAccount),
}
for i := range r.storageBuckets {
r.storageBuckets[i].storages = make(map[common.Address]map[common.Hash]common.Hash)
}
return r
}
// Account implements StateReader, retrieving the account specified by the address.
// The returned account might be nil if it's not existent.
//
// An error will be returned if the state is corrupted in the underlying reader.
func (r *readerWithCache) Account(addr common.Address) (*types.StateAccount, error) {
// Try to resolve the requested account in the local cache
r.accountLock.RLock()
acct, ok := r.accounts[addr]
r.accountLock.RUnlock()
if ok {
return acct, nil
}
// Try to resolve the requested account from the underlying reader
acct, err := r.Reader.Account(addr)
if err != nil {
return nil, err
}
r.accountLock.Lock()
r.accounts[addr] = acct
r.accountLock.Unlock()
return acct, nil
}
// Storage implements StateReader, retrieving the storage slot specified by the
// address and slot key. The returned storage slot might be empty if it's not
// existent.
//
// An error will be returned if the state is corrupted in the underlying reader.
func (r *readerWithCache) Storage(addr common.Address, slot common.Hash) (common.Hash, error) {
var (
value common.Hash
ok bool
bucket = &r.storageBuckets[addr[0]&0x0f]
)
// Try to resolve the requested storage slot in the local cache
bucket.lock.RLock()
slots, ok := bucket.storages[addr]
if ok {
value, ok = slots[slot]
}
bucket.lock.RUnlock()
if ok {
return value, nil
}
// Try to resolve the requested storage slot from the underlying reader
value, err := r.Reader.Storage(addr, slot)
if err != nil {
return common.Hash{}, err
}
bucket.lock.Lock()
slots, ok = bucket.storages[addr]
if !ok {
slots = make(map[common.Hash]common.Hash)
bucket.storages[addr] = slots
}
slots[slot] = value
bucket.lock.Unlock()
return value, nil
}

View file

@ -166,7 +166,9 @@ func newHelper(scheme string) *testHelper {
diskdb := rawdb.NewMemoryDatabase() diskdb := rawdb.NewMemoryDatabase()
config := &triedb.Config{} config := &triedb.Config{}
if scheme == rawdb.PathScheme { if scheme == rawdb.PathScheme {
config.PathDB = &pathdb.Config{} // disable caching config.PathDB = &pathdb.Config{
SnapshotNoBuild: true,
} // disable caching
} else { } else {
config.HashDB = &hashdb.Config{} // disable caching config.HashDB = &hashdb.Config{} // disable caching
} }

View file

@ -159,11 +159,17 @@ type StateDB struct {
// New creates a new state from a given trie. // New creates a new state from a given trie.
func New(root common.Hash, db Database) (*StateDB, error) { func New(root common.Hash, db Database) (*StateDB, error) {
tr, err := db.OpenTrie(root) reader, err := db.Reader(root)
if err != nil { if err != nil {
return nil, err return nil, err
} }
reader, err := db.Reader(root) return NewWithReader(root, db, reader)
}
// NewWithReader creates a new state for the specified state root. Unlike New,
// this function accepts an additional Reader which is bound to the given root.
func NewWithReader(root common.Hash, db Database, reader Reader) (*StateDB, error) {
tr, err := db.OpenTrie(root)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -292,7 +298,7 @@ func (s *StateDB) SubRefund(gas uint64) {
} }
// Exist reports whether the given account address exists in the state. // Exist reports whether the given account address exists in the state.
// Notably this also returns true for self-destructed accounts. // Notably this also returns true for self-destructed accounts within the current transaction.
func (s *StateDB) Exist(addr common.Address) bool { func (s *StateDB) Exist(addr common.Address) bool {
return s.getStateObject(addr) != nil return s.getStateObject(addr) != nil
} }
@ -392,6 +398,12 @@ func (s *StateDB) Database() Database {
return s.db return s.db
} }
// Reader retrieves the low level database reader supporting the
// lower level operations.
func (s *StateDB) Reader() Reader {
return s.reader
}
func (s *StateDB) HasSelfDestructed(addr common.Address) bool { func (s *StateDB) HasSelfDestructed(addr common.Address) bool {
stateObject := s.getStateObject(addr) stateObject := s.getStateObject(addr)
if stateObject != nil { if stateObject != nil {
@ -650,11 +662,10 @@ func (s *StateDB) CreateContract(addr common.Address) {
// Snapshots of the copied state cannot be applied to the copy. // Snapshots of the copied state cannot be applied to the copy.
func (s *StateDB) Copy() *StateDB { func (s *StateDB) Copy() *StateDB {
// Copy all the basic fields, initialize the memory ones // Copy all the basic fields, initialize the memory ones
reader, _ := s.db.Reader(s.originalRoot) // impossible to fail
state := &StateDB{ state := &StateDB{
db: s.db, db: s.db,
trie: mustCopyTrie(s.trie), trie: mustCopyTrie(s.trie),
reader: reader, reader: s.reader,
originalRoot: s.originalRoot, originalRoot: s.originalRoot,
stateObjects: make(map[common.Address]*stateObject, len(s.stateObjects)), stateObjects: make(map[common.Address]*stateObject, len(s.stateObjects)),
stateObjectsDestruct: make(map[common.Address]*stateObject, len(s.stateObjectsDestruct)), stateObjectsDestruct: make(map[common.Address]*stateObject, len(s.stateObjectsDestruct)),

View file

@ -979,7 +979,8 @@ func testMissingTrieNodes(t *testing.T, scheme string) {
) )
if scheme == rawdb.PathScheme { if scheme == rawdb.PathScheme {
tdb = triedb.NewDatabase(memDb, &triedb.Config{PathDB: &pathdb.Config{ tdb = triedb.NewDatabase(memDb, &triedb.Config{PathDB: &pathdb.Config{
CleanCacheSize: 0, TrieCleanSize: 0,
StateCleanSize: 0,
WriteBufferSize: 0, WriteBufferSize: 0,
}}) // disable caching }}) // disable caching
} else { } else {

View file

@ -17,17 +17,22 @@
package core package core
import ( import (
"bytes"
"runtime"
"sync/atomic" "sync/atomic"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/state" "github.com/ethereum/go-ethereum/core/state"
"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"
"golang.org/x/sync/errgroup"
) )
// statePrefetcher is a basic Prefetcher, which blindly executes a block on top // statePrefetcher is a basic Prefetcher that executes transactions from a block
// of an arbitrary state with the goal of prefetching potentially useful state // on top of the parent state, aiming to prefetch potentially useful state data
// data from disk before the main block processor start executing. // from disk. Transactions are executed in parallel to fully leverage the
// SSD's read performance.
type statePrefetcher struct { type statePrefetcher struct {
config *params.ChainConfig // Chain configuration options config *params.ChainConfig // Chain configuration options
chain *HeaderChain // Canonical block chain chain *HeaderChain // Canonical block chain
@ -43,41 +48,81 @@ func newStatePrefetcher(config *params.ChainConfig, chain *HeaderChain) *statePr
// Prefetch processes the state changes according to the Ethereum rules by running // Prefetch processes the state changes according to the Ethereum rules by running
// the transaction messages using the statedb, but any changes are discarded. The // the transaction messages using the statedb, but any changes are discarded. The
// only goal is to pre-cache transaction signatures and state trie nodes. // only goal is to warm the state caches.
func (p *statePrefetcher) Prefetch(block *types.Block, statedb *state.StateDB, cfg vm.Config, interrupt *atomic.Bool) { func (p *statePrefetcher) Prefetch(block *types.Block, statedb *state.StateDB, cfg vm.Config, interrupt *atomic.Bool) {
var ( var (
header = block.Header() fails atomic.Int64
gaspool = new(GasPool).AddGas(block.GasLimit()) header = block.Header()
blockContext = NewEVMBlockContext(header, p.chain, nil) signer = types.MakeSigner(p.config, header.Number, header.Time)
evm = vm.NewEVM(blockContext, statedb, p.config, cfg) workers errgroup.Group
signer = types.MakeSigner(p.config, header.Number, header.Time) reader = statedb.Reader()
) )
// Iterate over and process the individual transactions workers.SetLimit(runtime.NumCPU() / 2)
byzantium := p.config.IsByzantium(block.Number())
for i, tx := range block.Transactions() {
// If block precaching was interrupted, abort
if interrupt != nil && interrupt.Load() {
return
}
// Convert the transaction into an executable message and pre-cache its sender
msg, err := TransactionToMessage(tx, signer, header.BaseFee)
if err != nil {
return // Also invalid block, bail out
}
statedb.SetTxContext(tx.Hash(), i)
// We attempt to apply a transaction. The goal is not to execute // Iterate over and process the individual transactions
// the transaction successfully, rather to warm up touched data slots. for i, tx := range block.Transactions() {
if _, err := ApplyMessage(evm, msg, gaspool); err != nil { stateCpy := statedb.Copy() // closure
return // Ugh, something went horribly wrong, bail out workers.Go(func() error {
} // If block precaching was interrupted, abort
// If we're pre-byzantium, pre-load trie nodes for the intermediate root if interrupt != nil && interrupt.Load() {
if !byzantium { return nil
statedb.IntermediateRoot(true) }
} // Preload the touched accounts and storage slots in advance
} sender, err := types.Sender(signer, tx)
// If were post-byzantium, pre-load trie nodes for the final root hash if err != nil {
if byzantium { fails.Add(1)
statedb.IntermediateRoot(true) return nil
}
reader.Account(sender)
if tx.To() != nil {
account, _ := reader.Account(*tx.To())
// Preload the contract code if the destination has non-empty code
if account != nil && !bytes.Equal(account.CodeHash, types.EmptyCodeHash.Bytes()) {
reader.Code(*tx.To(), common.BytesToHash(account.CodeHash))
}
}
for _, list := range tx.AccessList() {
reader.Account(list.Address)
if len(list.StorageKeys) > 0 {
for _, slot := range list.StorageKeys {
reader.Storage(list.Address, slot)
}
}
}
// Execute the message to preload the implicit touched states
evm := vm.NewEVM(NewEVMBlockContext(header, p.chain, nil), stateCpy, p.config, cfg)
// Convert the transaction into an executable message and pre-cache its sender
msg, err := TransactionToMessage(tx, signer, header.BaseFee)
if err != nil {
fails.Add(1)
return nil // Also invalid block, bail out
}
// Disable the nonce check
msg.SkipNonceChecks = true
stateCpy.SetTxContext(tx.Hash(), i)
// We attempt to apply a transaction. The goal is not to execute
// the transaction successfully, rather to warm up touched data slots.
if _, err := ApplyMessage(evm, msg, new(GasPool).AddGas(block.GasLimit())); err != nil {
fails.Add(1)
return nil // Ugh, something went horribly wrong, bail out
}
// Pre-load trie nodes for the intermediate root.
//
// This operation incurs significant memory allocations due to
// trie hashing and node decoding. TODO(rjl493456442): investigate
// ways to mitigate this overhead.
stateCpy.IntermediateRoot(true)
return nil
})
} }
workers.Wait()
blockPrefetchTxsValidMeter.Mark(int64(len(block.Transactions())) - fails.Load())
blockPrefetchTxsInvalidMeter.Mark(fails.Load())
return
} }

View file

@ -113,9 +113,13 @@ func (p *StateProcessor) Process(block *types.Block, statedb *state.StateDB, cfg
return nil, err return nil, err
} }
// EIP-7002 // EIP-7002
ProcessWithdrawalQueue(&requests, evm) if err := ProcessWithdrawalQueue(&requests, evm); err != nil {
return nil, err
}
// EIP-7251 // EIP-7251
ProcessConsolidationQueue(&requests, evm) if err := ProcessConsolidationQueue(&requests, evm); err != nil {
return nil, err
}
} }
// Finalize the block, applying any consensus engine specific extras (e.g. block rewards) // Finalize the block, applying any consensus engine specific extras (e.g. block rewards)
@ -265,17 +269,17 @@ func ProcessParentBlockHash(prevHash common.Hash, evm *vm.EVM) {
// ProcessWithdrawalQueue calls the EIP-7002 withdrawal queue contract. // ProcessWithdrawalQueue calls the EIP-7002 withdrawal queue contract.
// It returns the opaque request data returned by the contract. // It returns the opaque request data returned by the contract.
func ProcessWithdrawalQueue(requests *[][]byte, evm *vm.EVM) { func ProcessWithdrawalQueue(requests *[][]byte, evm *vm.EVM) error {
processRequestsSystemCall(requests, evm, 0x01, params.WithdrawalQueueAddress) return processRequestsSystemCall(requests, evm, 0x01, params.WithdrawalQueueAddress)
} }
// ProcessConsolidationQueue calls the EIP-7251 consolidation queue contract. // ProcessConsolidationQueue calls the EIP-7251 consolidation queue contract.
// It returns the opaque request data returned by the contract. // It returns the opaque request data returned by the contract.
func ProcessConsolidationQueue(requests *[][]byte, evm *vm.EVM) { func ProcessConsolidationQueue(requests *[][]byte, evm *vm.EVM) error {
processRequestsSystemCall(requests, evm, 0x02, params.ConsolidationQueueAddress) return processRequestsSystemCall(requests, evm, 0x02, params.ConsolidationQueueAddress)
} }
func processRequestsSystemCall(requests *[][]byte, evm *vm.EVM, requestType byte, addr common.Address) { func processRequestsSystemCall(requests *[][]byte, evm *vm.EVM, requestType byte, addr common.Address) error {
if tracer := evm.Config.Tracer; tracer != nil { if tracer := evm.Config.Tracer; tracer != nil {
onSystemCallStart(tracer, evm.GetVMContext()) onSystemCallStart(tracer, evm.GetVMContext())
if tracer.OnSystemCallEnd != nil { if tracer.OnSystemCallEnd != nil {
@ -292,17 +296,20 @@ func processRequestsSystemCall(requests *[][]byte, evm *vm.EVM, requestType byte
} }
evm.SetTxContext(NewEVMTxContext(msg)) evm.SetTxContext(NewEVMTxContext(msg))
evm.StateDB.AddAddressToAccessList(addr) evm.StateDB.AddAddressToAccessList(addr)
ret, _, _ := evm.Call(msg.From, *msg.To, msg.Data, 30_000_000, common.U2560) ret, _, err := evm.Call(msg.From, *msg.To, msg.Data, 30_000_000, common.U2560)
evm.StateDB.Finalise(true) evm.StateDB.Finalise(true)
if len(ret) == 0 { if err != nil {
return // skip empty output return fmt.Errorf("system call failed to execute: %v", err)
}
if len(ret) == 0 {
return nil // skip empty output
} }
// Append prefixed requestsData to the requests list. // Append prefixed requestsData to the requests list.
requestsData := make([]byte, len(ret)+1) requestsData := make([]byte, len(ret)+1)
requestsData[0] = requestType requestsData[0] = requestType
copy(requestsData[1:], ret) copy(requestsData[1:], ret)
*requests = append(*requests, requestsData) *requests = append(*requests, requestsData)
return nil
} }
var depositTopic = common.HexToHash("0x649bbc62d0e31342afea4e5cd82d4049e7e1ee912fc0889aa790803be39038c5") var depositTopic = common.HexToHash("0x649bbc62d0e31342afea4e5cd82d4049e7e1ee912fc0889aa790803be39038c5")

View file

@ -255,7 +255,8 @@ func TestStateProcessorErrors(t *testing.T) {
}, },
want: "could not apply tx 0 [0xc18d10f4c809dbdfa1a074c3300de9bc4b7f16a20f0ec667f6f67312b71b956a]: EIP-7702 transaction with empty auth list (sender 0x71562b71999873DB5b286dF957af199Ec94617F7)", want: "could not apply tx 0 [0xc18d10f4c809dbdfa1a074c3300de9bc4b7f16a20f0ec667f6f67312b71b956a]: EIP-7702 transaction with empty auth list (sender 0x71562b71999873DB5b286dF957af199Ec94617F7)",
}, },
// ErrSetCodeTxCreate cannot be tested: it is impossible to create a SetCode-tx with nil `to`. // ErrSetCodeTxCreate cannot be tested here: it is impossible to create a SetCode-tx with nil `to`.
// The EstimateGas API tests test this case.
} { } {
block := GenerateBadBlock(gspec.ToBlock(), beacon.New(ethash.NewFaker()), tt.txs, gspec.Config, false) block := GenerateBadBlock(gspec.ToBlock(), beacon.New(ethash.NewFaker()), tt.txs, gspec.Config, false)
_, err := blockchain.InsertChain(types.Blocks{block}) _, err := blockchain.InsertChain(types.Blocks{block})

View file

@ -34,10 +34,10 @@ import (
// ExecutionResult includes all output after executing given evm // ExecutionResult includes all output after executing given evm
// message no matter the execution itself is successful or not. // message no matter the execution itself is successful or not.
type ExecutionResult struct { type ExecutionResult struct {
UsedGas uint64 // Total used gas, not including the refunded gas UsedGas uint64 // Total used gas, not including the refunded gas
RefundedGas uint64 // Total gas refunded after execution MaxUsedGas uint64 // Maximum gas consumed during execution, excluding gas refunds.
Err error // Any error encountered during the execution(listed in core/vm/errors.go) Err error // Any error encountered during the execution(listed in core/vm/errors.go)
ReturnData []byte // Returned data from evm(function result or data supplied with revert opcode) ReturnData []byte // Returned data from evm(function result or data supplied with revert opcode)
} }
// Unwrap returns the internal evm error which allows us for further // Unwrap returns the internal evm error which allows us for further
@ -159,7 +159,9 @@ type Message struct {
// When SkipNonceChecks is true, the message nonce is not checked against the // When SkipNonceChecks is true, the message nonce is not checked against the
// account nonce in state. // account nonce in state.
// This field will be set to true for operations like RPC eth_call. //
// This field will be set to true for operations like RPC eth_call
// or the state prefetching.
SkipNonceChecks bool SkipNonceChecks bool
// When SkipFromEOACheck is true, the message sender is not checked to be an EOA. // When SkipFromEOACheck is true, the message sender is not checked to be an EOA.
@ -455,7 +457,7 @@ func (st *stateTransition) execute() (*ExecutionResult, error) {
st.evm.AccessEvents.AddTxOrigin(msg.From) st.evm.AccessEvents.AddTxOrigin(msg.From)
if targetAddr := msg.To; targetAddr != nil { if targetAddr := msg.To; targetAddr != nil {
st.evm.AccessEvents.AddTxDestination(*targetAddr, msg.Value.Sign() != 0) st.evm.AccessEvents.AddTxDestination(*targetAddr, msg.Value.Sign() != 0, !st.state.Exist(*targetAddr))
} }
} }
@ -509,9 +511,12 @@ func (st *stateTransition) execute() (*ExecutionResult, error) {
ret, st.gasRemaining, vmerr = st.evm.Call(msg.From, st.to(), msg.Data, st.gasRemaining, value) ret, st.gasRemaining, vmerr = st.evm.Call(msg.From, st.to(), msg.Data, st.gasRemaining, value)
} }
// Record the gas used excluding gas refunds. This value represents the actual
// gas allowance required to complete execution.
peakGasUsed := st.gasUsed()
// Compute refund counter, capped to a refund quotient. // Compute refund counter, capped to a refund quotient.
gasRefund := st.calcRefund() st.gasRemaining += st.calcRefund()
st.gasRemaining += gasRefund
if rules.IsPrague { if rules.IsPrague {
// After EIP-7623: Data-heavy transactions pay the floor gas. // After EIP-7623: Data-heavy transactions pay the floor gas.
if st.gasUsed() < floorDataGas { if st.gasUsed() < floorDataGas {
@ -521,6 +526,9 @@ func (st *stateTransition) execute() (*ExecutionResult, error) {
t.OnGasChange(prev, st.gasRemaining, tracing.GasChangeTxDataFloor) t.OnGasChange(prev, st.gasRemaining, tracing.GasChangeTxDataFloor)
} }
} }
if peakGasUsed < floorDataGas {
peakGasUsed = floorDataGas
}
} }
st.returnGas() st.returnGas()
@ -544,15 +552,15 @@ func (st *stateTransition) execute() (*ExecutionResult, error) {
// add the coinbase to the witness iff the fee is greater than 0 // add the coinbase to the witness iff the fee is greater than 0
if rules.IsEIP4762 && fee.Sign() != 0 { if rules.IsEIP4762 && fee.Sign() != 0 {
st.evm.AccessEvents.AddAccount(st.evm.Context.Coinbase, true) st.evm.AccessEvents.AddAccount(st.evm.Context.Coinbase, true, math.MaxUint64)
} }
} }
return &ExecutionResult{ return &ExecutionResult{
UsedGas: st.gasUsed(), UsedGas: st.gasUsed(),
RefundedGas: gasRefund, MaxUsedGas: peakGasUsed,
Err: vmerr, Err: vmerr,
ReturnData: ret, ReturnData: ret,
}, nil }, nil
} }

View file

@ -17,8 +17,8 @@
package core package core
import ( import (
"errors"
"fmt" "fmt"
"sync/atomic"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/rawdb" "github.com/ethereum/go-ethereum/core/rawdb"
@ -47,26 +47,38 @@ type txIndexer struct {
// and all others shouldn't. // and all others shouldn't.
limit uint64 limit uint64
// The current head of blockchain for transaction indexing. This field
// is accessed by both the indexer and the indexing progress queries.
head atomic.Uint64
// The current tail of the indexed transactions, null indicates
// that no transactions have been indexed yet.
//
// This field is accessed by both the indexer and the indexing
// progress queries.
tail atomic.Pointer[uint64]
// cutoff denotes the block number before which the chain segment should // cutoff denotes the block number before which the chain segment should
// be pruned and not available locally. // be pruned and not available locally.
cutoff uint64 cutoff uint64
db ethdb.Database db ethdb.Database
progress chan chan TxIndexProgress term chan chan struct{}
term chan chan struct{} closed chan struct{}
closed chan struct{}
} }
// newTxIndexer initializes the transaction indexer. // newTxIndexer initializes the transaction indexer.
func newTxIndexer(limit uint64, chain *BlockChain) *txIndexer { func newTxIndexer(limit uint64, chain *BlockChain) *txIndexer {
cutoff, _ := chain.HistoryPruningCutoff() cutoff, _ := chain.HistoryPruningCutoff()
indexer := &txIndexer{ indexer := &txIndexer{
limit: limit, limit: limit,
cutoff: cutoff, cutoff: cutoff,
db: chain.db, db: chain.db,
progress: make(chan chan TxIndexProgress), term: make(chan chan struct{}),
term: make(chan chan struct{}), closed: make(chan struct{}),
closed: make(chan struct{}),
} }
indexer.head.Store(indexer.resolveHead())
indexer.tail.Store(rawdb.ReadTxIndexTail(chain.db))
go indexer.loop(chain) go indexer.loop(chain)
var msg string var msg string
@ -154,6 +166,7 @@ func (indexer *txIndexer) repair(head uint64) {
// A crash may occur between the two delete operations, // A crash may occur between the two delete operations,
// potentially leaving dangling indexes in the database. // potentially leaving dangling indexes in the database.
// However, this is considered acceptable. // However, this is considered acceptable.
indexer.tail.Store(nil)
rawdb.DeleteTxIndexTail(indexer.db) rawdb.DeleteTxIndexTail(indexer.db)
rawdb.DeleteAllTxLookupEntries(indexer.db, nil) rawdb.DeleteAllTxLookupEntries(indexer.db, nil)
log.Warn("Purge transaction indexes", "head", head, "tail", *tail) log.Warn("Purge transaction indexes", "head", head, "tail", *tail)
@ -174,6 +187,7 @@ func (indexer *txIndexer) repair(head uint64) {
// Traversing the database directly within the transaction // Traversing the database directly within the transaction
// index namespace might be slow and expensive, but we // index namespace might be slow and expensive, but we
// have no choice. // have no choice.
indexer.tail.Store(nil)
rawdb.DeleteTxIndexTail(indexer.db) rawdb.DeleteTxIndexTail(indexer.db)
rawdb.DeleteAllTxLookupEntries(indexer.db, nil) rawdb.DeleteAllTxLookupEntries(indexer.db, nil)
log.Warn("Purge transaction indexes", "head", head, "cutoff", indexer.cutoff) log.Warn("Purge transaction indexes", "head", head, "cutoff", indexer.cutoff)
@ -187,6 +201,7 @@ func (indexer *txIndexer) repair(head uint64) {
// A crash may occur between the two delete operations, // A crash may occur between the two delete operations,
// potentially leaving dangling indexes in the database. // potentially leaving dangling indexes in the database.
// However, this is considered acceptable. // However, this is considered acceptable.
indexer.tail.Store(&indexer.cutoff)
rawdb.WriteTxIndexTail(indexer.db, indexer.cutoff) rawdb.WriteTxIndexTail(indexer.db, indexer.cutoff)
rawdb.DeleteAllTxLookupEntries(indexer.db, func(txhash common.Hash, blob []byte) bool { rawdb.DeleteAllTxLookupEntries(indexer.db, func(txhash common.Hash, blob []byte) bool {
n := rawdb.DecodeTxLookupEntry(blob, indexer.db) n := rawdb.DecodeTxLookupEntry(blob, indexer.db)
@ -216,16 +231,15 @@ func (indexer *txIndexer) loop(chain *BlockChain) {
// Listening to chain events and manipulate the transaction indexes. // Listening to chain events and manipulate the transaction indexes.
var ( var (
stop chan struct{} // Non-nil if background routine is active stop chan struct{} // Non-nil if background routine is active
done chan struct{} // Non-nil if background routine is active done chan struct{} // Non-nil if background routine is active
head = indexer.resolveHead() // The latest announced chain head
headCh = make(chan ChainHeadEvent) headCh = make(chan ChainHeadEvent)
sub = chain.SubscribeChainHeadEvent(headCh) sub = chain.SubscribeChainHeadEvent(headCh)
) )
defer sub.Unsubscribe() defer sub.Unsubscribe()
// Validate the transaction indexes and repair if necessary // Validate the transaction indexes and repair if necessary
head := indexer.head.Load()
indexer.repair(head) indexer.repair(head)
// Launch the initial processing if chain is not empty (head != genesis). // Launch the initial processing if chain is not empty (head != genesis).
@ -238,17 +252,18 @@ func (indexer *txIndexer) loop(chain *BlockChain) {
for { for {
select { select {
case h := <-headCh: case h := <-headCh:
indexer.head.Store(h.Header.Number.Uint64())
if done == nil { if done == nil {
stop = make(chan struct{}) stop = make(chan struct{})
done = make(chan struct{}) done = make(chan struct{})
go indexer.run(h.Header.Number.Uint64(), stop, done) go indexer.run(h.Header.Number.Uint64(), stop, done)
} }
head = h.Header.Number.Uint64()
case <-done: case <-done:
stop = nil stop = nil
done = nil done = nil
case ch := <-indexer.progress: indexer.tail.Store(rawdb.ReadTxIndexTail(indexer.db))
ch <- indexer.report(head)
case ch := <-indexer.term: case ch := <-indexer.term:
if stop != nil { if stop != nil {
close(stop) close(stop)
@ -264,7 +279,7 @@ func (indexer *txIndexer) loop(chain *BlockChain) {
} }
// report returns the tx indexing progress. // report returns the tx indexing progress.
func (indexer *txIndexer) report(head uint64) TxIndexProgress { func (indexer *txIndexer) report(head uint64, tail *uint64) TxIndexProgress {
// Special case if the head is even below the cutoff, // Special case if the head is even below the cutoff,
// nothing to index. // nothing to index.
if head < indexer.cutoff { if head < indexer.cutoff {
@ -284,7 +299,6 @@ func (indexer *txIndexer) report(head uint64) TxIndexProgress {
} }
// Compute how many blocks have been indexed // Compute how many blocks have been indexed
var indexed uint64 var indexed uint64
tail := rawdb.ReadTxIndexTail(indexer.db)
if tail != nil { if tail != nil {
indexed = head - *tail + 1 indexed = head - *tail + 1
} }
@ -300,16 +314,12 @@ func (indexer *txIndexer) report(head uint64) TxIndexProgress {
} }
} }
// txIndexProgress retrieves the tx indexing progress, or an error if the // txIndexProgress retrieves the transaction indexing progress. The reported
// background tx indexer is already stopped. // progress may slightly lag behind the actual indexing state, as the tail is
func (indexer *txIndexer) txIndexProgress() (TxIndexProgress, error) { // only updated at the end of each indexing operation. However, this delay is
ch := make(chan TxIndexProgress, 1) // considered acceptable.
select { func (indexer *txIndexer) txIndexProgress() TxIndexProgress {
case indexer.progress <- ch: return indexer.report(indexer.head.Load(), indexer.tail.Load())
return <-ch, nil
case <-indexer.closed:
return TxIndexProgress{}, errors.New("indexer is closed")
}
} }
// close shutdown the indexer. Safe to be called for multiple times. // close shutdown the indexer. Safe to be called for multiple times.

View file

@ -117,13 +117,12 @@ func TestTxIndexer(t *testing.T) {
} }
for _, c := range cases { for _, c := range cases {
db, _ := rawdb.NewDatabaseWithFreezer(rawdb.NewMemoryDatabase(), "", "", false) db, _ := rawdb.NewDatabaseWithFreezer(rawdb.NewMemoryDatabase(), "", "", false)
rawdb.WriteAncientBlocks(db, append([]*types.Block{gspec.ToBlock()}, blocks...), append([]types.Receipts{{}}, receipts...)) rawdb.WriteAncientBlocks(db, append([]*types.Block{gspec.ToBlock()}, blocks...), types.EncodeBlockReceiptLists(append([]types.Receipts{{}}, receipts...)))
// Index the initial blocks from ancient store // Index the initial blocks from ancient store
indexer := &txIndexer{ indexer := &txIndexer{
limit: 0, limit: 0,
db: db, db: db,
progress: make(chan chan TxIndexProgress),
} }
for i, limit := range c.limits { for i, limit := range c.limits {
indexer.limit = limit indexer.limit = limit
@ -237,13 +236,13 @@ func TestTxIndexerRepair(t *testing.T) {
} }
for _, c := range cases { for _, c := range cases {
db, _ := rawdb.NewDatabaseWithFreezer(rawdb.NewMemoryDatabase(), "", "", false) db, _ := rawdb.NewDatabaseWithFreezer(rawdb.NewMemoryDatabase(), "", "", false)
rawdb.WriteAncientBlocks(db, append([]*types.Block{gspec.ToBlock()}, blocks...), append([]types.Receipts{{}}, receipts...)) encReceipts := types.EncodeBlockReceiptLists(append([]types.Receipts{{}}, receipts...))
rawdb.WriteAncientBlocks(db, append([]*types.Block{gspec.ToBlock()}, blocks...), encReceipts)
// Index the initial blocks from ancient store // Index the initial blocks from ancient store
indexer := &txIndexer{ indexer := &txIndexer{
limit: c.limit, limit: c.limit,
db: db, db: db,
progress: make(chan chan TxIndexProgress),
} }
indexer.run(chainHead, make(chan struct{}), make(chan struct{})) indexer.run(chainHead, make(chan struct{}), make(chan struct{}))
@ -428,19 +427,16 @@ func TestTxIndexerReport(t *testing.T) {
} }
for _, c := range cases { for _, c := range cases {
db, _ := rawdb.NewDatabaseWithFreezer(rawdb.NewMemoryDatabase(), "", "", false) db, _ := rawdb.NewDatabaseWithFreezer(rawdb.NewMemoryDatabase(), "", "", false)
rawdb.WriteAncientBlocks(db, append([]*types.Block{gspec.ToBlock()}, blocks...), append([]types.Receipts{{}}, receipts...)) encReceipts := types.EncodeBlockReceiptLists(append([]types.Receipts{{}}, receipts...))
rawdb.WriteAncientBlocks(db, append([]*types.Block{gspec.ToBlock()}, blocks...), encReceipts)
// Index the initial blocks from ancient store // Index the initial blocks from ancient store
indexer := &txIndexer{ indexer := &txIndexer{
limit: c.limit, limit: c.limit,
cutoff: c.cutoff, cutoff: c.cutoff,
db: db, db: db,
progress: make(chan chan TxIndexProgress),
} }
if c.tail != nil { p := indexer.report(c.head, c.tail)
rawdb.WriteTxIndexTail(db, *c.tail)
}
p := indexer.report(c.head)
if p.Indexed != c.expIndexed { if p.Indexed != c.expIndexed {
t.Fatalf("Unexpected indexed: %d, expected: %d", p.Indexed, c.expIndexed) t.Fatalf("Unexpected indexed: %d, expected: %d", p.Indexed, c.expIndexed)
} }

View file

@ -1494,22 +1494,22 @@ func (pool *LegacyPool) promoteExecutables(accounts []common.Address) []*types.T
// equal number for all for accounts with many pending transactions. // equal number for all for accounts with many pending transactions.
func (pool *LegacyPool) truncatePending() { func (pool *LegacyPool) truncatePending() {
pending := uint64(0) pending := uint64(0)
for _, list := range pool.pending {
pending += uint64(list.Len()) // Assemble a spam order to penalize large transactors first
spammers := prque.New[uint64, common.Address](nil)
for addr, list := range pool.pending {
// Only evict transactions from high rollers
length := uint64(list.Len())
pending += length
if length > pool.config.AccountSlots {
spammers.Push(addr, length)
}
} }
if pending <= pool.config.GlobalSlots { if pending <= pool.config.GlobalSlots {
return return
} }
pendingBeforeCap := pending pendingBeforeCap := pending
// Assemble a spam order to penalize large transactors first
spammers := prque.New[int64, common.Address](nil)
for addr, list := range pool.pending {
// Only evict transactions from high rollers
if uint64(list.Len()) > pool.config.AccountSlots {
spammers.Push(addr, int64(list.Len()))
}
}
// Gradually drop transactions from offenders // Gradually drop transactions from offenders
offenders := []common.Address{} offenders := []common.Address{}
for pending > pool.config.GlobalSlots && !spammers.Empty() { for pending > pool.config.GlobalSlots && !spammers.Empty() {
@ -1934,7 +1934,7 @@ func (pool *LegacyPool) Clear() {
pool.reserver.Release(addr) pool.reserver.Release(addr)
} }
pool.all.Clear() pool.all.Clear()
pool.priced = newPricedList(pool.all) pool.priced.Reheap()
pool.pending = make(map[common.Address]*list) pool.pending = make(map[common.Address]*list)
pool.queue = make(map[common.Address]*list) pool.queue = make(map[common.Address]*list)
pool.pendingNonces = newNoncer(pool.currentState) pool.pendingNonces = newNoncer(pool.currentState)

View file

@ -38,17 +38,13 @@ type Account struct {
Storage map[common.Hash]common.Hash `json:"storage,omitempty"` Storage map[common.Hash]common.Hash `json:"storage,omitempty"`
Balance *big.Int `json:"balance" gencodec:"required"` Balance *big.Int `json:"balance" gencodec:"required"`
Nonce uint64 `json:"nonce,omitempty"` Nonce uint64 `json:"nonce,omitempty"`
// used in tests
PrivateKey []byte `json:"secretKey,omitempty"`
} }
type accountMarshaling struct { type accountMarshaling struct {
Code hexutil.Bytes Code hexutil.Bytes
Balance *math.HexOrDecimal256 Balance *math.HexOrDecimal256
Nonce math.HexOrDecimal64 Nonce math.HexOrDecimal64
Storage map[storageJSON]storageJSON Storage map[storageJSON]storageJSON
PrivateKey hexutil.Bytes
} }
// storageJSON represents a 256 bit byte array, but allows less than 256 bits when // storageJSON represents a 256 bit byte array, but allows less than 256 bits when

View file

@ -59,11 +59,12 @@ func (b *Bloom) SetBytes(d []byte) {
// Add adds d to the filter. Future calls of Test(d) will return true. // Add adds d to the filter. Future calls of Test(d) will return true.
func (b *Bloom) Add(d []byte) { func (b *Bloom) Add(d []byte) {
b.add(d, make([]byte, 6)) var buf [6]byte
b.AddWithBuffer(d, &buf)
} }
// add is internal version of Add, which takes a scratch buffer for reuse (needs to be at least 6 bytes) // add is internal version of Add, which takes a scratch buffer for reuse (needs to be at least 6 bytes)
func (b *Bloom) add(d []byte, buf []byte) { func (b *Bloom) AddWithBuffer(d []byte, buf *[6]byte) {
i1, v1, i2, v2, i3, v3 := bloomValues(d, buf) i1, v1, i2, v2, i3, v3 := bloomValues(d, buf)
b[i1] |= v1 b[i1] |= v1
b[i2] |= v2 b[i2] |= v2
@ -84,7 +85,8 @@ func (b Bloom) Bytes() []byte {
// Test checks if the given topic is present in the bloom filter // Test checks if the given topic is present in the bloom filter
func (b Bloom) Test(topic []byte) bool { func (b Bloom) Test(topic []byte) bool {
i1, v1, i2, v2, i3, v3 := bloomValues(topic, make([]byte, 6)) var buf [6]byte
i1, v1, i2, v2, i3, v3 := bloomValues(topic, &buf)
return v1 == v1&b[i1] && return v1 == v1&b[i1] &&
v2 == v2&b[i2] && v2 == v2&b[i2] &&
v3 == v3&b[i3] v3 == v3&b[i3]
@ -104,12 +106,12 @@ func (b *Bloom) UnmarshalText(input []byte) error {
func CreateBloom(receipt *Receipt) Bloom { func CreateBloom(receipt *Receipt) Bloom {
var ( var (
bin Bloom bin Bloom
buf = make([]byte, 6) buf [6]byte
) )
for _, log := range receipt.Logs { for _, log := range receipt.Logs {
bin.add(log.Address.Bytes(), buf) bin.AddWithBuffer(log.Address.Bytes(), &buf)
for _, b := range log.Topics { for _, b := range log.Topics {
bin.add(b[:], buf) bin.AddWithBuffer(b[:], &buf)
} }
} }
return bin return bin
@ -139,21 +141,20 @@ func Bloom9(data []byte) []byte {
} }
// bloomValues returns the bytes (index-value pairs) to set for the given data // bloomValues returns the bytes (index-value pairs) to set for the given data
func bloomValues(data []byte, hashbuf []byte) (uint, byte, uint, byte, uint, byte) { func bloomValues(data []byte, hashbuf *[6]byte) (uint, byte, uint, byte, uint, byte) {
sha := hasherPool.Get().(crypto.KeccakState) sha := hasherPool.Get().(crypto.KeccakState)
sha.Reset() sha.Reset()
sha.Write(data) sha.Write(data)
sha.Read(hashbuf) sha.Read(hashbuf[:])
hasherPool.Put(sha) hasherPool.Put(sha)
// The actual bits to flip // The actual bits to flip
v1 := byte(1 << (hashbuf[1] & 0x7)) v1 := byte(1 << (hashbuf[1] & 0x7))
v2 := byte(1 << (hashbuf[3] & 0x7)) v2 := byte(1 << (hashbuf[3] & 0x7))
v3 := byte(1 << (hashbuf[5] & 0x7)) v3 := byte(1 << (hashbuf[5] & 0x7))
// The indices for the bytes to OR in // The indices for the bytes to OR in
i1 := BloomByteLength - uint((binary.BigEndian.Uint16(hashbuf)&0x7ff)>>3) - 1 i1 := BloomByteLength - uint((binary.BigEndian.Uint16(hashbuf[0:])&0x7ff)>>3) - 1
i2 := BloomByteLength - uint((binary.BigEndian.Uint16(hashbuf[2:])&0x7ff)>>3) - 1 i2 := BloomByteLength - uint((binary.BigEndian.Uint16(hashbuf[2:])&0x7ff)>>3) - 1
i3 := BloomByteLength - uint((binary.BigEndian.Uint16(hashbuf[4:])&0x7ff)>>3) - 1 i3 := BloomByteLength - uint((binary.BigEndian.Uint16(hashbuf[4:])&0x7ff)>>3) - 1
return i1, v1, i2, v2, i3, v3 return i1, v1, i2, v2, i3, v3
} }

View file

@ -17,11 +17,10 @@ var _ = (*accountMarshaling)(nil)
// MarshalJSON marshals as JSON. // MarshalJSON marshals as JSON.
func (a Account) MarshalJSON() ([]byte, error) { func (a Account) MarshalJSON() ([]byte, error) {
type Account struct { type Account struct {
Code hexutil.Bytes `json:"code,omitempty"` Code hexutil.Bytes `json:"code,omitempty"`
Storage map[storageJSON]storageJSON `json:"storage,omitempty"` Storage map[storageJSON]storageJSON `json:"storage,omitempty"`
Balance *math.HexOrDecimal256 `json:"balance" gencodec:"required"` Balance *math.HexOrDecimal256 `json:"balance" gencodec:"required"`
Nonce math.HexOrDecimal64 `json:"nonce,omitempty"` Nonce math.HexOrDecimal64 `json:"nonce,omitempty"`
PrivateKey hexutil.Bytes `json:"secretKey,omitempty"`
} }
var enc Account var enc Account
enc.Code = a.Code enc.Code = a.Code
@ -33,18 +32,16 @@ func (a Account) MarshalJSON() ([]byte, error) {
} }
enc.Balance = (*math.HexOrDecimal256)(a.Balance) enc.Balance = (*math.HexOrDecimal256)(a.Balance)
enc.Nonce = math.HexOrDecimal64(a.Nonce) enc.Nonce = math.HexOrDecimal64(a.Nonce)
enc.PrivateKey = a.PrivateKey
return json.Marshal(&enc) return json.Marshal(&enc)
} }
// UnmarshalJSON unmarshals from JSON. // UnmarshalJSON unmarshals from JSON.
func (a *Account) UnmarshalJSON(input []byte) error { func (a *Account) UnmarshalJSON(input []byte) error {
type Account struct { type Account struct {
Code *hexutil.Bytes `json:"code,omitempty"` Code *hexutil.Bytes `json:"code,omitempty"`
Storage map[storageJSON]storageJSON `json:"storage,omitempty"` Storage map[storageJSON]storageJSON `json:"storage,omitempty"`
Balance *math.HexOrDecimal256 `json:"balance" gencodec:"required"` Balance *math.HexOrDecimal256 `json:"balance" gencodec:"required"`
Nonce *math.HexOrDecimal64 `json:"nonce,omitempty"` Nonce *math.HexOrDecimal64 `json:"nonce,omitempty"`
PrivateKey *hexutil.Bytes `json:"secretKey,omitempty"`
} }
var dec Account var dec Account
if err := json.Unmarshal(input, &dec); err != nil { if err := json.Unmarshal(input, &dec); err != nil {
@ -66,8 +63,5 @@ func (a *Account) UnmarshalJSON(input []byte) error {
if dec.Nonce != nil { if dec.Nonce != nil {
a.Nonce = uint64(*dec.Nonce) a.Nonce = uint64(*dec.Nonce)
} }
if dec.PrivateKey != nil {
a.PrivateKey = *dec.PrivateKey
}
return nil return nil
} }

View file

@ -25,12 +25,11 @@ import (
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/rlp" "github.com/ethereum/go-ethereum/rlp"
"golang.org/x/crypto/sha3"
) )
// hasherPool holds LegacyKeccak256 hashers for rlpHash. // hasherPool holds LegacyKeccak256 hashers for rlpHash.
var hasherPool = sync.Pool{ var hasherPool = sync.Pool{
New: func() interface{} { return sha3.NewLegacyKeccak256() }, New: func() interface{} { return crypto.NewKeccakState() },
} }
// encodeBufferPool holds temporary encoder buffers for DeriveSha and TX encoding. // encodeBufferPool holds temporary encoder buffers for DeriveSha and TX encoding.

View file

@ -27,6 +27,7 @@ import (
"github.com/ethereum/go-ethereum/common" "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/crypto" "github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/log"
"github.com/ethereum/go-ethereum/params" "github.com/ethereum/go-ethereum/params"
"github.com/ethereum/go-ethereum/rlp" "github.com/ethereum/go-ethereum/rlp"
) )
@ -258,7 +259,7 @@ func (r *Receipt) Size() common.StorageSize {
} }
// ReceiptForStorage is a wrapper around a Receipt with RLP serialization // ReceiptForStorage is a wrapper around a Receipt with RLP serialization
// that omits the Bloom field and deserialization that re-computes it. // that omits the Bloom field. The Bloom field is recomputed by DeriveFields.
type ReceiptForStorage Receipt type ReceiptForStorage Receipt
// EncodeRLP implements rlp.Encoder, and flattens all content fields of a receipt // EncodeRLP implements rlp.Encoder, and flattens all content fields of a receipt
@ -291,7 +292,6 @@ func (r *ReceiptForStorage) DecodeRLP(s *rlp.Stream) error {
} }
r.CumulativeGasUsed = stored.CumulativeGasUsed r.CumulativeGasUsed = stored.CumulativeGasUsed
r.Logs = stored.Logs r.Logs = stored.Logs
r.Bloom = CreateBloom((*Receipt)(r))
return nil return nil
} }
@ -372,6 +372,26 @@ func (rs Receipts) DeriveFields(config *params.ChainConfig, hash common.Hash, nu
rs[i].Logs[j].Index = logIndex rs[i].Logs[j].Index = logIndex
logIndex++ logIndex++
} }
// also derive the Bloom if not derived yet
rs[i].Bloom = CreateBloom(rs[i])
} }
return nil return nil
} }
// EncodeBlockReceiptLists encodes a list of block receipt lists into RLP.
func EncodeBlockReceiptLists(receipts []Receipts) []rlp.RawValue {
var storageReceipts []*ReceiptForStorage
result := make([]rlp.RawValue, len(receipts))
for i, receipt := range receipts {
storageReceipts = storageReceipts[:0]
for _, r := range receipt {
storageReceipts = append(storageReceipts, (*ReceiptForStorage)(r))
}
bytes, err := rlp.EncodeToBytes(storageReceipts)
if err != nil {
log.Crit("Failed to encode block receipts", "err", err)
}
result[i] = bytes
}
return result
}

View file

@ -22,6 +22,7 @@ import (
"math" "math"
"math/big" "math/big"
"reflect" "reflect"
"sync"
"testing" "testing"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
@ -154,148 +155,161 @@ var (
blockNumber = big.NewInt(1) blockNumber = big.NewInt(1)
blockTime = uint64(2) blockTime = uint64(2)
blockHash = common.BytesToHash([]byte{0x03, 0x14}) blockHash = common.BytesToHash([]byte{0x03, 0x14})
// Create the corresponding receipts
receipts = Receipts{
&Receipt{
Status: ReceiptStatusFailed,
CumulativeGasUsed: 1,
Logs: []*Log{
{
Address: common.BytesToAddress([]byte{0x11}),
Topics: []common.Hash{common.HexToHash("dead"), common.HexToHash("beef")},
// derived fields:
BlockNumber: blockNumber.Uint64(),
TxHash: txs[0].Hash(),
TxIndex: 0,
BlockHash: blockHash,
Index: 0,
},
{
Address: common.BytesToAddress([]byte{0x01, 0x11}),
Topics: []common.Hash{common.HexToHash("dead"), common.HexToHash("beef")},
// derived fields:
BlockNumber: blockNumber.Uint64(),
TxHash: txs[0].Hash(),
TxIndex: 0,
BlockHash: blockHash,
Index: 1,
},
},
// derived fields:
TxHash: txs[0].Hash(),
ContractAddress: common.HexToAddress("0x5a443704dd4b594b382c22a083e2bd3090a6fef3"),
GasUsed: 1,
EffectiveGasPrice: big.NewInt(11),
BlockHash: blockHash,
BlockNumber: blockNumber,
TransactionIndex: 0,
},
&Receipt{
PostState: common.Hash{2}.Bytes(),
CumulativeGasUsed: 3,
Logs: []*Log{
{
Address: common.BytesToAddress([]byte{0x22}),
Topics: []common.Hash{common.HexToHash("dead"), common.HexToHash("beef")},
// derived fields:
BlockNumber: blockNumber.Uint64(),
TxHash: txs[1].Hash(),
TxIndex: 1,
BlockHash: blockHash,
Index: 2,
},
{
Address: common.BytesToAddress([]byte{0x02, 0x22}),
Topics: []common.Hash{common.HexToHash("dead"), common.HexToHash("beef")},
// derived fields:
BlockNumber: blockNumber.Uint64(),
TxHash: txs[1].Hash(),
TxIndex: 1,
BlockHash: blockHash,
Index: 3,
},
},
// derived fields:
TxHash: txs[1].Hash(),
GasUsed: 2,
EffectiveGasPrice: big.NewInt(22),
BlockHash: blockHash,
BlockNumber: blockNumber,
TransactionIndex: 1,
},
&Receipt{
Type: AccessListTxType,
PostState: common.Hash{3}.Bytes(),
CumulativeGasUsed: 6,
Logs: []*Log{},
// derived fields:
TxHash: txs[2].Hash(),
GasUsed: 3,
EffectiveGasPrice: big.NewInt(33),
BlockHash: blockHash,
BlockNumber: blockNumber,
TransactionIndex: 2,
},
&Receipt{
Type: DynamicFeeTxType,
PostState: common.Hash{4}.Bytes(),
CumulativeGasUsed: 10,
Logs: []*Log{},
// derived fields:
TxHash: txs[3].Hash(),
GasUsed: 4,
EffectiveGasPrice: big.NewInt(1044),
BlockHash: blockHash,
BlockNumber: blockNumber,
TransactionIndex: 3,
},
&Receipt{
Type: DynamicFeeTxType,
PostState: common.Hash{5}.Bytes(),
CumulativeGasUsed: 15,
Logs: []*Log{},
// derived fields:
TxHash: txs[4].Hash(),
GasUsed: 5,
EffectiveGasPrice: big.NewInt(1055),
BlockHash: blockHash,
BlockNumber: blockNumber,
TransactionIndex: 4,
},
&Receipt{
Type: BlobTxType,
PostState: common.Hash{6}.Bytes(),
CumulativeGasUsed: 21,
Logs: []*Log{},
// derived fields:
TxHash: txs[5].Hash(),
GasUsed: 6,
EffectiveGasPrice: big.NewInt(1066),
BlobGasUsed: params.BlobTxBlobGasPerBlob,
BlobGasPrice: big.NewInt(920),
BlockHash: blockHash,
BlockNumber: blockNumber,
TransactionIndex: 5,
},
&Receipt{
Type: BlobTxType,
PostState: common.Hash{7}.Bytes(),
CumulativeGasUsed: 28,
Logs: []*Log{},
// derived fields:
TxHash: txs[6].Hash(),
GasUsed: 7,
EffectiveGasPrice: big.NewInt(1077),
BlobGasUsed: 3 * params.BlobTxBlobGasPerBlob,
BlobGasPrice: big.NewInt(920),
BlockHash: blockHash,
BlockNumber: blockNumber,
TransactionIndex: 6,
},
}
) )
var receiptsOnce sync.Once
var testReceipts Receipts
func getTestReceipts() Receipts {
// Compute the blooms only once
receiptsOnce.Do(func() {
// Create the corresponding receipts
r := Receipts{
&Receipt{
Status: ReceiptStatusFailed,
CumulativeGasUsed: 1,
Logs: []*Log{
{
Address: common.BytesToAddress([]byte{0x11}),
Topics: []common.Hash{common.HexToHash("dead"), common.HexToHash("beef")},
// derived fields:
BlockNumber: blockNumber.Uint64(),
TxHash: txs[0].Hash(),
TxIndex: 0,
BlockHash: blockHash,
Index: 0,
},
{
Address: common.BytesToAddress([]byte{0x01, 0x11}),
Topics: []common.Hash{common.HexToHash("dead"), common.HexToHash("beef")},
// derived fields:
BlockNumber: blockNumber.Uint64(),
TxHash: txs[0].Hash(),
TxIndex: 0,
BlockHash: blockHash,
Index: 1,
},
},
// derived fields:
TxHash: txs[0].Hash(),
ContractAddress: common.HexToAddress("0x5a443704dd4b594b382c22a083e2bd3090a6fef3"),
GasUsed: 1,
EffectiveGasPrice: big.NewInt(11),
BlockHash: blockHash,
BlockNumber: blockNumber,
TransactionIndex: 0,
},
&Receipt{
PostState: common.Hash{2}.Bytes(),
CumulativeGasUsed: 3,
Logs: []*Log{
{
Address: common.BytesToAddress([]byte{0x22}),
Topics: []common.Hash{common.HexToHash("dead"), common.HexToHash("beef")},
// derived fields:
BlockNumber: blockNumber.Uint64(),
TxHash: txs[1].Hash(),
TxIndex: 1,
BlockHash: blockHash,
Index: 2,
},
{
Address: common.BytesToAddress([]byte{0x02, 0x22}),
Topics: []common.Hash{common.HexToHash("dead"), common.HexToHash("beef")},
// derived fields:
BlockNumber: blockNumber.Uint64(),
TxHash: txs[1].Hash(),
TxIndex: 1,
BlockHash: blockHash,
Index: 3,
},
},
// derived fields:
TxHash: txs[1].Hash(),
GasUsed: 2,
EffectiveGasPrice: big.NewInt(22),
BlockHash: blockHash,
BlockNumber: blockNumber,
TransactionIndex: 1,
},
&Receipt{
Type: AccessListTxType,
PostState: common.Hash{3}.Bytes(),
CumulativeGasUsed: 6,
Logs: []*Log{},
// derived fields:
TxHash: txs[2].Hash(),
GasUsed: 3,
EffectiveGasPrice: big.NewInt(33),
BlockHash: blockHash,
BlockNumber: blockNumber,
TransactionIndex: 2,
},
&Receipt{
Type: DynamicFeeTxType,
PostState: common.Hash{4}.Bytes(),
CumulativeGasUsed: 10,
Logs: []*Log{},
// derived fields:
TxHash: txs[3].Hash(),
GasUsed: 4,
EffectiveGasPrice: big.NewInt(1044),
BlockHash: blockHash,
BlockNumber: blockNumber,
TransactionIndex: 3,
},
&Receipt{
Type: DynamicFeeTxType,
PostState: common.Hash{5}.Bytes(),
CumulativeGasUsed: 15,
Logs: []*Log{},
// derived fields:
TxHash: txs[4].Hash(),
GasUsed: 5,
EffectiveGasPrice: big.NewInt(1055),
BlockHash: blockHash,
BlockNumber: blockNumber,
TransactionIndex: 4,
},
&Receipt{
Type: BlobTxType,
PostState: common.Hash{6}.Bytes(),
CumulativeGasUsed: 21,
Logs: []*Log{},
// derived fields:
TxHash: txs[5].Hash(),
GasUsed: 6,
EffectiveGasPrice: big.NewInt(1066),
BlobGasUsed: params.BlobTxBlobGasPerBlob,
BlobGasPrice: big.NewInt(920),
BlockHash: blockHash,
BlockNumber: blockNumber,
TransactionIndex: 5,
},
&Receipt{
Type: BlobTxType,
PostState: common.Hash{7}.Bytes(),
CumulativeGasUsed: 28,
Logs: []*Log{},
// derived fields:
TxHash: txs[6].Hash(),
GasUsed: 7,
EffectiveGasPrice: big.NewInt(1077),
BlobGasUsed: 3 * params.BlobTxBlobGasPerBlob,
BlobGasPrice: big.NewInt(920),
BlockHash: blockHash,
BlockNumber: blockNumber,
TransactionIndex: 6,
},
}
for _, receipt := range r {
receipt.Bloom = CreateBloom(receipt)
}
testReceipts = r
})
return testReceipts
}
func TestDecodeEmptyTypedReceipt(t *testing.T) { func TestDecodeEmptyTypedReceipt(t *testing.T) {
input := []byte{0x80} input := []byte{0x80}
var r Receipt var r Receipt
@ -310,6 +324,7 @@ func TestDeriveFields(t *testing.T) {
// Re-derive receipts. // Re-derive receipts.
basefee := big.NewInt(1000) basefee := big.NewInt(1000)
blobGasPrice := big.NewInt(920) blobGasPrice := big.NewInt(920)
receipts := getTestReceipts()
derivedReceipts := clearComputedFieldsOnReceipts(receipts) derivedReceipts := clearComputedFieldsOnReceipts(receipts)
err := Receipts(derivedReceipts).DeriveFields(params.TestChainConfig, blockHash, blockNumber.Uint64(), blockTime, basefee, blobGasPrice, txs) err := Receipts(derivedReceipts).DeriveFields(params.TestChainConfig, blockHash, blockNumber.Uint64(), blockTime, basefee, blobGasPrice, txs)
if err != nil { if err != nil {
@ -335,6 +350,7 @@ func TestDeriveFields(t *testing.T) {
// Test that we can marshal/unmarshal receipts to/from json without errors. // Test that we can marshal/unmarshal receipts to/from json without errors.
// This also confirms that our test receipts contain all the required fields. // This also confirms that our test receipts contain all the required fields.
func TestReceiptJSON(t *testing.T) { func TestReceiptJSON(t *testing.T) {
receipts := getTestReceipts()
for i := range receipts { for i := range receipts {
b, err := receipts[i].MarshalJSON() b, err := receipts[i].MarshalJSON()
if err != nil { if err != nil {
@ -351,6 +367,7 @@ func TestReceiptJSON(t *testing.T) {
// Test we can still parse receipt without EffectiveGasPrice for backwards compatibility, even // Test we can still parse receipt without EffectiveGasPrice for backwards compatibility, even
// though it is required per the spec. // though it is required per the spec.
func TestEffectiveGasPriceNotRequired(t *testing.T) { func TestEffectiveGasPriceNotRequired(t *testing.T) {
receipts := getTestReceipts()
r := *receipts[0] r := *receipts[0]
r.EffectiveGasPrice = nil r.EffectiveGasPrice = nil
b, err := r.MarshalJSON() b, err := r.MarshalJSON()
@ -511,6 +528,7 @@ func clearComputedFieldsOnReceipt(receipt *Receipt) *Receipt {
cpy.EffectiveGasPrice = big.NewInt(0) cpy.EffectiveGasPrice = big.NewInt(0)
cpy.BlobGasUsed = 0 cpy.BlobGasUsed = 0
cpy.BlobGasPrice = nil cpy.BlobGasPrice = nil
cpy.Bloom = CreateBloom(&cpy)
return &cpy return &cpy
} }

View file

@ -355,28 +355,31 @@ func (tx *Transaction) GasTipCapIntCmp(other *big.Int) int {
// Note: if the effective gasTipCap is negative, this method returns both error // Note: if the effective gasTipCap is negative, this method returns both error
// the actual negative value, _and_ ErrGasFeeCapTooLow // the actual negative value, _and_ ErrGasFeeCapTooLow
func (tx *Transaction) EffectiveGasTip(baseFee *big.Int) (*big.Int, error) { func (tx *Transaction) EffectiveGasTip(baseFee *big.Int) (*big.Int, error) {
dst := new(big.Int)
err := tx.calcEffectiveGasTip(dst, baseFee)
return dst, err
}
// calcEffectiveGasTip calculates the effective gas tip of the transaction and
// saves the result to dst.
func (tx *Transaction) calcEffectiveGasTip(dst *big.Int, baseFee *big.Int) error {
if baseFee == nil { if baseFee == nil {
return tx.GasTipCap(), nil dst.Set(tx.inner.gasTipCap())
return nil
} }
var err error var err error
gasFeeCap := tx.GasFeeCap() gasFeeCap := tx.inner.gasFeeCap()
if gasFeeCap.Cmp(baseFee) < 0 { if gasFeeCap.Cmp(baseFee) < 0 {
err = ErrGasFeeCapTooLow err = ErrGasFeeCapTooLow
} }
gasFeeCap = gasFeeCap.Sub(gasFeeCap, baseFee)
gasTipCap := tx.GasTipCap() dst.Sub(gasFeeCap, baseFee)
if gasTipCap.Cmp(gasFeeCap) < 0 { gasTipCap := tx.inner.gasTipCap()
return gasTipCap, err if gasTipCap.Cmp(dst) < 0 {
dst.Set(gasTipCap)
} }
return gasFeeCap, err return err
}
// EffectiveGasTipValue is identical to EffectiveGasTip, but does not return an
// error in case the effective gasTipCap is negative
func (tx *Transaction) EffectiveGasTipValue(baseFee *big.Int) *big.Int {
effectiveTip, _ := tx.EffectiveGasTip(baseFee)
return effectiveTip
} }
// EffectiveGasTipCmp compares the effective gasTipCap of two transactions assuming the given base fee. // EffectiveGasTipCmp compares the effective gasTipCap of two transactions assuming the given base fee.
@ -384,7 +387,11 @@ func (tx *Transaction) EffectiveGasTipCmp(other *Transaction, baseFee *big.Int)
if baseFee == nil { if baseFee == nil {
return tx.GasTipCapCmp(other) return tx.GasTipCapCmp(other)
} }
return tx.EffectiveGasTipValue(baseFee).Cmp(other.EffectiveGasTipValue(baseFee)) // Use more efficient internal method.
txTip, otherTip := new(big.Int), new(big.Int)
tx.calcEffectiveGasTip(txTip, baseFee)
other.calcEffectiveGasTip(otherTip, baseFee)
return txTip.Cmp(otherTip)
} }
// EffectiveGasTipIntCmp compares the effective gasTipCap of a transaction to the given gasTipCap. // EffectiveGasTipIntCmp compares the effective gasTipCap of a transaction to the given gasTipCap.
@ -392,7 +399,9 @@ func (tx *Transaction) EffectiveGasTipIntCmp(other *big.Int, baseFee *big.Int) i
if baseFee == nil { if baseFee == nil {
return tx.GasTipCapIntCmp(other) return tx.GasTipCapIntCmp(other)
} }
return tx.EffectiveGasTipValue(baseFee).Cmp(other) txTip := new(big.Int)
tx.calcEffectiveGasTip(txTip, baseFee)
return txTip.Cmp(other)
} }
// BlobGas returns the blob gas limit of the transaction for blob transactions, 0 otherwise. // BlobGas returns the blob gas limit of the transaction for blob transactions, 0 otherwise.

View file

@ -29,6 +29,7 @@ import (
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/params"
"github.com/ethereum/go-ethereum/rlp" "github.com/ethereum/go-ethereum/rlp"
) )
@ -593,3 +594,101 @@ func BenchmarkHash(b *testing.B) {
signer.Hash(tx) signer.Hash(tx)
} }
} }
func BenchmarkEffectiveGasTip(b *testing.B) {
signer := LatestSigner(params.TestChainConfig)
key, _ := crypto.GenerateKey()
txdata := &DynamicFeeTx{
ChainID: big.NewInt(1),
Nonce: 0,
GasTipCap: big.NewInt(2000000000),
GasFeeCap: big.NewInt(3000000000),
Gas: 21000,
To: &common.Address{},
Value: big.NewInt(0),
Data: nil,
}
tx, _ := SignNewTx(key, signer, txdata)
baseFee := big.NewInt(1000000000) // 1 gwei
b.Run("Original", func(b *testing.B) {
b.ReportAllocs()
for i := 0; i < b.N; i++ {
_, err := tx.EffectiveGasTip(baseFee)
if err != nil {
b.Fatal(err)
}
}
})
b.Run("IntoMethod", func(b *testing.B) {
b.ReportAllocs()
dst := new(big.Int)
for i := 0; i < b.N; i++ {
err := tx.calcEffectiveGasTip(dst, baseFee)
if err != nil {
b.Fatal(err)
}
}
})
}
func TestEffectiveGasTipInto(t *testing.T) {
signer := LatestSigner(params.TestChainConfig)
key, _ := crypto.GenerateKey()
testCases := []struct {
tipCap int64
feeCap int64
baseFee *int64
}{
{tipCap: 1, feeCap: 100, baseFee: intPtr(50)},
{tipCap: 10, feeCap: 100, baseFee: intPtr(50)},
{tipCap: 50, feeCap: 100, baseFee: intPtr(50)},
{tipCap: 100, feeCap: 100, baseFee: intPtr(50)},
{tipCap: 1, feeCap: 50, baseFee: intPtr(50)},
{tipCap: 1, feeCap: 20, baseFee: intPtr(50)}, // Base fee higher than fee cap
{tipCap: 50, feeCap: 100, baseFee: intPtr(0)},
{tipCap: 50, feeCap: 100, baseFee: nil}, // nil base fee
}
for i, tc := range testCases {
txdata := &DynamicFeeTx{
ChainID: big.NewInt(1),
Nonce: 0,
GasTipCap: big.NewInt(tc.tipCap),
GasFeeCap: big.NewInt(tc.feeCap),
Gas: 21000,
To: &common.Address{},
Value: big.NewInt(0),
Data: nil,
}
tx, _ := SignNewTx(key, signer, txdata)
var baseFee *big.Int
if tc.baseFee != nil {
baseFee = big.NewInt(*tc.baseFee)
}
// Get result from original method
orig, origErr := tx.EffectiveGasTip(baseFee)
// Get result from new method
dst := new(big.Int)
newErr := tx.calcEffectiveGasTip(dst, baseFee)
// Compare results
if (origErr != nil) != (newErr != nil) {
t.Fatalf("case %d: error mismatch: orig %v, new %v", i, origErr, newErr)
}
if orig.Cmp(dst) != 0 {
t.Fatalf("case %d: result mismatch: orig %v, new %v", i, orig, dst)
}
}
}
// Helper function to create integer pointer
func intPtr(i int64) *int64 {
return &i
}

View file

@ -139,7 +139,7 @@ var PrecompiledContractsPrague = PrecompiledContracts{
var PrecompiledContractsBLS = PrecompiledContractsPrague var PrecompiledContractsBLS = PrecompiledContractsPrague
var PrecompiledContractsVerkle = PrecompiledContractsPrague var PrecompiledContractsVerkle = PrecompiledContractsBerlin
var ( var (
PrecompiledAddressesPrague []common.Address PrecompiledAddressesPrague []common.Address

View file

@ -339,12 +339,10 @@ func opExtCodeCopyEIP4762(pc *uint64, interpreter *EVMInterpreter, scope *ScopeC
addr := common.Address(a.Bytes20()) addr := common.Address(a.Bytes20())
code := interpreter.evm.StateDB.GetCode(addr) code := interpreter.evm.StateDB.GetCode(addr)
paddedCodeCopy, copyOffset, nonPaddedCopyLength := getDataAndAdjustedBounds(code, uint64CodeOffset, length.Uint64()) paddedCodeCopy, copyOffset, nonPaddedCopyLength := getDataAndAdjustedBounds(code, uint64CodeOffset, length.Uint64())
if !scope.Contract.IsSystemCall { consumed, wanted := interpreter.evm.AccessEvents.CodeChunksRangeGas(addr, copyOffset, nonPaddedCopyLength, uint64(len(code)), false, scope.Contract.Gas)
statelessGas := interpreter.evm.AccessEvents.CodeChunksRangeGas(addr, copyOffset, nonPaddedCopyLength, uint64(len(code)), false) scope.Contract.UseGas(consumed, interpreter.evm.Config.Tracer, tracing.GasChangeUnspecified)
if !scope.Contract.UseGas(statelessGas, interpreter.evm.Config.Tracer, tracing.GasChangeUnspecified) { if consumed < wanted {
scope.Contract.Gas = 0 return nil, ErrOutOfGas
return nil, ErrOutOfGas
}
} }
scope.Memory.Set(memOffset.Uint64(), length.Uint64(), paddedCodeCopy) scope.Memory.Set(memOffset.Uint64(), length.Uint64(), paddedCodeCopy)
@ -367,9 +365,9 @@ func opPush1EIP4762(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext
// touch next chunk if PUSH1 is at the boundary. if so, *pc has // touch next chunk if PUSH1 is at the boundary. if so, *pc has
// advanced past this boundary. // advanced past this boundary.
contractAddr := scope.Contract.Address() contractAddr := scope.Contract.Address()
statelessGas := interpreter.evm.AccessEvents.CodeChunksRangeGas(contractAddr, *pc+1, uint64(1), uint64(len(scope.Contract.Code)), false) consumed, wanted := interpreter.evm.AccessEvents.CodeChunksRangeGas(contractAddr, *pc+1, uint64(1), uint64(len(scope.Contract.Code)), false, scope.Contract.Gas)
if !scope.Contract.UseGas(statelessGas, interpreter.evm.Config.Tracer, tracing.GasChangeUnspecified) { scope.Contract.UseGas(wanted, interpreter.evm.Config.Tracer, tracing.GasChangeUnspecified)
scope.Contract.Gas = 0 if consumed < wanted {
return nil, ErrOutOfGas return nil, ErrOutOfGas
} }
} }
@ -395,9 +393,9 @@ func makePushEIP4762(size uint64, pushByteSize int) executionFunc {
if !scope.Contract.IsDeployment && !scope.Contract.IsSystemCall { if !scope.Contract.IsDeployment && !scope.Contract.IsSystemCall {
contractAddr := scope.Contract.Address() contractAddr := scope.Contract.Address()
statelessGas := interpreter.evm.AccessEvents.CodeChunksRangeGas(contractAddr, uint64(start), uint64(pushByteSize), uint64(len(scope.Contract.Code)), false) consumed, wanted := interpreter.evm.AccessEvents.CodeChunksRangeGas(contractAddr, uint64(start), uint64(pushByteSize), uint64(len(scope.Contract.Code)), false, scope.Contract.Gas)
if !scope.Contract.UseGas(statelessGas, interpreter.evm.Config.Tracer, tracing.GasChangeUnspecified) { scope.Contract.UseGas(consumed, interpreter.evm.Config.Tracer, tracing.GasChangeUnspecified)
scope.Contract.Gas = 0 if consumed < wanted {
return nil, ErrOutOfGas return nil, ErrOutOfGas
} }
} }

View file

@ -206,8 +206,14 @@ func (evm *EVM) Call(caller common.Address, addr common.Address, input []byte, g
if !evm.StateDB.Exist(addr) { if !evm.StateDB.Exist(addr) {
if !isPrecompile && evm.chainRules.IsEIP4762 && !isSystemCall(caller) { if !isPrecompile && evm.chainRules.IsEIP4762 && !isSystemCall(caller) {
// add proof of absence to witness // Add proof of absence to witness
wgas := evm.AccessEvents.AddAccount(addr, false) // At this point, the read costs have already been charged, either because this
// is a direct tx call, in which case it's covered by the intrinsic gas, or because
// of a CALL instruction, in which case BASIC_DATA has been added to the access
// list in write mode. If there is enough gas paying for the addition of the code
// hash leaf to the access list, then account creation will proceed unimpaired.
// Thus, only pay for the creation of the code hash leaf here.
wgas := evm.AccessEvents.CodeHashGas(addr, true, gas, false)
if gas < wgas { if gas < wgas {
evm.StateDB.RevertToSnapshot(snapshot) evm.StateDB.RevertToSnapshot(snapshot)
return nil, 0, ErrOutOfGas return nil, 0, ErrOutOfGas
@ -433,7 +439,7 @@ func (evm *EVM) create(caller common.Address, code []byte, gas uint64, value *ui
// Charge the contract creation init gas in verkle mode // Charge the contract creation init gas in verkle mode
if evm.chainRules.IsEIP4762 { if evm.chainRules.IsEIP4762 {
statelessGas := evm.AccessEvents.ContractCreatePreCheckGas(address) statelessGas := evm.AccessEvents.ContractCreatePreCheckGas(address, gas)
if statelessGas > gas { if statelessGas > gas {
return nil, common.Address{}, 0, ErrOutOfGas return nil, common.Address{}, 0, ErrOutOfGas
} }
@ -481,14 +487,14 @@ func (evm *EVM) create(caller common.Address, code []byte, gas uint64, value *ui
} }
// Charge the contract creation init gas in verkle mode // Charge the contract creation init gas in verkle mode
if evm.chainRules.IsEIP4762 { if evm.chainRules.IsEIP4762 {
statelessGas := evm.AccessEvents.ContractCreateInitGas(address) consumed, wanted := evm.AccessEvents.ContractCreateInitGas(address, gas)
if statelessGas > gas { if consumed < wanted {
return nil, common.Address{}, 0, ErrOutOfGas return nil, common.Address{}, 0, ErrOutOfGas
} }
if evm.Config.Tracer != nil && evm.Config.Tracer.OnGasChange != nil { if evm.Config.Tracer != nil && evm.Config.Tracer.OnGasChange != nil {
evm.Config.Tracer.OnGasChange(gas, gas-statelessGas, tracing.GasChangeWitnessContractInit) evm.Config.Tracer.OnGasChange(gas, gas-consumed, tracing.GasChangeWitnessContractInit)
} }
gas = gas - statelessGas gas = gas - consumed
} }
evm.Context.Transfer(evm.StateDB, caller, address, value) evm.Context.Transfer(evm.StateDB, caller, address, value)
@ -535,7 +541,9 @@ func (evm *EVM) initNewContract(contract *Contract, address common.Address) ([]b
return ret, ErrCodeStoreOutOfGas return ret, ErrCodeStoreOutOfGas
} }
} else { } else {
if len(ret) > 0 && !contract.UseGas(evm.AccessEvents.CodeChunksRangeGas(address, 0, uint64(len(ret)), uint64(len(ret)), true), evm.Config.Tracer, tracing.GasChangeWitnessCodeChunk) { consumed, wanted := evm.AccessEvents.CodeChunksRangeGas(address, 0, uint64(len(ret)), uint64(len(ret)), true, contract.Gas)
contract.UseGas(consumed, evm.Config.Tracer, tracing.GasChangeWitnessCodeChunk)
if len(ret) > 0 && (consumed < wanted) {
return ret, ErrCodeStoreOutOfGas return ret, ErrCodeStoreOutOfGas
} }
} }
@ -555,7 +563,8 @@ func (evm *EVM) Create(caller common.Address, code []byte, gas uint64, value *ui
// The different between Create2 with Create is Create2 uses keccak256(0xff ++ msg.sender ++ salt ++ keccak256(init_code))[12:] // The different between Create2 with Create is Create2 uses keccak256(0xff ++ msg.sender ++ salt ++ keccak256(init_code))[12:]
// instead of the usual sender-and-nonce-hash as the address where the contract is initialized at. // instead of the usual sender-and-nonce-hash as the address where the contract is initialized at.
func (evm *EVM) Create2(caller common.Address, code []byte, gas uint64, endowment *uint256.Int, salt *uint256.Int) (ret []byte, contractAddr common.Address, leftOverGas uint64, err error) { func (evm *EVM) Create2(caller common.Address, code []byte, gas uint64, endowment *uint256.Int, salt *uint256.Int) (ret []byte, contractAddr common.Address, leftOverGas uint64, err error) {
contractAddr = crypto.CreateAddress2(caller, salt.Bytes32(), crypto.Keccak256(code)) inithash := crypto.HashData(evm.interpreter.hasher, code)
contractAddr = crypto.CreateAddress2(caller, salt.Bytes32(), inithash[:])
return evm.create(caller, code, gas, endowment, contractAddr, CREATE2) return evm.create(caller, code, gas, endowment, contractAddr, CREATE2)
} }

View file

@ -394,14 +394,7 @@ func gasCall(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize
if gas, overflow = math.SafeAdd(gas, memoryGas); overflow { if gas, overflow = math.SafeAdd(gas, memoryGas); overflow {
return 0, ErrGasUintOverflow return 0, ErrGasUintOverflow
} }
if evm.chainRules.IsEIP4762 && !contract.IsSystemCall {
if transfersValue {
gas, overflow = math.SafeAdd(gas, evm.AccessEvents.ValueTransferGas(contract.Address(), address))
if overflow {
return 0, ErrGasUintOverflow
}
}
}
evm.callGasTemp, err = callGas(evm.chainRules.IsEIP150, contract.Gas, gas, stack.Back(0)) evm.callGasTemp, err = callGas(evm.chainRules.IsEIP150, contract.Gas, gas, stack.Back(0))
if err != nil { if err != nil {
return 0, err return 0, err
@ -428,16 +421,6 @@ func gasCallCode(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memory
if gas, overflow = math.SafeAdd(gas, memoryGas); overflow { if gas, overflow = math.SafeAdd(gas, memoryGas); overflow {
return 0, ErrGasUintOverflow return 0, ErrGasUintOverflow
} }
if evm.chainRules.IsEIP4762 && !contract.IsSystemCall {
address := common.Address(stack.Back(1).Bytes20())
transfersValue := !stack.Back(2).IsZero()
if transfersValue {
gas, overflow = math.SafeAdd(gas, evm.AccessEvents.ValueTransferGas(contract.Address(), address))
if overflow {
return 0, ErrGasUintOverflow
}
}
}
evm.callGasTemp, err = callGas(evm.chainRules.IsEIP150, contract.Gas, gas, stack.Back(0)) evm.callGasTemp, err = callGas(evm.chainRules.IsEIP150, contract.Gas, gas, stack.Back(0))
if err != nil { if err != nil {
return 0, err return 0, err

View file

@ -22,7 +22,6 @@ import (
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/tracing" "github.com/ethereum/go-ethereum/core/tracing"
"github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/params" "github.com/ethereum/go-ethereum/params"
"github.com/holiman/uint256" "github.com/holiman/uint256"
) )
@ -234,11 +233,7 @@ func opKeccak256(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) (
offset, size := scope.Stack.pop(), scope.Stack.peek() offset, size := scope.Stack.pop(), scope.Stack.peek()
data := scope.Memory.GetPtr(offset.Uint64(), size.Uint64()) data := scope.Memory.GetPtr(offset.Uint64(), size.Uint64())
if interpreter.hasher == nil { interpreter.hasher.Reset()
interpreter.hasher = crypto.NewKeccakState()
} else {
interpreter.hasher.Reset()
}
interpreter.hasher.Write(data) interpreter.hasher.Write(data)
interpreter.hasher.Read(interpreter.hasherBuf[:]) interpreter.hasher.Read(interpreter.hasherBuf[:])

View file

@ -69,7 +69,7 @@ type StateDB interface {
SelfDestruct6780(common.Address) (uint256.Int, bool) SelfDestruct6780(common.Address) (uint256.Int, bool)
// Exist reports whether the given account exists in state. // Exist reports whether the given account exists in state.
// Notably this should also return true for self-destructed accounts. // Notably this also returns true for self-destructed accounts within the current transaction.
Exist(common.Address) bool Exist(common.Address) bool
// Empty returns whether the given account is empty. Empty // Empty returns whether the given account is empty. Empty
// is defined according to EIP161 (balance = nonce = code = 0). // is defined according to EIP161 (balance = nonce = code = 0).

View file

@ -150,7 +150,7 @@ func NewEVMInterpreter(evm *EVM) *EVMInterpreter {
} }
} }
evm.Config.ExtraEips = extraEips evm.Config.ExtraEips = extraEips
return &EVMInterpreter{evm: evm, table: table} return &EVMInterpreter{evm: evm, table: table, hasher: crypto.NewKeccakState()}
} }
// Run loops and evaluates the contract's code with the given input data and returns // Run loops and evaluates the contract's code with the given input data and returns
@ -237,7 +237,11 @@ func (in *EVMInterpreter) Run(contract *Contract, input []byte, readOnly bool) (
// if the PC ends up in a new "chunk" of verkleized code, charge the // if the PC ends up in a new "chunk" of verkleized code, charge the
// associated costs. // associated costs.
contractAddr := contract.Address() contractAddr := contract.Address()
contract.Gas -= in.evm.TxContext.AccessEvents.CodeChunksRangeGas(contractAddr, pc, 1, uint64(len(contract.Code)), false) consumed, wanted := in.evm.TxContext.AccessEvents.CodeChunksRangeGas(contractAddr, pc, 1, uint64(len(contract.Code)), false, contract.Gas)
contract.UseGas(consumed, in.evm.Config.Tracer, tracing.GasChangeWitnessCodeChunk)
if consumed < wanted {
return nil, ErrOutOfGas
}
} }
// Get the operation from the jump table and validate the stack to ensure there are // Get the operation from the jump table and validate the stack to ensure there are

View file

@ -87,7 +87,7 @@ func validate(jt JumpTable) JumpTable {
} }
func newVerkleInstructionSet() JumpTable { func newVerkleInstructionSet() JumpTable {
instructionSet := newCancunInstructionSet() instructionSet := newShanghaiInstructionSet()
enable4762(&instructionSet) enable4762(&instructionSet)
return validate(instructionSet) return validate(instructionSet)
} }

View file

@ -25,31 +25,16 @@ import (
) )
func gasSStore4762(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) { func gasSStore4762(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) {
gas := evm.AccessEvents.SlotGas(contract.Address(), stack.peek().Bytes32(), true) return evm.AccessEvents.SlotGas(contract.Address(), stack.peek().Bytes32(), true, contract.Gas, true), nil
if gas == 0 {
gas = params.WarmStorageReadCostEIP2929
}
return gas, nil
} }
func gasSLoad4762(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) { func gasSLoad4762(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) {
gas := evm.AccessEvents.SlotGas(contract.Address(), stack.peek().Bytes32(), false) return evm.AccessEvents.SlotGas(contract.Address(), stack.peek().Bytes32(), false, contract.Gas, true), nil
if gas == 0 {
gas = params.WarmStorageReadCostEIP2929
}
return gas, nil
} }
func gasBalance4762(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) { func gasBalance4762(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) {
if contract.IsSystemCall {
return 0, nil
}
address := stack.peek().Bytes20() address := stack.peek().Bytes20()
gas := evm.AccessEvents.BasicDataGas(address, false) return evm.AccessEvents.BasicDataGas(address, false, contract.Gas, true), nil
if gas == 0 {
gas = params.WarmStorageReadCostEIP2929
}
return gas, nil
} }
func gasExtCodeSize4762(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) { func gasExtCodeSize4762(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) {
@ -57,56 +42,69 @@ func gasExtCodeSize4762(evm *EVM, contract *Contract, stack *Stack, mem *Memory,
if _, isPrecompile := evm.precompile(address); isPrecompile { if _, isPrecompile := evm.precompile(address); isPrecompile {
return 0, nil return 0, nil
} }
if contract.IsSystemCall { return evm.AccessEvents.BasicDataGas(address, false, contract.Gas, true), nil
return 0, nil
}
gas := evm.AccessEvents.BasicDataGas(address, false)
if gas == 0 {
gas = params.WarmStorageReadCostEIP2929
}
return gas, nil
} }
func gasExtCodeHash4762(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) { func gasExtCodeHash4762(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) {
if contract.IsSystemCall {
return 0, nil
}
address := stack.peek().Bytes20() address := stack.peek().Bytes20()
if _, isPrecompile := evm.precompile(address); isPrecompile { if _, isPrecompile := evm.precompile(address); isPrecompile {
return 0, nil return 0, nil
} }
gas := evm.AccessEvents.CodeHashGas(address, false) return evm.AccessEvents.CodeHashGas(address, false, contract.Gas, true), nil
if gas == 0 {
gas = params.WarmStorageReadCostEIP2929
}
return gas, nil
} }
func makeCallVariantGasEIP4762(oldCalculator gasFunc) gasFunc { func makeCallVariantGasEIP4762(oldCalculator gasFunc, withTransferCosts bool) gasFunc {
return func(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) { return func(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) {
gas, err := oldCalculator(evm, contract, stack, mem, memorySize) var (
if err != nil { target = common.Address(stack.Back(1).Bytes20())
return 0, err witnessGas uint64
} _, isPrecompile = evm.precompile(target)
if contract.IsSystemCall { isSystemContract = target == params.HistoryStorageAddress
return gas, nil )
}
if _, isPrecompile := evm.precompile(contract.Address()); isPrecompile { // If value is transferred, it is charged before 1/64th
return gas, nil // is subtracted from the available gas pool.
} if withTransferCosts && !stack.Back(2).IsZero() {
witnessGas := evm.AccessEvents.MessageCallGas(contract.Address()) wantedValueTransferWitnessGas := evm.AccessEvents.ValueTransferGas(contract.Address(), target, contract.Gas)
if witnessGas == 0 { if wantedValueTransferWitnessGas > contract.Gas {
return wantedValueTransferWitnessGas, nil
}
witnessGas = wantedValueTransferWitnessGas
} else if isPrecompile || isSystemContract {
witnessGas = params.WarmStorageReadCostEIP2929 witnessGas = params.WarmStorageReadCostEIP2929
} else {
// The charging for the value transfer is done BEFORE subtracting
// the 1/64th gas, as this is considered part of the CALL instruction.
// (so before we get to this point)
// But the message call is part of the subcall, for which only 63/64th
// of the gas should be available.
wantedMessageCallWitnessGas := evm.AccessEvents.MessageCallGas(target, contract.Gas-witnessGas)
var overflow bool
if witnessGas, overflow = math.SafeAdd(witnessGas, wantedMessageCallWitnessGas); overflow {
return 0, ErrGasUintOverflow
}
if witnessGas > contract.Gas {
return witnessGas, nil
}
} }
return witnessGas + gas, nil
contract.Gas -= witnessGas
// if the operation fails, adds witness gas to the gas before returning the error
gas, err := oldCalculator(evm, contract, stack, mem, memorySize)
contract.Gas += witnessGas // restore witness gas so that it can be charged at the callsite
var overflow bool
if gas, overflow = math.SafeAdd(gas, witnessGas); overflow {
return 0, ErrGasUintOverflow
}
return gas, err
} }
} }
var ( var (
gasCallEIP4762 = makeCallVariantGasEIP4762(gasCall) gasCallEIP4762 = makeCallVariantGasEIP4762(gasCall, true)
gasCallCodeEIP4762 = makeCallVariantGasEIP4762(gasCallCode) gasCallCodeEIP4762 = makeCallVariantGasEIP4762(gasCallCode, false)
gasStaticCallEIP4762 = makeCallVariantGasEIP4762(gasStaticCall) gasStaticCallEIP4762 = makeCallVariantGasEIP4762(gasStaticCall, false)
gasDelegateCallEIP4762 = makeCallVariantGasEIP4762(gasDelegateCall) gasDelegateCallEIP4762 = makeCallVariantGasEIP4762(gasDelegateCall, false)
) )
func gasSelfdestructEIP4762(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) { func gasSelfdestructEIP4762(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) {
@ -118,15 +116,44 @@ func gasSelfdestructEIP4762(evm *EVM, contract *Contract, stack *Stack, mem *Mem
return 0, nil return 0, nil
} }
contractAddr := contract.Address() contractAddr := contract.Address()
statelessGas := evm.AccessEvents.BasicDataGas(contractAddr, false) wanted := evm.AccessEvents.BasicDataGas(contractAddr, false, contract.Gas, false)
if wanted > contract.Gas {
return wanted, nil
}
statelessGas := wanted
balanceIsZero := evm.StateDB.GetBalance(contractAddr).Sign() == 0
_, isPrecompile := evm.precompile(beneficiaryAddr)
isSystemContract := beneficiaryAddr == params.HistoryStorageAddress
if (isPrecompile || isSystemContract) && balanceIsZero {
return statelessGas, nil
}
if contractAddr != beneficiaryAddr { if contractAddr != beneficiaryAddr {
statelessGas += evm.AccessEvents.BasicDataGas(beneficiaryAddr, false) wanted := evm.AccessEvents.BasicDataGas(beneficiaryAddr, false, contract.Gas-statelessGas, false)
if wanted > contract.Gas-statelessGas {
return statelessGas + wanted, nil
}
statelessGas += wanted
} }
// Charge write costs if it transfers value // Charge write costs if it transfers value
if evm.StateDB.GetBalance(contractAddr).Sign() != 0 { if !balanceIsZero {
statelessGas += evm.AccessEvents.BasicDataGas(contractAddr, true) wanted := evm.AccessEvents.BasicDataGas(contractAddr, true, contract.Gas-statelessGas, false)
if wanted > contract.Gas-statelessGas {
return statelessGas + wanted, nil
}
statelessGas += wanted
if contractAddr != beneficiaryAddr { if contractAddr != beneficiaryAddr {
statelessGas += evm.AccessEvents.BasicDataGas(beneficiaryAddr, true) if evm.StateDB.Exist(beneficiaryAddr) {
wanted = evm.AccessEvents.BasicDataGas(beneficiaryAddr, true, contract.Gas-statelessGas, false)
} else {
wanted = evm.AccessEvents.AddAccount(beneficiaryAddr, true, contract.Gas-statelessGas)
}
if wanted > contract.Gas-statelessGas {
return statelessGas + wanted, nil
}
statelessGas += wanted
} }
} }
return statelessGas, nil return statelessGas, nil
@ -137,17 +164,19 @@ func gasCodeCopyEip4762(evm *EVM, contract *Contract, stack *Stack, mem *Memory,
if err != nil { if err != nil {
return 0, err return 0, err
} }
var (
codeOffset = stack.Back(1)
length = stack.Back(2)
)
uint64CodeOffset, overflow := codeOffset.Uint64WithOverflow()
if overflow {
uint64CodeOffset = gomath.MaxUint64
}
_, copyOffset, nonPaddedCopyLength := getDataAndAdjustedBounds(contract.Code, uint64CodeOffset, length.Uint64())
if !contract.IsDeployment && !contract.IsSystemCall { if !contract.IsDeployment && !contract.IsSystemCall {
gas += evm.AccessEvents.CodeChunksRangeGas(contract.Address(), copyOffset, nonPaddedCopyLength, uint64(len(contract.Code)), false) var (
codeOffset = stack.Back(1)
length = stack.Back(2)
)
uint64CodeOffset, overflow := codeOffset.Uint64WithOverflow()
if overflow {
uint64CodeOffset = gomath.MaxUint64
}
_, copyOffset, nonPaddedCopyLength := getDataAndAdjustedBounds(contract.Code, uint64CodeOffset, length.Uint64())
_, wanted := evm.AccessEvents.CodeChunksRangeGas(contract.Address(), copyOffset, nonPaddedCopyLength, uint64(len(contract.Code)), false, contract.Gas-gas)
gas += wanted
} }
return gas, nil return gas, nil
} }
@ -158,16 +187,17 @@ func gasExtCodeCopyEIP4762(evm *EVM, contract *Contract, stack *Stack, mem *Memo
if err != nil { if err != nil {
return 0, err return 0, err
} }
if contract.IsSystemCall { addr := common.Address(stack.peek().Bytes20())
_, isPrecompile := evm.precompile(addr)
if isPrecompile || addr == params.HistoryStorageAddress {
var overflow bool
if gas, overflow = math.SafeAdd(gas, params.WarmStorageReadCostEIP2929); overflow {
return 0, ErrGasUintOverflow
}
return gas, nil return gas, nil
} }
addr := common.Address(stack.peek().Bytes20()) wgas := evm.AccessEvents.BasicDataGas(addr, false, contract.Gas-gas, true)
wgas := evm.AccessEvents.BasicDataGas(addr, false)
if wgas == 0 {
wgas = params.WarmStorageReadCostEIP2929
}
var overflow bool var overflow bool
// We charge (cold-warm), since 'warm' is already charged as constantGas
if gas, overflow = math.SafeAdd(gas, wgas); overflow { if gas, overflow = math.SafeAdd(gas, wgas); overflow {
return 0, ErrGasUintOverflow return 0, ErrGasUintOverflow
} }

View file

@ -28,6 +28,7 @@ import (
"io" "io"
"math/big" "math/big"
"os" "os"
"sync"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/math" "github.com/ethereum/go-ethereum/common/math"
@ -73,6 +74,12 @@ func NewKeccakState() KeccakState {
return sha3.NewLegacyKeccak256().(KeccakState) return sha3.NewLegacyKeccak256().(KeccakState)
} }
var hasherPool = sync.Pool{
New: func() any {
return sha3.NewLegacyKeccak256().(KeccakState)
},
}
// HashData hashes the provided data using the KeccakState and returns a 32 byte hash // HashData hashes the provided data using the KeccakState and returns a 32 byte hash
func HashData(kh KeccakState, data []byte) (h common.Hash) { func HashData(kh KeccakState, data []byte) (h common.Hash) {
kh.Reset() kh.Reset()
@ -84,22 +91,26 @@ func HashData(kh KeccakState, data []byte) (h common.Hash) {
// Keccak256 calculates and returns the Keccak256 hash of the input data. // Keccak256 calculates and returns the Keccak256 hash of the input data.
func Keccak256(data ...[]byte) []byte { func Keccak256(data ...[]byte) []byte {
b := make([]byte, 32) b := make([]byte, 32)
d := NewKeccakState() d := hasherPool.Get().(KeccakState)
d.Reset()
for _, b := range data { for _, b := range data {
d.Write(b) d.Write(b)
} }
d.Read(b) d.Read(b)
hasherPool.Put(d)
return b return b
} }
// Keccak256Hash calculates and returns the Keccak256 hash of the input data, // Keccak256Hash calculates and returns the Keccak256 hash of the input data,
// converting it to an internal Hash data structure. // converting it to an internal Hash data structure.
func Keccak256Hash(data ...[]byte) (h common.Hash) { func Keccak256Hash(data ...[]byte) (h common.Hash) {
d := NewKeccakState() d := hasherPool.Get().(KeccakState)
d.Reset()
for _, b := range data { for _, b := range data {
d.Write(b) d.Write(b)
} }
d.Read(h[:]) d.Read(h[:])
hasherPool.Put(d)
return h return h
} }

View file

@ -149,6 +149,17 @@ func VerifyBlobProof(blob *Blob, commitment Commitment, proof Proof) error {
return gokzgVerifyBlobProof(blob, commitment, proof) return gokzgVerifyBlobProof(blob, commitment, proof)
} }
// ComputeCellProofs returns the KZG cell proofs that are used to verify the blob against
// the commitment.
//
// This method does not verify that the commitment is correct with respect to blob.
func ComputeCellProofs(blob *Blob) ([]Proof, error) {
if useCKZG.Load() {
return ckzgComputeCellProofs(blob)
}
return gokzgComputeCellProofs(blob)
}
// CalcBlobHashV1 calculates the 'versioned blob hash' of a commitment. // CalcBlobHashV1 calculates the 'versioned blob hash' of a commitment.
// The given hasher must be a sha256 hash instance, otherwise the result will be invalid! // The given hasher must be a sha256 hash instance, otherwise the result will be invalid!
func CalcBlobHashV1(hasher hash.Hash, commit *Commitment) (vh [32]byte) { func CalcBlobHashV1(hasher hash.Hash, commit *Commitment) (vh [32]byte) {

View file

@ -23,8 +23,8 @@ import (
"errors" "errors"
"sync" "sync"
gokzg4844 "github.com/crate-crypto/go-kzg-4844" gokzg4844 "github.com/crate-crypto/go-eth-kzg"
ckzg4844 "github.com/ethereum/c-kzg-4844/bindings/go" ckzg4844 "github.com/ethereum/c-kzg-4844/v2/bindings/go"
"github.com/ethereum/go-ethereum/common/hexutil" "github.com/ethereum/go-ethereum/common/hexutil"
) )
@ -47,15 +47,21 @@ func ckzgInit() {
if err = gokzg4844.CheckTrustedSetupIsWellFormed(params); err != nil { if err = gokzg4844.CheckTrustedSetupIsWellFormed(params); err != nil {
panic(err) panic(err)
} }
g1s := make([]byte, len(params.SetupG1Lagrange)*(len(params.SetupG1Lagrange[0])-2)/2) g1Lag := make([]byte, len(params.SetupG1Lagrange)*(len(params.SetupG1Lagrange[0])-2)/2)
for i, g1 := range params.SetupG1Lagrange { for i, g1 := range params.SetupG1Lagrange {
copy(g1Lag[i*(len(g1)-2)/2:], hexutil.MustDecode(g1))
}
g1s := make([]byte, len(params.SetupG1Monomial)*(len(params.SetupG1Monomial[0])-2)/2)
for i, g1 := range params.SetupG1Monomial {
copy(g1s[i*(len(g1)-2)/2:], hexutil.MustDecode(g1)) copy(g1s[i*(len(g1)-2)/2:], hexutil.MustDecode(g1))
} }
g2s := make([]byte, len(params.SetupG2)*(len(params.SetupG2[0])-2)/2) g2s := make([]byte, len(params.SetupG2)*(len(params.SetupG2[0])-2)/2)
for i, g2 := range params.SetupG2 { for i, g2 := range params.SetupG2 {
copy(g2s[i*(len(g2)-2)/2:], hexutil.MustDecode(g2)) copy(g2s[i*(len(g2)-2)/2:], hexutil.MustDecode(g2))
} }
if err = ckzg4844.LoadTrustedSetup(g1s, g2s); err != nil { // The last parameter determines the multiplication table, see https://notes.ethereum.org/@jtraglia/windowed_multiplications
// I think 6 is an decent compromise between size and speed
if err = ckzg4844.LoadTrustedSetup(g1s, g1Lag, g2s, 6); err != nil {
panic(err) panic(err)
} }
} }
@ -125,3 +131,21 @@ func ckzgVerifyBlobProof(blob *Blob, commitment Commitment, proof Proof) error {
} }
return nil return nil
} }
// ckzgComputeCellProofs returns the KZG cell proofs that are used to verify the blob against
// the commitment.
//
// This method does not verify that the commitment is correct with respect to blob.
func ckzgComputeCellProofs(blob *Blob) ([]Proof, error) {
ckzgIniter.Do(ckzgInit)
_, proofs, err := ckzg4844.ComputeCellsAndKZGProofs((*ckzg4844.Blob)(blob))
if err != nil {
return []Proof{}, err
}
var p []Proof
for _, proof := range proofs {
p = append(p, (Proof)(proof))
}
return p, nil
}

View file

@ -60,3 +60,11 @@ func ckzgComputeBlobProof(blob *Blob, commitment Commitment) (Proof, error) {
func ckzgVerifyBlobProof(blob *Blob, commitment Commitment, proof Proof) error { func ckzgVerifyBlobProof(blob *Blob, commitment Commitment, proof Proof) error {
panic("unsupported platform") panic("unsupported platform")
} }
// ckzgComputeCellProofs returns the KZG cell proofs that are used to verify the blob against
// the commitment.
//
// This method does not verify that the commitment is correct with respect to blob.
func ckzgComputeCellProofs(blob *Blob) ([]Proof, error) {
panic("unsupported platform")
}

View file

@ -20,7 +20,7 @@ import (
"encoding/json" "encoding/json"
"sync" "sync"
gokzg4844 "github.com/crate-crypto/go-kzg-4844" gokzg4844 "github.com/crate-crypto/go-eth-kzg"
) )
// context is the crypto primitive pre-seeded with the trusted setup parameters. // context is the crypto primitive pre-seeded with the trusted setup parameters.
@ -96,3 +96,21 @@ func gokzgVerifyBlobProof(blob *Blob, commitment Commitment, proof Proof) error
return context.VerifyBlobKZGProof((*gokzg4844.Blob)(blob), (gokzg4844.KZGCommitment)(commitment), (gokzg4844.KZGProof)(proof)) return context.VerifyBlobKZGProof((*gokzg4844.Blob)(blob), (gokzg4844.KZGCommitment)(commitment), (gokzg4844.KZGProof)(proof))
} }
// gokzgComputeCellProofs returns the KZG cell proofs that are used to verify the blob against
// the commitment.
//
// This method does not verify that the commitment is correct with respect to blob.
func gokzgComputeCellProofs(blob *Blob) ([]Proof, error) {
gokzgIniter.Do(gokzgInit)
_, proofs, err := context.ComputeCellsAndKZGProofs((*gokzg4844.Blob)(blob), 0)
if err != nil {
return []Proof{}, err
}
var p []Proof
for _, proof := range proofs {
p = append(p, (Proof)(proof))
}
return p, nil
}

View file

@ -21,7 +21,7 @@ import (
"testing" "testing"
"github.com/consensys/gnark-crypto/ecc/bls12-381/fr" "github.com/consensys/gnark-crypto/ecc/bls12-381/fr"
gokzg4844 "github.com/crate-crypto/go-kzg-4844" gokzg4844 "github.com/crate-crypto/go-eth-kzg"
) )
func randFieldElement() [32]byte { func randFieldElement() [32]byte {

File diff suppressed because it is too large Load diff

View file

@ -349,22 +349,20 @@ func (b *EthAPIBackend) GetPoolTransaction(hash common.Hash) *types.Transaction
// GetTransaction retrieves the lookup along with the transaction itself associate // GetTransaction retrieves the lookup along with the transaction itself associate
// with the given transaction hash. // with the given transaction hash.
// //
// An error will be returned if the transaction is not found, and background // A null will be returned if the transaction is not found. The transaction is not
// indexing for transactions is still in progress. The error is used to indicate the // existent from the node's perspective. This can be due to the transaction indexer
// scenario explicitly that the transaction might be reachable shortly. // not being finished. The caller must explicitly check the indexer progress.
// func (b *EthAPIBackend) GetTransaction(txHash common.Hash) (bool, *types.Transaction, common.Hash, uint64, uint64) {
// A null will be returned in the transaction is not found and background transaction lookup, tx := b.eth.blockchain.GetTransactionLookup(txHash)
// indexing is already finished. The transaction is not existent from the perspective
// of node.
func (b *EthAPIBackend) GetTransaction(ctx context.Context, txHash common.Hash) (bool, *types.Transaction, common.Hash, uint64, uint64, error) {
lookup, tx, err := b.eth.blockchain.GetTransactionLookup(txHash)
if err != nil {
return false, nil, common.Hash{}, 0, 0, err
}
if lookup == nil || tx == nil { if lookup == nil || tx == nil {
return false, nil, common.Hash{}, 0, 0, nil return false, nil, common.Hash{}, 0, 0
} }
return true, tx, lookup.BlockHash, lookup.BlockIndex, lookup.Index, nil return true, tx, lookup.BlockHash, lookup.BlockIndex, lookup.Index
}
// TxIndexDone returns true if the transaction indexer has finished indexing.
func (b *EthAPIBackend) TxIndexDone() bool {
return b.eth.blockchain.TxIndexDone()
} }
func (b *EthAPIBackend) GetPoolNonce(ctx context.Context, addr common.Address) (uint64, error) { func (b *EthAPIBackend) GetPoolNonce(ctx context.Context, addr common.Address) (uint64, error) {
@ -391,7 +389,7 @@ func (b *EthAPIBackend) SubscribeNewTxsEvent(ch chan<- core.NewTxsEvent) event.S
return b.eth.txPool.SubscribeTransactions(ch, true) return b.eth.txPool.SubscribeTransactions(ch, true)
} }
func (b *EthAPIBackend) SyncProgress() ethereum.SyncProgress { func (b *EthAPIBackend) SyncProgress(ctx context.Context) ethereum.SyncProgress {
prog := b.eth.Downloader().Progress() prog := b.eth.Downloader().Progress()
if txProg, err := b.eth.blockchain.TxIndexProgress(); err == nil { if txProg, err := b.eth.blockchain.TxIndexProgress(); err == nil {
prog.TxIndexFinishedBlocks = txProg.Indexed prog.TxIndexFinishedBlocks = txProg.Indexed
@ -443,6 +441,14 @@ func (b *EthAPIBackend) RPCTxFeeCap() float64 {
return b.eth.config.RPCTxFeeCap return b.eth.config.RPCTxFeeCap
} }
func (b *EthAPIBackend) CurrentView() *filtermaps.ChainView {
head := b.eth.blockchain.CurrentBlock()
if head == nil {
return nil
}
return filtermaps.NewChainView(b.eth.blockchain, head.Number.Uint64(), head.Hash())
}
func (b *EthAPIBackend) NewMatcherBackend() filtermaps.MatcherBackend { func (b *EthAPIBackend) NewMatcherBackend() filtermaps.MatcherBackend {
return b.eth.filterMaps.NewMatcherBackend() return b.eth.filterMaps.NewMatcherBackend()
} }

View file

@ -134,13 +134,17 @@ func TestSendTx(t *testing.T) {
func testSendTx(t *testing.T, withLocal bool) { func testSendTx(t *testing.T, withLocal bool) {
b := initBackend(withLocal) b := initBackend(withLocal)
txA := pricedSetCodeTx(0, 250000, uint256.NewInt(params.GWei), uint256.NewInt(params.GWei), key, []unsignedAuth{ txA := pricedSetCodeTx(0, 250000, uint256.NewInt(params.GWei), uint256.NewInt(params.GWei), key, []unsignedAuth{{nonce: 0, key: key}})
{ if err := b.SendTx(context.Background(), txA); err != nil {
nonce: 0, t.Fatalf("Failed to submit tx: %v", err)
key: key, }
}, for {
}) pending, _ := b.TxPool().ContentFrom(address)
b.SendTx(context.Background(), txA) if len(pending) == 1 {
break
}
time.Sleep(100 * time.Millisecond)
}
txB := makeTx(1, nil, nil, key) txB := makeTx(1, nil, nil, key)
err := b.SendTx(context.Background(), txB) err := b.SendTx(context.Background(), txB)

View file

@ -271,26 +271,26 @@ func storageRangeAt(statedb *state.StateDB, root common.Hash, address common.Add
// //
// With one parameter, returns the list of accounts modified in the specified block. // With one parameter, returns the list of accounts modified in the specified block.
func (api *DebugAPI) GetModifiedAccountsByNumber(startNum uint64, endNum *uint64) ([]common.Address, error) { func (api *DebugAPI) GetModifiedAccountsByNumber(startNum uint64, endNum *uint64) ([]common.Address, error) {
var startBlock, endBlock *types.Block var startHeader, endHeader *types.Header
startBlock = api.eth.blockchain.GetBlockByNumber(startNum) startHeader = api.eth.blockchain.GetHeaderByNumber(startNum)
if startBlock == nil { if startHeader == nil {
return nil, fmt.Errorf("start block %x not found", startNum) return nil, fmt.Errorf("start block %x not found", startNum)
} }
if endNum == nil { if endNum == nil {
endBlock = startBlock endHeader = startHeader
startBlock = api.eth.blockchain.GetBlockByHash(startBlock.ParentHash()) startHeader = api.eth.blockchain.GetHeaderByHash(startHeader.ParentHash)
if startBlock == nil { if startHeader == nil {
return nil, fmt.Errorf("block %x has no parent", endBlock.Number()) return nil, fmt.Errorf("block %x has no parent", endHeader.Number)
} }
} else { } else {
endBlock = api.eth.blockchain.GetBlockByNumber(*endNum) endHeader = api.eth.blockchain.GetHeaderByNumber(*endNum)
if endBlock == nil { if endHeader == nil {
return nil, fmt.Errorf("end block %d not found", *endNum) return nil, fmt.Errorf("end block %d not found", *endNum)
} }
} }
return api.getModifiedAccounts(startBlock, endBlock) return api.getModifiedAccounts(startHeader, endHeader)
} }
// GetModifiedAccountsByHash returns all accounts that have changed between the // GetModifiedAccountsByHash returns all accounts that have changed between the
@ -299,38 +299,38 @@ func (api *DebugAPI) GetModifiedAccountsByNumber(startNum uint64, endNum *uint64
// //
// With one parameter, returns the list of accounts modified in the specified block. // With one parameter, returns the list of accounts modified in the specified block.
func (api *DebugAPI) GetModifiedAccountsByHash(startHash common.Hash, endHash *common.Hash) ([]common.Address, error) { func (api *DebugAPI) GetModifiedAccountsByHash(startHash common.Hash, endHash *common.Hash) ([]common.Address, error) {
var startBlock, endBlock *types.Block var startHeader, endHeader *types.Header
startBlock = api.eth.blockchain.GetBlockByHash(startHash) startHeader = api.eth.blockchain.GetHeaderByHash(startHash)
if startBlock == nil { if startHeader == nil {
return nil, fmt.Errorf("start block %x not found", startHash) return nil, fmt.Errorf("start block %x not found", startHash)
} }
if endHash == nil { if endHash == nil {
endBlock = startBlock endHeader = startHeader
startBlock = api.eth.blockchain.GetBlockByHash(startBlock.ParentHash()) startHeader = api.eth.blockchain.GetHeaderByHash(startHeader.ParentHash)
if startBlock == nil { if startHeader == nil {
return nil, fmt.Errorf("block %x has no parent", endBlock.Number()) return nil, fmt.Errorf("block %x has no parent", endHeader.Number)
} }
} else { } else {
endBlock = api.eth.blockchain.GetBlockByHash(*endHash) endHeader = api.eth.blockchain.GetHeaderByHash(*endHash)
if endBlock == nil { if endHeader == nil {
return nil, fmt.Errorf("end block %x not found", *endHash) return nil, fmt.Errorf("end block %x not found", *endHash)
} }
} }
return api.getModifiedAccounts(startBlock, endBlock) return api.getModifiedAccounts(startHeader, endHeader)
} }
func (api *DebugAPI) getModifiedAccounts(startBlock, endBlock *types.Block) ([]common.Address, error) { func (api *DebugAPI) getModifiedAccounts(startHeader, endHeader *types.Header) ([]common.Address, error) {
if startBlock.Number().Uint64() >= endBlock.Number().Uint64() { if startHeader.Number.Uint64() >= endHeader.Number.Uint64() {
return nil, fmt.Errorf("start block height (%d) must be less than end block height (%d)", startBlock.Number().Uint64(), endBlock.Number().Uint64()) return nil, fmt.Errorf("start block height (%d) must be less than end block height (%d)", startHeader.Number.Uint64(), endHeader.Number.Uint64())
} }
triedb := api.eth.BlockChain().TrieDB() triedb := api.eth.BlockChain().TrieDB()
oldTrie, err := trie.NewStateTrie(trie.StateTrieID(startBlock.Root()), triedb) oldTrie, err := trie.NewStateTrie(trie.StateTrieID(startHeader.Root), triedb)
if err != nil { if err != nil {
return nil, err return nil, err
} }
newTrie, err := trie.NewStateTrie(trie.StateTrieID(endBlock.Root()), triedb) newTrie, err := trie.NewStateTrie(trie.StateTrieID(endHeader.Root), triedb)
if err != nil { if err != nil {
return nil, err return nil, err
} }

View file

@ -18,25 +18,74 @@ package eth
import ( import (
"bytes" "bytes"
"crypto/ecdsa"
"fmt" "fmt"
"math/big"
"reflect" "reflect"
"slices" "slices"
"strings" "strings"
"testing" "testing"
"time"
"github.com/davecgh/go-spew/spew" "github.com/davecgh/go-spew/spew"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/consensus/ethash"
"github.com/ethereum/go-ethereum/core"
"github.com/ethereum/go-ethereum/core/rawdb" "github.com/ethereum/go-ethereum/core/rawdb"
"github.com/ethereum/go-ethereum/core/state" "github.com/ethereum/go-ethereum/core/state"
"github.com/ethereum/go-ethereum/core/tracing" "github.com/ethereum/go-ethereum/core/tracing"
"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/crypto" "github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/params"
"github.com/ethereum/go-ethereum/triedb" "github.com/ethereum/go-ethereum/triedb"
"github.com/holiman/uint256" "github.com/holiman/uint256"
"github.com/stretchr/testify/assert"
) )
var dumper = spew.ConfigState{Indent: " "} var dumper = spew.ConfigState{Indent: " "}
type Account struct {
key *ecdsa.PrivateKey
addr common.Address
}
func newAccounts(n int) (accounts []Account) {
for i := 0; i < n; i++ {
key, _ := crypto.GenerateKey()
addr := crypto.PubkeyToAddress(key.PublicKey)
accounts = append(accounts, Account{key: key, addr: addr})
}
slices.SortFunc(accounts, func(a, b Account) int { return a.addr.Cmp(b.addr) })
return accounts
}
// newTestBlockChain creates a new test blockchain. OBS: After test is done, teardown must be
// invoked in order to release associated resources.
func newTestBlockChain(t *testing.T, n int, gspec *core.Genesis, generator func(i int, b *core.BlockGen)) *core.BlockChain {
engine := ethash.NewFaker()
// Generate blocks for testing
_, blocks, _ := core.GenerateChainWithGenesis(gspec, engine, n, generator)
// Import the canonical chain
cacheConfig := &core.CacheConfig{
TrieCleanLimit: 256,
TrieDirtyLimit: 256,
TrieTimeLimit: 5 * time.Minute,
SnapshotLimit: 0,
Preimages: true,
TrieDirtyDisabled: true, // Archive mode
}
chain, err := core.NewBlockChain(rawdb.NewMemoryDatabase(), cacheConfig, gspec, nil, engine, vm.Config{}, 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)
}
return chain
}
func accountRangeTest(t *testing.T, trie *state.Trie, statedb *state.StateDB, start common.Hash, requestedNum int, expectedNum int) state.Dump { func accountRangeTest(t *testing.T, trie *state.Trie, statedb *state.StateDB, start common.Hash, requestedNum int, expectedNum int) state.Dump {
result := statedb.RawDump(&state.DumpConfig{ result := statedb.RawDump(&state.DumpConfig{
SkipCode: true, SkipCode: true,
@ -224,3 +273,66 @@ func TestStorageRangeAt(t *testing.T) {
} }
} }
} }
func TestGetModifiedAccounts(t *testing.T) {
t.Parallel()
// Initialize test accounts
accounts := newAccounts(4)
genesis := &core.Genesis{
Config: params.TestChainConfig,
Alloc: types.GenesisAlloc{
accounts[0].addr: {Balance: big.NewInt(params.Ether)},
accounts[1].addr: {Balance: big.NewInt(params.Ether)},
accounts[2].addr: {Balance: big.NewInt(params.Ether)},
accounts[3].addr: {Balance: big.NewInt(params.Ether)},
},
}
genBlocks := 1
signer := types.HomesteadSigner{}
blockChain := newTestBlockChain(t, genBlocks, genesis, func(_ int, b *core.BlockGen) {
// Transfer from account[0] to account[1]
// value: 1000 wei
// fee: 0 wei
for _, account := range accounts[:3] {
tx, _ := types.SignTx(types.NewTx(&types.LegacyTx{
Nonce: 0,
To: &accounts[3].addr,
Value: big.NewInt(1000),
Gas: params.TxGas,
GasPrice: b.BaseFee(),
Data: nil}),
signer, account.key)
b.AddTx(tx)
}
})
defer blockChain.Stop()
// Create a debug API instance.
api := NewDebugAPI(&Ethereum{blockchain: blockChain})
// Test GetModifiedAccountsByNumber
t.Run("GetModifiedAccountsByNumber", func(t *testing.T) {
addrs, err := api.GetModifiedAccountsByNumber(uint64(genBlocks), nil)
assert.NoError(t, err)
assert.Len(t, addrs, len(accounts)+1) // +1 for the coinbase
for _, account := range accounts {
if !slices.Contains(addrs, account.addr) {
t.Fatalf("account %s not found in modified accounts", account.addr.Hex())
}
}
})
// Test GetModifiedAccountsByHash
t.Run("GetModifiedAccountsByHash", func(t *testing.T) {
header := blockChain.GetHeaderByNumber(uint64(genBlocks))
addrs, err := api.GetModifiedAccountsByHash(header.Hash(), nil)
assert.NoError(t, err)
assert.Len(t, addrs, len(accounts)+1) // +1 for the coinbase
for _, account := range accounts {
if !slices.Contains(addrs, account.addr) {
t.Fatalf("account %s not found in modified accounts", account.addr.Hex())
}
}
})
}

View file

@ -38,6 +38,7 @@ import (
"github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/log"
"github.com/ethereum/go-ethereum/miner" "github.com/ethereum/go-ethereum/miner"
"github.com/ethereum/go-ethereum/node" "github.com/ethereum/go-ethereum/node"
"github.com/ethereum/go-ethereum/params"
"github.com/ethereum/go-ethereum/params/forks" "github.com/ethereum/go-ethereum/params/forks"
"github.com/ethereum/go-ethereum/rlp" "github.com/ethereum/go-ethereum/rlp"
"github.com/ethereum/go-ethereum/rpc" "github.com/ethereum/go-ethereum/rpc"
@ -196,11 +197,11 @@ func newConsensusAPIWithoutHeartbeat(eth *eth.Ethereum) *ConsensusAPI {
// and return its payloadID. // and return its payloadID.
func (api *ConsensusAPI) ForkchoiceUpdatedV1(update engine.ForkchoiceStateV1, payloadAttributes *engine.PayloadAttributes) (engine.ForkChoiceResponse, error) { func (api *ConsensusAPI) ForkchoiceUpdatedV1(update engine.ForkchoiceStateV1, payloadAttributes *engine.PayloadAttributes) (engine.ForkChoiceResponse, error) {
if payloadAttributes != nil { if payloadAttributes != nil {
if payloadAttributes.Withdrawals != nil || payloadAttributes.BeaconRoot != nil { switch {
return engine.STATUS_INVALID, engine.InvalidParams.With(errors.New("withdrawals and beacon root not supported in V1")) case payloadAttributes.Withdrawals != nil || payloadAttributes.BeaconRoot != nil:
} return engine.STATUS_INVALID, paramsErr("withdrawals and beacon root not supported in V1")
if api.eth.BlockChain().Config().IsShanghai(api.eth.BlockChain().Config().LondonBlock, payloadAttributes.Timestamp) { case !api.checkFork(payloadAttributes.Timestamp, forks.Paris, forks.Shanghai):
return engine.STATUS_INVALID, engine.InvalidParams.With(errors.New("forkChoiceUpdateV1 called post-shanghai")) return engine.STATUS_INVALID, paramsErr("fcuV1 called post-shanghai")
} }
} }
return api.forkchoiceUpdated(update, payloadAttributes, engine.PayloadV1, false) return api.forkchoiceUpdated(update, payloadAttributes, engine.PayloadV1, false)
@ -210,20 +211,15 @@ func (api *ConsensusAPI) ForkchoiceUpdatedV1(update engine.ForkchoiceStateV1, pa
// attributes. It supports both PayloadAttributesV1 and PayloadAttributesV2. // attributes. It supports both PayloadAttributesV1 and PayloadAttributesV2.
func (api *ConsensusAPI) ForkchoiceUpdatedV2(update engine.ForkchoiceStateV1, params *engine.PayloadAttributes) (engine.ForkChoiceResponse, error) { func (api *ConsensusAPI) ForkchoiceUpdatedV2(update engine.ForkchoiceStateV1, params *engine.PayloadAttributes) (engine.ForkChoiceResponse, error) {
if params != nil { if params != nil {
if params.BeaconRoot != nil { switch {
return engine.STATUS_INVALID, engine.InvalidPayloadAttributes.With(errors.New("unexpected beacon root")) case params.BeaconRoot != nil:
} return engine.STATUS_INVALID, attributesErr("unexpected beacon root")
switch api.eth.BlockChain().Config().LatestFork(params.Timestamp) { case api.checkFork(params.Timestamp, forks.Paris) && params.Withdrawals != nil:
case forks.Paris: return engine.STATUS_INVALID, attributesErr("withdrawals before shanghai")
if params.Withdrawals != nil { case api.checkFork(params.Timestamp, forks.Shanghai) && params.Withdrawals == nil:
return engine.STATUS_INVALID, engine.InvalidPayloadAttributes.With(errors.New("withdrawals before shanghai")) return engine.STATUS_INVALID, attributesErr("missing withdrawals")
} case !api.checkFork(params.Timestamp, forks.Paris, forks.Shanghai):
case forks.Shanghai: return engine.STATUS_INVALID, unsupportedForkErr("fcuV2 must only be called with paris or shanghai payloads")
if params.Withdrawals == nil {
return engine.STATUS_INVALID, engine.InvalidPayloadAttributes.With(errors.New("missing withdrawals"))
}
default:
return engine.STATUS_INVALID, engine.UnsupportedFork.With(errors.New("forkchoiceUpdatedV2 must only be called with paris and shanghai payloads"))
} }
} }
return api.forkchoiceUpdated(update, params, engine.PayloadV2, false) return api.forkchoiceUpdated(update, params, engine.PayloadV2, false)
@ -233,14 +229,13 @@ func (api *ConsensusAPI) ForkchoiceUpdatedV2(update engine.ForkchoiceStateV1, pa
// in the payload attributes. It supports only PayloadAttributesV3. // in the payload attributes. It supports only PayloadAttributesV3.
func (api *ConsensusAPI) ForkchoiceUpdatedV3(update engine.ForkchoiceStateV1, params *engine.PayloadAttributes) (engine.ForkChoiceResponse, error) { func (api *ConsensusAPI) ForkchoiceUpdatedV3(update engine.ForkchoiceStateV1, params *engine.PayloadAttributes) (engine.ForkChoiceResponse, error) {
if params != nil { if params != nil {
if params.Withdrawals == nil { switch {
return engine.STATUS_INVALID, engine.InvalidPayloadAttributes.With(errors.New("missing withdrawals")) case params.Withdrawals == nil:
} return engine.STATUS_INVALID, attributesErr("missing withdrawals")
if params.BeaconRoot == nil { case params.BeaconRoot == nil:
return engine.STATUS_INVALID, engine.InvalidPayloadAttributes.With(errors.New("missing beacon root")) return engine.STATUS_INVALID, attributesErr("missing beacon root")
} case !api.checkFork(params.Timestamp, forks.Cancun, forks.Prague):
if api.eth.BlockChain().Config().LatestFork(params.Timestamp) != forks.Cancun && api.eth.BlockChain().Config().LatestFork(params.Timestamp) != forks.Prague { return engine.STATUS_INVALID, unsupportedForkErr("fcuV3 must only be called for cancun or prague payloads")
return engine.STATUS_INVALID, engine.UnsupportedFork.With(errors.New("forkchoiceUpdatedV3 must only be called for cancun payloads"))
} }
} }
// TODO(matt): the spec requires that fcu is applied when called on a valid // TODO(matt): the spec requires that fcu is applied when called on a valid
@ -254,11 +249,11 @@ func (api *ConsensusAPI) ForkchoiceUpdatedV3(update engine.ForkchoiceStateV1, pa
// generates an execution witness too if block building was requested. // generates an execution witness too if block building was requested.
func (api *ConsensusAPI) ForkchoiceUpdatedWithWitnessV1(update engine.ForkchoiceStateV1, payloadAttributes *engine.PayloadAttributes) (engine.ForkChoiceResponse, error) { func (api *ConsensusAPI) ForkchoiceUpdatedWithWitnessV1(update engine.ForkchoiceStateV1, payloadAttributes *engine.PayloadAttributes) (engine.ForkChoiceResponse, error) {
if payloadAttributes != nil { if payloadAttributes != nil {
if payloadAttributes.Withdrawals != nil || payloadAttributes.BeaconRoot != nil { switch {
return engine.STATUS_INVALID, engine.InvalidParams.With(errors.New("withdrawals and beacon root not supported in V1")) case payloadAttributes.Withdrawals != nil || payloadAttributes.BeaconRoot != nil:
} return engine.STATUS_INVALID, paramsErr("withdrawals and beacon root not supported in V1")
if api.eth.BlockChain().Config().IsShanghai(api.eth.BlockChain().Config().LondonBlock, payloadAttributes.Timestamp) { case !api.checkFork(payloadAttributes.Timestamp, forks.Paris, forks.Shanghai):
return engine.STATUS_INVALID, engine.InvalidParams.With(errors.New("forkChoiceUpdateV1 called post-shanghai")) return engine.STATUS_INVALID, paramsErr("fcuV1 called post-shanghai")
} }
} }
return api.forkchoiceUpdated(update, payloadAttributes, engine.PayloadV1, true) return api.forkchoiceUpdated(update, payloadAttributes, engine.PayloadV1, true)
@ -268,20 +263,15 @@ func (api *ConsensusAPI) ForkchoiceUpdatedWithWitnessV1(update engine.Forkchoice
// generates an execution witness too if block building was requested. // generates an execution witness too if block building was requested.
func (api *ConsensusAPI) ForkchoiceUpdatedWithWitnessV2(update engine.ForkchoiceStateV1, params *engine.PayloadAttributes) (engine.ForkChoiceResponse, error) { func (api *ConsensusAPI) ForkchoiceUpdatedWithWitnessV2(update engine.ForkchoiceStateV1, params *engine.PayloadAttributes) (engine.ForkChoiceResponse, error) {
if params != nil { if params != nil {
if params.BeaconRoot != nil { switch {
return engine.STATUS_INVALID, engine.InvalidPayloadAttributes.With(errors.New("unexpected beacon root")) case params.BeaconRoot != nil:
} return engine.STATUS_INVALID, attributesErr("unexpected beacon root")
switch api.eth.BlockChain().Config().LatestFork(params.Timestamp) { case api.checkFork(params.Timestamp, forks.Paris) && params.Withdrawals != nil:
case forks.Paris: return engine.STATUS_INVALID, attributesErr("withdrawals before shanghai")
if params.Withdrawals != nil { case api.checkFork(params.Timestamp, forks.Shanghai) && params.Withdrawals == nil:
return engine.STATUS_INVALID, engine.InvalidPayloadAttributes.With(errors.New("withdrawals before shanghai")) return engine.STATUS_INVALID, attributesErr("missing withdrawals")
} case !api.checkFork(params.Timestamp, forks.Paris, forks.Shanghai):
case forks.Shanghai: return engine.STATUS_INVALID, unsupportedForkErr("fcuV2 must only be called with paris or shanghai payloads")
if params.Withdrawals == nil {
return engine.STATUS_INVALID, engine.InvalidPayloadAttributes.With(errors.New("missing withdrawals"))
}
default:
return engine.STATUS_INVALID, engine.UnsupportedFork.With(errors.New("forkchoiceUpdatedV2 must only be called with paris and shanghai payloads"))
} }
} }
return api.forkchoiceUpdated(update, params, engine.PayloadV2, true) return api.forkchoiceUpdated(update, params, engine.PayloadV2, true)
@ -291,14 +281,13 @@ func (api *ConsensusAPI) ForkchoiceUpdatedWithWitnessV2(update engine.Forkchoice
// generates an execution witness too if block building was requested. // generates an execution witness too if block building was requested.
func (api *ConsensusAPI) ForkchoiceUpdatedWithWitnessV3(update engine.ForkchoiceStateV1, params *engine.PayloadAttributes) (engine.ForkChoiceResponse, error) { func (api *ConsensusAPI) ForkchoiceUpdatedWithWitnessV3(update engine.ForkchoiceStateV1, params *engine.PayloadAttributes) (engine.ForkChoiceResponse, error) {
if params != nil { if params != nil {
if params.Withdrawals == nil { switch {
return engine.STATUS_INVALID, engine.InvalidPayloadAttributes.With(errors.New("missing withdrawals")) case params.Withdrawals == nil:
} return engine.STATUS_INVALID, attributesErr("missing withdrawals")
if params.BeaconRoot == nil { case params.BeaconRoot == nil:
return engine.STATUS_INVALID, engine.InvalidPayloadAttributes.With(errors.New("missing beacon root")) return engine.STATUS_INVALID, attributesErr("missing beacon root")
} case !api.checkFork(params.Timestamp, forks.Cancun, forks.Prague):
if api.eth.BlockChain().Config().LatestFork(params.Timestamp) != forks.Cancun && api.eth.BlockChain().Config().LatestFork(params.Timestamp) != forks.Prague { return engine.STATUS_INVALID, unsupportedForkErr("fcuV3 must only be called for cancun or prague payloads")
return engine.STATUS_INVALID, engine.UnsupportedFork.With(errors.New("forkchoiceUpdatedV3 must only be called for cancun payloads"))
} }
} }
// TODO(matt): the spec requires that fcu is applied when called on a valid // TODO(matt): the spec requires that fcu is applied when called on a valid
@ -373,8 +362,11 @@ func (api *ConsensusAPI) forkchoiceUpdated(update engine.ForkchoiceStateV1, payl
} }
valid := func(id *engine.PayloadID) engine.ForkChoiceResponse { valid := func(id *engine.PayloadID) engine.ForkChoiceResponse {
return engine.ForkChoiceResponse{ return engine.ForkChoiceResponse{
PayloadStatus: engine.PayloadStatusV1{Status: engine.VALID, LatestValidHash: &update.HeadBlockHash}, PayloadStatus: engine.PayloadStatusV1{
PayloadID: id, Status: engine.VALID,
LatestValidHash: &update.HeadBlockHash,
},
PayloadID: id,
} }
} }
if rawdb.ReadCanonicalHash(api.eth.ChainDb(), block.NumberU64()) != update.HeadBlockHash { if rawdb.ReadCanonicalHash(api.eth.ChainDb(), block.NumberU64()) != update.HeadBlockHash {
@ -465,7 +457,7 @@ func (api *ConsensusAPI) ExchangeTransitionConfigurationV1(config engine.Transit
api.lastTransitionUpdate = time.Now() api.lastTransitionUpdate = time.Now()
api.lastTransitionLock.Unlock() api.lastTransitionLock.Unlock()
ttd := api.eth.BlockChain().Config().TerminalTotalDifficulty ttd := api.config().TerminalTotalDifficulty
if ttd == nil || ttd.Cmp(config.TerminalTotalDifficulty.ToInt()) != 0 { if ttd == nil || ttd.Cmp(config.TerminalTotalDifficulty.ToInt()) != 0 {
log.Warn("Invalid TTD configured", "geth", ttd, "beacon", config.TerminalTotalDifficulty) log.Warn("Invalid TTD configured", "geth", ttd, "beacon", config.TerminalTotalDifficulty)
return nil, fmt.Errorf("invalid ttd: execution %v consensus %v", ttd, config.TerminalTotalDifficulty) return nil, fmt.Errorf("invalid ttd: execution %v consensus %v", ttd, config.TerminalTotalDifficulty)
@ -547,86 +539,74 @@ func (api *ConsensusAPI) GetBlobsV1(hashes []common.Hash) ([]*engine.BlobAndProo
return res, nil return res, nil
} }
// Helper for NewPayload* methods.
var invalidStatus = engine.PayloadStatusV1{Status: engine.INVALID}
// NewPayloadV1 creates an Eth1 block, inserts it in the chain, and returns the status of the chain. // NewPayloadV1 creates an Eth1 block, inserts it in the chain, and returns the status of the chain.
func (api *ConsensusAPI) NewPayloadV1(params engine.ExecutableData) (engine.PayloadStatusV1, error) { func (api *ConsensusAPI) NewPayloadV1(params engine.ExecutableData) (engine.PayloadStatusV1, error) {
if params.Withdrawals != nil { if params.Withdrawals != nil {
return engine.PayloadStatusV1{Status: engine.INVALID}, engine.InvalidParams.With(errors.New("withdrawals not supported in V1")) return invalidStatus, paramsErr("withdrawals not supported in V1")
} }
return api.newPayload(params, nil, nil, nil, false) return api.newPayload(params, nil, nil, nil, false)
} }
// NewPayloadV2 creates an Eth1 block, inserts it in the chain, and returns the status of the chain. // NewPayloadV2 creates an Eth1 block, inserts it in the chain, and returns the status of the chain.
func (api *ConsensusAPI) NewPayloadV2(params engine.ExecutableData) (engine.PayloadStatusV1, error) { func (api *ConsensusAPI) NewPayloadV2(params engine.ExecutableData) (engine.PayloadStatusV1, error) {
if api.eth.BlockChain().Config().IsCancun(api.eth.BlockChain().Config().LondonBlock, params.Timestamp) { var (
return engine.PayloadStatusV1{Status: engine.INVALID}, engine.InvalidParams.With(errors.New("can't use newPayloadV2 post-cancun")) cancun = api.config().IsCancun(api.config().LondonBlock, params.Timestamp)
} shanghai = api.config().IsShanghai(api.config().LondonBlock, params.Timestamp)
if api.eth.BlockChain().Config().LatestFork(params.Timestamp) == forks.Shanghai { )
if params.Withdrawals == nil { switch {
return engine.PayloadStatusV1{Status: engine.INVALID}, engine.InvalidParams.With(errors.New("nil withdrawals post-shanghai")) case cancun:
} return invalidStatus, paramsErr("can't use newPayloadV2 post-cancun")
} else { case shanghai && params.Withdrawals == nil:
if params.Withdrawals != nil { return invalidStatus, paramsErr("nil withdrawals post-shanghai")
return engine.PayloadStatusV1{Status: engine.INVALID}, engine.InvalidParams.With(errors.New("non-nil withdrawals pre-shanghai")) case !shanghai && params.Withdrawals != nil:
} return invalidStatus, paramsErr("non-nil withdrawals pre-shanghai")
} case params.ExcessBlobGas != nil:
if params.ExcessBlobGas != nil { return invalidStatus, paramsErr("non-nil excessBlobGas pre-cancun")
return engine.PayloadStatusV1{Status: engine.INVALID}, engine.InvalidParams.With(errors.New("non-nil excessBlobGas pre-cancun")) case params.BlobGasUsed != nil:
} return invalidStatus, paramsErr("non-nil blobGasUsed pre-cancun")
if params.BlobGasUsed != nil {
return engine.PayloadStatusV1{Status: engine.INVALID}, engine.InvalidParams.With(errors.New("non-nil blobGasUsed pre-cancun"))
} }
return api.newPayload(params, nil, nil, nil, false) return api.newPayload(params, nil, nil, nil, false)
} }
// NewPayloadV3 creates an Eth1 block, inserts it in the chain, and returns the status of the chain. // NewPayloadV3 creates an Eth1 block, inserts it in the chain, and returns the status of the chain.
func (api *ConsensusAPI) NewPayloadV3(params engine.ExecutableData, versionedHashes []common.Hash, beaconRoot *common.Hash) (engine.PayloadStatusV1, error) { func (api *ConsensusAPI) NewPayloadV3(params engine.ExecutableData, versionedHashes []common.Hash, beaconRoot *common.Hash) (engine.PayloadStatusV1, error) {
if params.Withdrawals == nil { switch {
return engine.PayloadStatusV1{Status: engine.INVALID}, engine.InvalidParams.With(errors.New("nil withdrawals post-shanghai")) case params.Withdrawals == nil:
} return invalidStatus, paramsErr("nil withdrawals post-shanghai")
if params.ExcessBlobGas == nil { case params.ExcessBlobGas == nil:
return engine.PayloadStatusV1{Status: engine.INVALID}, engine.InvalidParams.With(errors.New("nil excessBlobGas post-cancun")) return invalidStatus, paramsErr("nil excessBlobGas post-cancun")
} case params.BlobGasUsed == nil:
if params.BlobGasUsed == nil { return invalidStatus, paramsErr("nil blobGasUsed post-cancun")
return engine.PayloadStatusV1{Status: engine.INVALID}, engine.InvalidParams.With(errors.New("nil blobGasUsed post-cancun")) case versionedHashes == nil:
} return invalidStatus, paramsErr("nil versionedHashes post-cancun")
case beaconRoot == nil:
if versionedHashes == nil { return invalidStatus, paramsErr("nil beaconRoot post-cancun")
return engine.PayloadStatusV1{Status: engine.INVALID}, engine.InvalidParams.With(errors.New("nil versionedHashes post-cancun")) case !api.checkFork(params.Timestamp, forks.Cancun):
} return invalidStatus, unsupportedForkErr("newPayloadV3 must only be called for cancun payloads")
if beaconRoot == nil {
return engine.PayloadStatusV1{Status: engine.INVALID}, engine.InvalidParams.With(errors.New("nil beaconRoot post-cancun"))
}
if api.eth.BlockChain().Config().LatestFork(params.Timestamp) != forks.Cancun {
return engine.PayloadStatusV1{Status: engine.INVALID}, engine.UnsupportedFork.With(errors.New("newPayloadV3 must only be called for cancun payloads"))
} }
return api.newPayload(params, versionedHashes, beaconRoot, nil, false) return api.newPayload(params, versionedHashes, beaconRoot, nil, false)
} }
// NewPayloadV4 creates an Eth1 block, inserts it in the chain, and returns the status of the chain. // NewPayloadV4 creates an Eth1 block, inserts it in the chain, and returns the status of the chain.
func (api *ConsensusAPI) NewPayloadV4(params engine.ExecutableData, versionedHashes []common.Hash, beaconRoot *common.Hash, executionRequests []hexutil.Bytes) (engine.PayloadStatusV1, error) { func (api *ConsensusAPI) NewPayloadV4(params engine.ExecutableData, versionedHashes []common.Hash, beaconRoot *common.Hash, executionRequests []hexutil.Bytes) (engine.PayloadStatusV1, error) {
if params.Withdrawals == nil { switch {
return engine.PayloadStatusV1{Status: engine.INVALID}, engine.InvalidParams.With(errors.New("nil withdrawals post-shanghai")) case params.Withdrawals == nil:
} return invalidStatus, paramsErr("nil withdrawals post-shanghai")
if params.ExcessBlobGas == nil { case params.ExcessBlobGas == nil:
return engine.PayloadStatusV1{Status: engine.INVALID}, engine.InvalidParams.With(errors.New("nil excessBlobGas post-cancun")) return invalidStatus, paramsErr("nil excessBlobGas post-cancun")
} case params.BlobGasUsed == nil:
if params.BlobGasUsed == nil { return invalidStatus, paramsErr("nil blobGasUsed post-cancun")
return engine.PayloadStatusV1{Status: engine.INVALID}, engine.InvalidParams.With(errors.New("nil blobGasUsed post-cancun")) case versionedHashes == nil:
} return invalidStatus, paramsErr("nil versionedHashes post-cancun")
case beaconRoot == nil:
if versionedHashes == nil { return invalidStatus, paramsErr("nil beaconRoot post-cancun")
return engine.PayloadStatusV1{Status: engine.INVALID}, engine.InvalidParams.With(errors.New("nil versionedHashes post-cancun")) case executionRequests == nil:
} return invalidStatus, paramsErr("nil executionRequests post-prague")
if beaconRoot == nil { case !api.checkFork(params.Timestamp, forks.Prague):
return engine.PayloadStatusV1{Status: engine.INVALID}, engine.InvalidParams.With(errors.New("nil beaconRoot post-cancun")) return invalidStatus, unsupportedForkErr("newPayloadV3 must only be called for cancun payloads")
}
if executionRequests == nil {
return engine.PayloadStatusV1{Status: engine.INVALID}, engine.InvalidParams.With(errors.New("nil executionRequests post-prague"))
}
if api.eth.BlockChain().Config().LatestFork(params.Timestamp) != forks.Prague {
return engine.PayloadStatusV1{Status: engine.INVALID}, engine.UnsupportedFork.With(errors.New("newPayloadV4 must only be called for prague payloads"))
} }
requests := convertRequests(executionRequests) requests := convertRequests(executionRequests)
if err := validateRequests(requests); err != nil { if err := validateRequests(requests); err != nil {
@ -647,23 +627,21 @@ func (api *ConsensusAPI) NewPayloadWithWitnessV1(params engine.ExecutableData) (
// NewPayloadWithWitnessV2 is analogous to NewPayloadV2, only it also generates // NewPayloadWithWitnessV2 is analogous to NewPayloadV2, only it also generates
// and returns a stateless witness after running the payload. // and returns a stateless witness after running the payload.
func (api *ConsensusAPI) NewPayloadWithWitnessV2(params engine.ExecutableData) (engine.PayloadStatusV1, error) { func (api *ConsensusAPI) NewPayloadWithWitnessV2(params engine.ExecutableData) (engine.PayloadStatusV1, error) {
if api.eth.BlockChain().Config().IsCancun(api.eth.BlockChain().Config().LondonBlock, params.Timestamp) { var (
return engine.PayloadStatusV1{Status: engine.INVALID}, engine.InvalidParams.With(errors.New("can't use newPayloadV2 post-cancun")) cancun = api.config().IsCancun(api.config().LondonBlock, params.Timestamp)
} shanghai = api.config().IsShanghai(api.config().LondonBlock, params.Timestamp)
if api.eth.BlockChain().Config().LatestFork(params.Timestamp) == forks.Shanghai { )
if params.Withdrawals == nil { switch {
return engine.PayloadStatusV1{Status: engine.INVALID}, engine.InvalidParams.With(errors.New("nil withdrawals post-shanghai")) case cancun:
} return invalidStatus, paramsErr("can't use newPayloadV2 post-cancun")
} else { case shanghai && params.Withdrawals == nil:
if params.Withdrawals != nil { return invalidStatus, paramsErr("nil withdrawals post-shanghai")
return engine.PayloadStatusV1{Status: engine.INVALID}, engine.InvalidParams.With(errors.New("non-nil withdrawals pre-shanghai")) case !shanghai && params.Withdrawals != nil:
} return invalidStatus, paramsErr("non-nil withdrawals pre-shanghai")
} case params.ExcessBlobGas != nil:
if params.ExcessBlobGas != nil { return invalidStatus, paramsErr("non-nil excessBlobGas pre-cancun")
return engine.PayloadStatusV1{Status: engine.INVALID}, engine.InvalidParams.With(errors.New("non-nil excessBlobGas pre-cancun")) case params.BlobGasUsed != nil:
} return invalidStatus, paramsErr("non-nil blobGasUsed pre-cancun")
if params.BlobGasUsed != nil {
return engine.PayloadStatusV1{Status: engine.INVALID}, engine.InvalidParams.With(errors.New("non-nil blobGasUsed pre-cancun"))
} }
return api.newPayload(params, nil, nil, nil, true) return api.newPayload(params, nil, nil, nil, true)
} }
@ -671,25 +649,19 @@ func (api *ConsensusAPI) NewPayloadWithWitnessV2(params engine.ExecutableData) (
// NewPayloadWithWitnessV3 is analogous to NewPayloadV3, only it also generates // NewPayloadWithWitnessV3 is analogous to NewPayloadV3, only it also generates
// and returns a stateless witness after running the payload. // and returns a stateless witness after running the payload.
func (api *ConsensusAPI) NewPayloadWithWitnessV3(params engine.ExecutableData, versionedHashes []common.Hash, beaconRoot *common.Hash) (engine.PayloadStatusV1, error) { func (api *ConsensusAPI) NewPayloadWithWitnessV3(params engine.ExecutableData, versionedHashes []common.Hash, beaconRoot *common.Hash) (engine.PayloadStatusV1, error) {
if params.Withdrawals == nil { switch {
return engine.PayloadStatusV1{Status: engine.INVALID}, engine.InvalidParams.With(errors.New("nil withdrawals post-shanghai")) case params.Withdrawals == nil:
} return invalidStatus, paramsErr("nil withdrawals post-shanghai")
if params.ExcessBlobGas == nil { case params.ExcessBlobGas == nil:
return engine.PayloadStatusV1{Status: engine.INVALID}, engine.InvalidParams.With(errors.New("nil excessBlobGas post-cancun")) return invalidStatus, paramsErr("nil excessBlobGas post-cancun")
} case params.BlobGasUsed == nil:
if params.BlobGasUsed == nil { return invalidStatus, paramsErr("nil blobGasUsed post-cancun")
return engine.PayloadStatusV1{Status: engine.INVALID}, engine.InvalidParams.With(errors.New("nil blobGasUsed post-cancun")) case versionedHashes == nil:
} return invalidStatus, paramsErr("nil versionedHashes post-cancun")
case beaconRoot == nil:
if versionedHashes == nil { return invalidStatus, paramsErr("nil beaconRoot post-cancun")
return engine.PayloadStatusV1{Status: engine.INVALID}, engine.InvalidParams.With(errors.New("nil versionedHashes post-cancun")) case !api.checkFork(params.Timestamp, forks.Cancun):
} return invalidStatus, unsupportedForkErr("newPayloadV3 must only be called for cancun payloads")
if beaconRoot == nil {
return engine.PayloadStatusV1{Status: engine.INVALID}, engine.InvalidParams.With(errors.New("nil beaconRoot post-cancun"))
}
if api.eth.BlockChain().Config().LatestFork(params.Timestamp) != forks.Cancun {
return engine.PayloadStatusV1{Status: engine.INVALID}, engine.UnsupportedFork.With(errors.New("newPayloadWithWitnessV3 must only be called for cancun payloads"))
} }
return api.newPayload(params, versionedHashes, beaconRoot, nil, true) return api.newPayload(params, versionedHashes, beaconRoot, nil, true)
} }
@ -697,28 +669,21 @@ func (api *ConsensusAPI) NewPayloadWithWitnessV3(params engine.ExecutableData, v
// NewPayloadWithWitnessV4 is analogous to NewPayloadV4, only it also generates // NewPayloadWithWitnessV4 is analogous to NewPayloadV4, only it also generates
// and returns a stateless witness after running the payload. // and returns a stateless witness after running the payload.
func (api *ConsensusAPI) NewPayloadWithWitnessV4(params engine.ExecutableData, versionedHashes []common.Hash, beaconRoot *common.Hash, executionRequests []hexutil.Bytes) (engine.PayloadStatusV1, error) { func (api *ConsensusAPI) NewPayloadWithWitnessV4(params engine.ExecutableData, versionedHashes []common.Hash, beaconRoot *common.Hash, executionRequests []hexutil.Bytes) (engine.PayloadStatusV1, error) {
if params.Withdrawals == nil { switch {
return engine.PayloadStatusV1{Status: engine.INVALID}, engine.InvalidParams.With(errors.New("nil withdrawals post-shanghai")) case params.Withdrawals == nil:
} return invalidStatus, paramsErr("nil withdrawals post-shanghai")
if params.ExcessBlobGas == nil { case params.ExcessBlobGas == nil:
return engine.PayloadStatusV1{Status: engine.INVALID}, engine.InvalidParams.With(errors.New("nil excessBlobGas post-cancun")) return invalidStatus, paramsErr("nil excessBlobGas post-cancun")
} case params.BlobGasUsed == nil:
if params.BlobGasUsed == nil { return invalidStatus, paramsErr("nil blobGasUsed post-cancun")
return engine.PayloadStatusV1{Status: engine.INVALID}, engine.InvalidParams.With(errors.New("nil blobGasUsed post-cancun")) case versionedHashes == nil:
} return invalidStatus, paramsErr("nil versionedHashes post-cancun")
case beaconRoot == nil:
if versionedHashes == nil { return invalidStatus, paramsErr("nil beaconRoot post-cancun")
return engine.PayloadStatusV1{Status: engine.INVALID}, engine.InvalidParams.With(errors.New("nil versionedHashes post-cancun")) case executionRequests == nil:
} return invalidStatus, paramsErr("nil executionRequests post-prague")
if beaconRoot == nil { case !api.checkFork(params.Timestamp, forks.Prague):
return engine.PayloadStatusV1{Status: engine.INVALID}, engine.InvalidParams.With(errors.New("nil beaconRoot post-cancun")) return invalidStatus, unsupportedForkErr("newPayloadV3 must only be called for cancun payloads")
}
if executionRequests == nil {
return engine.PayloadStatusV1{Status: engine.INVALID}, engine.InvalidParams.With(errors.New("nil executionRequests post-prague"))
}
if api.eth.BlockChain().Config().LatestFork(params.Timestamp) != forks.Prague {
return engine.PayloadStatusV1{Status: engine.INVALID}, engine.UnsupportedFork.With(errors.New("newPayloadWithWitnessV4 must only be called for prague payloads"))
} }
requests := convertRequests(executionRequests) requests := convertRequests(executionRequests)
if err := validateRequests(requests); err != nil { if err := validateRequests(requests); err != nil {
@ -739,23 +704,21 @@ func (api *ConsensusAPI) ExecuteStatelessPayloadV1(params engine.ExecutableData,
// ExecuteStatelessPayloadV2 is analogous to NewPayloadV2, only it operates in // ExecuteStatelessPayloadV2 is analogous to NewPayloadV2, only it operates in
// a stateless mode on top of a provided witness instead of the local database. // a stateless mode on top of a provided witness instead of the local database.
func (api *ConsensusAPI) ExecuteStatelessPayloadV2(params engine.ExecutableData, opaqueWitness hexutil.Bytes) (engine.StatelessPayloadStatusV1, error) { func (api *ConsensusAPI) ExecuteStatelessPayloadV2(params engine.ExecutableData, opaqueWitness hexutil.Bytes) (engine.StatelessPayloadStatusV1, error) {
if api.eth.BlockChain().Config().IsCancun(api.eth.BlockChain().Config().LondonBlock, params.Timestamp) { var (
return engine.StatelessPayloadStatusV1{Status: engine.INVALID}, engine.InvalidParams.With(errors.New("can't use newPayloadV2 post-cancun")) cancun = api.config().IsCancun(api.config().LondonBlock, params.Timestamp)
} shanghai = api.config().IsShanghai(api.config().LondonBlock, params.Timestamp)
if api.eth.BlockChain().Config().LatestFork(params.Timestamp) == forks.Shanghai { )
if params.Withdrawals == nil { switch {
return engine.StatelessPayloadStatusV1{Status: engine.INVALID}, engine.InvalidParams.With(errors.New("nil withdrawals post-shanghai")) case cancun:
} return engine.StatelessPayloadStatusV1{Status: engine.INVALID}, paramsErr("can't use newPayloadV2 post-cancun")
} else { case shanghai && params.Withdrawals == nil:
if params.Withdrawals != nil { return engine.StatelessPayloadStatusV1{Status: engine.INVALID}, paramsErr("nil withdrawals post-shanghai")
return engine.StatelessPayloadStatusV1{Status: engine.INVALID}, engine.InvalidParams.With(errors.New("non-nil withdrawals pre-shanghai")) case !shanghai && params.Withdrawals != nil:
} return engine.StatelessPayloadStatusV1{Status: engine.INVALID}, paramsErr("non-nil withdrawals pre-shanghai")
} case params.ExcessBlobGas != nil:
if params.ExcessBlobGas != nil { return engine.StatelessPayloadStatusV1{Status: engine.INVALID}, paramsErr("non-nil excessBlobGas pre-cancun")
return engine.StatelessPayloadStatusV1{Status: engine.INVALID}, engine.InvalidParams.With(errors.New("non-nil excessBlobGas pre-cancun")) case params.BlobGasUsed != nil:
} return engine.StatelessPayloadStatusV1{Status: engine.INVALID}, paramsErr("non-nil blobGasUsed pre-cancun")
if params.BlobGasUsed != nil {
return engine.StatelessPayloadStatusV1{Status: engine.INVALID}, engine.InvalidParams.With(errors.New("non-nil blobGasUsed pre-cancun"))
} }
return api.executeStatelessPayload(params, nil, nil, nil, opaqueWitness) return api.executeStatelessPayload(params, nil, nil, nil, opaqueWitness)
} }
@ -763,25 +726,19 @@ func (api *ConsensusAPI) ExecuteStatelessPayloadV2(params engine.ExecutableData,
// ExecuteStatelessPayloadV3 is analogous to NewPayloadV3, only it operates in // ExecuteStatelessPayloadV3 is analogous to NewPayloadV3, only it operates in
// a stateless mode on top of a provided witness instead of the local database. // a stateless mode on top of a provided witness instead of the local database.
func (api *ConsensusAPI) ExecuteStatelessPayloadV3(params engine.ExecutableData, versionedHashes []common.Hash, beaconRoot *common.Hash, opaqueWitness hexutil.Bytes) (engine.StatelessPayloadStatusV1, error) { func (api *ConsensusAPI) ExecuteStatelessPayloadV3(params engine.ExecutableData, versionedHashes []common.Hash, beaconRoot *common.Hash, opaqueWitness hexutil.Bytes) (engine.StatelessPayloadStatusV1, error) {
if params.Withdrawals == nil { switch {
return engine.StatelessPayloadStatusV1{Status: engine.INVALID}, engine.InvalidParams.With(errors.New("nil withdrawals post-shanghai")) case params.Withdrawals == nil:
} return engine.StatelessPayloadStatusV1{Status: engine.INVALID}, paramsErr("nil withdrawals post-shanghai")
if params.ExcessBlobGas == nil { case params.ExcessBlobGas == nil:
return engine.StatelessPayloadStatusV1{Status: engine.INVALID}, engine.InvalidParams.With(errors.New("nil excessBlobGas post-cancun")) return engine.StatelessPayloadStatusV1{Status: engine.INVALID}, paramsErr("nil excessBlobGas post-cancun")
} case params.BlobGasUsed == nil:
if params.BlobGasUsed == nil { return engine.StatelessPayloadStatusV1{Status: engine.INVALID}, paramsErr("nil blobGasUsed post-cancun")
return engine.StatelessPayloadStatusV1{Status: engine.INVALID}, engine.InvalidParams.With(errors.New("nil blobGasUsed post-cancun")) case versionedHashes == nil:
} return engine.StatelessPayloadStatusV1{Status: engine.INVALID}, paramsErr("nil versionedHashes post-cancun")
case beaconRoot == nil:
if versionedHashes == nil { return engine.StatelessPayloadStatusV1{Status: engine.INVALID}, paramsErr("nil beaconRoot post-cancun")
return engine.StatelessPayloadStatusV1{Status: engine.INVALID}, engine.InvalidParams.With(errors.New("nil versionedHashes post-cancun")) case !api.checkFork(params.Timestamp, forks.Cancun):
} return engine.StatelessPayloadStatusV1{Status: engine.INVALID}, unsupportedForkErr("newPayloadV3 must only be called for cancun payloads")
if beaconRoot == nil {
return engine.StatelessPayloadStatusV1{Status: engine.INVALID}, engine.InvalidParams.With(errors.New("nil beaconRoot post-cancun"))
}
if api.eth.BlockChain().Config().LatestFork(params.Timestamp) != forks.Cancun {
return engine.StatelessPayloadStatusV1{Status: engine.INVALID}, engine.UnsupportedFork.With(errors.New("executeStatelessPayloadV3 must only be called for cancun payloads"))
} }
return api.executeStatelessPayload(params, versionedHashes, beaconRoot, nil, opaqueWitness) return api.executeStatelessPayload(params, versionedHashes, beaconRoot, nil, opaqueWitness)
} }
@ -789,30 +746,26 @@ func (api *ConsensusAPI) ExecuteStatelessPayloadV3(params engine.ExecutableData,
// ExecuteStatelessPayloadV4 is analogous to NewPayloadV4, only it operates in // ExecuteStatelessPayloadV4 is analogous to NewPayloadV4, only it operates in
// a stateless mode on top of a provided witness instead of the local database. // a stateless mode on top of a provided witness instead of the local database.
func (api *ConsensusAPI) ExecuteStatelessPayloadV4(params engine.ExecutableData, versionedHashes []common.Hash, beaconRoot *common.Hash, executionRequests []hexutil.Bytes, opaqueWitness hexutil.Bytes) (engine.StatelessPayloadStatusV1, error) { func (api *ConsensusAPI) ExecuteStatelessPayloadV4(params engine.ExecutableData, versionedHashes []common.Hash, beaconRoot *common.Hash, executionRequests []hexutil.Bytes, opaqueWitness hexutil.Bytes) (engine.StatelessPayloadStatusV1, error) {
if params.Withdrawals == nil { switch {
return engine.StatelessPayloadStatusV1{Status: engine.INVALID}, engine.InvalidParams.With(errors.New("nil withdrawals post-shanghai")) case params.Withdrawals == nil:
} return engine.StatelessPayloadStatusV1{Status: engine.INVALID}, paramsErr("nil withdrawals post-shanghai")
if params.ExcessBlobGas == nil { case params.ExcessBlobGas == nil:
return engine.StatelessPayloadStatusV1{Status: engine.INVALID}, engine.InvalidParams.With(errors.New("nil excessBlobGas post-cancun")) return engine.StatelessPayloadStatusV1{Status: engine.INVALID}, paramsErr("nil excessBlobGas post-cancun")
} case params.BlobGasUsed == nil:
if params.BlobGasUsed == nil { return engine.StatelessPayloadStatusV1{Status: engine.INVALID}, paramsErr("nil blobGasUsed post-cancun")
return engine.StatelessPayloadStatusV1{Status: engine.INVALID}, engine.InvalidParams.With(errors.New("nil blobGasUsed post-cancun")) case versionedHashes == nil:
} return engine.StatelessPayloadStatusV1{Status: engine.INVALID}, paramsErr("nil versionedHashes post-cancun")
case beaconRoot == nil:
if versionedHashes == nil { return engine.StatelessPayloadStatusV1{Status: engine.INVALID}, paramsErr("nil beaconRoot post-cancun")
return engine.StatelessPayloadStatusV1{Status: engine.INVALID}, engine.InvalidParams.With(errors.New("nil versionedHashes post-cancun")) case executionRequests == nil:
} return engine.StatelessPayloadStatusV1{Status: engine.INVALID}, paramsErr("nil executionRequests post-prague")
if beaconRoot == nil { case !api.checkFork(params.Timestamp, forks.Prague):
return engine.StatelessPayloadStatusV1{Status: engine.INVALID}, engine.InvalidParams.With(errors.New("nil beaconRoot post-cancun")) return engine.StatelessPayloadStatusV1{Status: engine.INVALID}, unsupportedForkErr("newPayloadV3 must only be called for cancun payloads")
}
if executionRequests == nil {
return engine.StatelessPayloadStatusV1{Status: engine.INVALID}, engine.InvalidParams.With(errors.New("nil executionRequests post-prague"))
}
if api.eth.BlockChain().Config().LatestFork(params.Timestamp) != forks.Prague {
return engine.StatelessPayloadStatusV1{Status: engine.INVALID}, engine.UnsupportedFork.With(errors.New("executeStatelessPayloadV4 must only be called for prague payloads"))
} }
requests := convertRequests(executionRequests) requests := convertRequests(executionRequests)
if err := validateRequests(requests); err != nil {
return engine.StatelessPayloadStatusV1{Status: engine.INVALID}, engine.InvalidParams.With(err)
}
return api.executeStatelessPayload(params, versionedHashes, beaconRoot, requests, opaqueWitness) return api.executeStatelessPayload(params, versionedHashes, beaconRoot, requests, opaqueWitness)
} }
@ -978,7 +931,7 @@ func (api *ConsensusAPI) executeStatelessPayload(params engine.ExecutableData, v
api.lastNewPayloadLock.Unlock() api.lastNewPayloadLock.Unlock()
log.Trace("Executing block statelessly", "number", block.Number(), "hash", params.BlockHash) log.Trace("Executing block statelessly", "number", block.Number(), "hash", params.BlockHash)
stateRoot, receiptRoot, err := core.ExecuteStateless(api.eth.BlockChain().Config(), vm.Config{}, block, witness) stateRoot, receiptRoot, err := core.ExecuteStateless(api.config(), vm.Config{}, block, witness)
if err != nil { if err != nil {
log.Warn("ExecuteStatelessPayload: execution failed", "err", err) log.Warn("ExecuteStatelessPayload: execution failed", "err", err)
errorMsg := err.Error() errorMsg := err.Error()
@ -1116,7 +1069,7 @@ func (api *ConsensusAPI) heartbeat() {
time.Sleep(beaconUpdateStartupTimeout) time.Sleep(beaconUpdateStartupTimeout)
// If the network is not yet merged/merging, don't bother continuing. // If the network is not yet merged/merging, don't bother continuing.
if api.eth.BlockChain().Config().TerminalTotalDifficulty == nil { if api.config().TerminalTotalDifficulty == nil {
return return
} }
@ -1160,6 +1113,23 @@ func (api *ConsensusAPI) heartbeat() {
} }
} }
// config retrieves the chain's fork configuration.
func (api *ConsensusAPI) config() *params.ChainConfig {
return api.eth.BlockChain().Config()
}
// checkFork returns true if the latest fork at the given timestamp
// is one of the forks provided.
func (api *ConsensusAPI) checkFork(timestamp uint64, forks ...forks.Fork) bool {
latest := api.config().LatestFork(timestamp)
for _, fork := range forks {
if latest == fork {
return true
}
}
return false
}
// ExchangeCapabilities returns the current methods provided by this node. // ExchangeCapabilities returns the current methods provided by this node.
func (api *ConsensusAPI) ExchangeCapabilities([]string) []string { func (api *ConsensusAPI) ExchangeCapabilities([]string) []string {
return caps return caps
@ -1285,3 +1255,21 @@ func validateRequests(requests [][]byte) error {
} }
return nil return nil
} }
// paramsErr is a helper function for creating an InvalidPayloadAttributes
// Engine API error.
func paramsErr(msg string) error {
return engine.InvalidParams.With(errors.New(msg))
}
// attributesErr is a helper function for creating an InvalidPayloadAttributes
// Engine API error.
func attributesErr(msg string) error {
return engine.InvalidPayloadAttributes.With(errors.New(msg))
}
// unsupportedForkErr is a helper function for creating an UnsupportedFork
// Engine API error.
func unsupportedForkErr(msg string) error {
return engine.UnsupportedFork.With(errors.New(msg))
}

View file

@ -36,6 +36,7 @@ import (
"github.com/ethereum/go-ethereum/event" "github.com/ethereum/go-ethereum/event"
"github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/log"
"github.com/ethereum/go-ethereum/params" "github.com/ethereum/go-ethereum/params"
"github.com/ethereum/go-ethereum/rlp"
"github.com/ethereum/go-ethereum/triedb" "github.com/ethereum/go-ethereum/triedb"
) )
@ -202,7 +203,7 @@ type BlockChain interface {
// into the local chain. Blocks older than the specified `ancientLimit` // into the local chain. Blocks older than the specified `ancientLimit`
// are stored directly in the ancient store, while newer blocks are stored // are stored directly in the ancient store, while newer blocks are stored
// in the live key-value store. // in the live key-value store.
InsertReceiptChain(types.Blocks, []types.Receipts, uint64) (int, error) InsertReceiptChain(types.Blocks, []rlp.RawValue, uint64) (int, error)
// Snapshots returns the blockchain snapshot tree to paused it during sync. // Snapshots returns the blockchain snapshot tree to paused it during sync.
Snapshots() *snapshot.Tree Snapshots() *snapshot.Tree
@ -1034,7 +1035,7 @@ func (d *Downloader) commitSnapSyncData(results []*fetchResult, stateSync *state
"lastnumn", last.Number, "lasthash", last.Hash(), "lastnumn", last.Number, "lasthash", last.Hash(),
) )
blocks := make([]*types.Block, len(results)) blocks := make([]*types.Block, len(results))
receipts := make([]types.Receipts, len(results)) receipts := make([]rlp.RawValue, len(results))
for i, result := range results { for i, result := range results {
blocks[i] = types.NewBlockWithHeader(result.Header).WithBody(result.body()) blocks[i] = types.NewBlockWithHeader(result.Header).WithBody(result.body())
receipts[i] = result.Receipts receipts[i] = result.Receipts
@ -1051,7 +1052,7 @@ func (d *Downloader) commitPivotBlock(result *fetchResult) error {
log.Debug("Committing snap sync pivot as new head", "number", block.Number(), "hash", block.Hash()) log.Debug("Committing snap sync pivot as new head", "number", block.Number(), "hash", block.Hash())
// Commit the pivot block as the new head, will require full sync from here on // Commit the pivot block as the new head, will require full sync from here on
if _, err := d.blockchain.InsertReceiptChain([]*types.Block{block}, []types.Receipts{result.Receipts}, d.ancientLimit); err != nil { if _, err := d.blockchain.InsertReceiptChain([]*types.Block{block}, []rlp.RawValue{result.Receipts}, d.ancientLimit); err != nil {
return err return err
} }
if err := d.blockchain.SnapSyncCommitHead(block.Hash()); err != nil { if err := d.blockchain.SnapSyncCommitHead(block.Hash()); err != nil {

View file

@ -255,23 +255,24 @@ func (dlp *downloadTesterPeer) RequestBodies(hashes []common.Hash, sink chan *et
// peer in the download tester. The returned function can be used to retrieve // peer in the download tester. The returned function can be used to retrieve
// batches of block receipts from the particularly requested peer. // batches of block receipts from the particularly requested peer.
func (dlp *downloadTesterPeer) RequestReceipts(hashes []common.Hash, sink chan *eth.Response) (*eth.Request, error) { func (dlp *downloadTesterPeer) RequestReceipts(hashes []common.Hash, sink chan *eth.Response) (*eth.Request, error) {
blobs := eth.ServiceGetReceiptsQuery(dlp.chain, hashes) blobs := eth.ServiceGetReceiptsQuery68(dlp.chain, hashes)
receipts := make([][]*types.Receipt, len(blobs)) receipts := make([]types.Receipts, len(blobs))
for i, blob := range blobs { for i, blob := range blobs {
rlp.DecodeBytes(blob, &receipts[i]) rlp.DecodeBytes(blob, &receipts[i])
} }
hasher := trie.NewStackTrie(nil) hasher := trie.NewStackTrie(nil)
hashes = make([]common.Hash, len(receipts)) hashes = make([]common.Hash, len(receipts))
for i, receipt := range receipts { for i, receipt := range receipts {
hashes[i] = types.DeriveSha(types.Receipts(receipt), hasher) hashes[i] = types.DeriveSha(receipt, hasher)
} }
req := &eth.Request{ req := &eth.Request{
Peer: dlp.id, Peer: dlp.id,
} }
resp := eth.ReceiptsRLPResponse(types.EncodeBlockReceiptLists(receipts))
res := &eth.Response{ res := &eth.Response{
Req: req, Req: req,
Res: (*eth.ReceiptsResponse)(&receipts), Res: &resp,
Meta: hashes, Meta: hashes,
Time: 1, Time: 1,
Done: make(chan error, 1), // Ignore the returned status Done: make(chan error, 1), // Ignore the returned status

View file

@ -88,7 +88,7 @@ func (q *receiptQueue) request(peer *peerConnection, req *fetchRequest, resCh ch
// deliver is responsible for taking a generic response packet from the concurrent // deliver is responsible for taking a generic response packet from the concurrent
// fetcher, unpacking the receipt data and delivering it to the downloader's queue. // fetcher, unpacking the receipt data and delivering it to the downloader's queue.
func (q *receiptQueue) deliver(peer *peerConnection, packet *eth.Response) (int, error) { func (q *receiptQueue) deliver(peer *peerConnection, packet *eth.Response) (int, error) {
receipts := *packet.Res.(*eth.ReceiptsResponse) receipts := *packet.Res.(*eth.ReceiptsRLPResponse)
hashes := packet.Meta.([]common.Hash) // {receipt hashes} hashes := packet.Meta.([]common.Hash) // {receipt hashes}
accepted, err := q.queue.DeliverReceipts(peer.id, receipts, hashes) accepted, err := q.queue.DeliverReceipts(peer.id, receipts, hashes)

Some files were not shown because too many files have changed in this diff Show more