Merge branch 'ethereum:master' into master

This commit is contained in:
crStiv 2025-04-03 01:22:36 +03:00 committed by GitHub
commit 0427856858
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
88 changed files with 1970 additions and 726 deletions

13
.gitignore vendored
View file

@ -43,3 +43,16 @@ profile.cov
.vscode .vscode
tests/spec-tests/ tests/spec-tests/
# binaries
cmd/abidump/abidump
cmd/abigen/abigen
cmd/blsync/blsync
cmd/clef/clef
cmd/devp2p/devp2p
cmd/era/era
cmd/ethkey/ethkey
cmd/evm/evm
cmd/geth/geth
cmd/rlpdump/rlpdump
cmd/workload/workload

View file

@ -2,12 +2,6 @@ language: go
go_import_path: github.com/ethereum/go-ethereum go_import_path: github.com/ethereum/go-ethereum
sudo: false sudo: false
jobs: jobs:
allow_failures:
- stage: build
os: osx
env:
- azure-osx
include: include:
# This builder create and push the Docker images for all architectures # This builder create and push the Docker images for all architectures
- stage: build - stage: build
@ -62,23 +56,6 @@ jobs:
- go run build/ci.go install -dlgo -arch arm64 -cc aarch64-linux-gnu-gcc - go run build/ci.go install -dlgo -arch arm64 -cc aarch64-linux-gnu-gcc
- go run build/ci.go archive -arch arm64 -type tar -signer LINUX_SIGNING_KEY -signify SIGNIFY_KEY -upload gethstore/builds - go run build/ci.go archive -arch arm64 -type tar -signer LINUX_SIGNING_KEY -signify SIGNIFY_KEY -upload gethstore/builds
# This builder does the OSX Azure uploads
- stage: build
if: type = push
os: osx
osx_image: xcode14.2
go: 1.23.1 # See https://github.com/ethereum/go-ethereum/pull/30478
env:
- azure-osx
git:
submodules: false # avoid cloning ethereum/tests
script:
- ln -sf /Users/travis/gopath/bin/go1.23.1 /usr/local/bin/go # Work around travis go-setup bug
- go run build/ci.go install -dlgo
- go run build/ci.go archive -type tar -signer OSX_SIGNING_KEY -signify SIGNIFY_KEY -upload gethstore/builds
- go run build/ci.go install -dlgo -arch arm64
- go run build/ci.go archive -arch arm64 -type tar -signer OSX_SIGNING_KEY -signify SIGNIFY_KEY -upload gethstore/builds
# These builders run the tests # These builders run the tests
- stage: build - stage: build
if: type = push if: type = push

View file

@ -541,7 +541,7 @@ var bindTests = []struct {
struct A { struct A {
bytes32 B; bytes32 B;
} }
function F() public view returns (A[] memory a, uint256[] memory c, bool[] memory d) { function F() public view returns (A[] memory a, uint256[] memory c, bool[] memory d) {
A[] memory a = new A[](2); A[] memory a = new A[](2);
a[0].B = bytes32(uint256(1234) << 96); a[0].B = bytes32(uint256(1234) << 96);
@ -549,7 +549,7 @@ var bindTests = []struct {
bool[] memory d; bool[] memory d;
return (a, c, d); return (a, c, d);
} }
function G() public view returns (A[] memory a) { function G() public view returns (A[] memory a) {
A[] memory a = new A[](2); A[] memory a = new A[](2);
a[0].B = bytes32(uint256(1234) << 96); a[0].B = bytes32(uint256(1234) << 96);
@ -571,10 +571,10 @@ var bindTests = []struct {
// Generate a new random account and a funded simulator // Generate a new random account and a funded simulator
key, _ := crypto.GenerateKey() key, _ := crypto.GenerateKey()
auth, _ := bind.NewKeyedTransactorWithChainID(key, big.NewInt(1337)) auth, _ := bind.NewKeyedTransactorWithChainID(key, big.NewInt(1337))
sim := backends.NewSimulatedBackend(types.GenesisAlloc{auth.From: {Balance: big.NewInt(10000000000000000)}}, 10000000) sim := backends.NewSimulatedBackend(types.GenesisAlloc{auth.From: {Balance: big.NewInt(10000000000000000)}}, 10000000)
defer sim.Close() defer sim.Close()
// Deploy a structs method invoker contract and execute its default method // Deploy a structs method invoker contract and execute its default method
_, _, structs, err := DeployStructs(auth, sim) _, _, structs, err := DeployStructs(auth, sim)
if err != nil { if err != nil {
@ -1701,13 +1701,13 @@ var bindTests = []struct {
`NewFallbacks`, `NewFallbacks`,
` `
pragma solidity >=0.6.0 <0.7.0; pragma solidity >=0.6.0 <0.7.0;
contract NewFallbacks { contract NewFallbacks {
event Fallback(bytes data); event Fallback(bytes data);
fallback() external { fallback() external {
emit Fallback(msg.data); emit Fallback(msg.data);
} }
event Received(address addr, uint value); event Received(address addr, uint value);
receive() external payable { receive() external payable {
emit Received(msg.sender, msg.value); emit Received(msg.sender, msg.value);
@ -1719,7 +1719,7 @@ var bindTests = []struct {
` `
"bytes" "bytes"
"math/big" "math/big"
"github.com/ethereum/go-ethereum/accounts/abi/bind" "github.com/ethereum/go-ethereum/accounts/abi/bind"
"github.com/ethereum/go-ethereum/accounts/abi/bind/backends" "github.com/ethereum/go-ethereum/accounts/abi/bind/backends"
"github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/core/types"
@ -1728,22 +1728,22 @@ var bindTests = []struct {
` `
key, _ := crypto.GenerateKey() key, _ := crypto.GenerateKey()
addr := crypto.PubkeyToAddress(key.PublicKey) addr := crypto.PubkeyToAddress(key.PublicKey)
sim := backends.NewSimulatedBackend(types.GenesisAlloc{addr: {Balance: big.NewInt(10000000000000000)}}, 1000000) sim := backends.NewSimulatedBackend(types.GenesisAlloc{addr: {Balance: big.NewInt(10000000000000000)}}, 1000000)
defer sim.Close() defer sim.Close()
opts, _ := bind.NewKeyedTransactorWithChainID(key, big.NewInt(1337)) opts, _ := bind.NewKeyedTransactorWithChainID(key, big.NewInt(1337))
_, _, c, err := DeployNewFallbacks(opts, sim) _, _, c, err := DeployNewFallbacks(opts, sim)
if err != nil { if err != nil {
t.Fatalf("Failed to deploy contract: %v", err) t.Fatalf("Failed to deploy contract: %v", err)
} }
sim.Commit() sim.Commit()
// Test receive function // Test receive function
opts.Value = big.NewInt(100) opts.Value = big.NewInt(100)
c.Receive(opts) c.Receive(opts)
sim.Commit() sim.Commit()
var gotEvent bool var gotEvent bool
iter, _ := c.FilterReceived(nil) iter, _ := c.FilterReceived(nil)
defer iter.Close() defer iter.Close()
@ -1760,14 +1760,14 @@ var bindTests = []struct {
if !gotEvent { if !gotEvent {
t.Fatal("Expect to receive event emitted by receive") t.Fatal("Expect to receive event emitted by receive")
} }
// Test fallback function // Test fallback function
gotEvent = false gotEvent = false
opts.Value = nil opts.Value = nil
calldata := []byte{0x01, 0x02, 0x03} calldata := []byte{0x01, 0x02, 0x03}
c.Fallback(opts, calldata) c.Fallback(opts, calldata)
sim.Commit() sim.Commit()
iter2, _ := c.FilterFallback(nil) iter2, _ := c.FilterFallback(nil)
defer iter2.Close() defer iter2.Close()
for iter2.Next() { for iter2.Next() {
@ -1806,7 +1806,9 @@ var bindTests = []struct {
[]string{"608060405234801561001057600080fd5b50610113806100206000396000f3fe6080604052348015600f57600080fd5b506004361060285760003560e01c806324ec1d3f14602d575b600080fd5b60336035565b005b7fb4b2ff75e30cb4317eaae16dd8a187dd89978df17565104caa6c2797caae27d460405180604001604052806001815260200160028152506040516078919060ba565b60405180910390a1565b6040820160008201516096600085018260ad565b50602082015160a7602085018260ad565b50505050565b60b48160d3565b82525050565b600060408201905060cd60008301846082565b92915050565b600081905091905056fea26469706673582212208823628796125bf9941ce4eda18da1be3cf2931b231708ab848e1bd7151c0c9a64736f6c63430008070033"}, []string{"608060405234801561001057600080fd5b50610113806100206000396000f3fe6080604052348015600f57600080fd5b506004361060285760003560e01c806324ec1d3f14602d575b600080fd5b60336035565b005b7fb4b2ff75e30cb4317eaae16dd8a187dd89978df17565104caa6c2797caae27d460405180604001604052806001815260200160028152506040516078919060ba565b60405180910390a1565b6040820160008201516096600085018260ad565b50602082015160a7602085018260ad565b50505050565b60b48160d3565b82525050565b600060408201905060cd60008301846082565b92915050565b600081905091905056fea26469706673582212208823628796125bf9941ce4eda18da1be3cf2931b231708ab848e1bd7151c0c9a64736f6c63430008070033"},
[]string{`[{"anonymous":false,"inputs":[{"components":[{"internalType":"uint256","name":"a","type":"uint256"},{"internalType":"uint256","name":"b","type":"uint256"}],"indexed":false,"internalType":"struct Test.MyStruct","name":"s","type":"tuple"}],"name":"StructEvent","type":"event"},{"inputs":[],"name":"TestEvent","outputs":[],"stateMutability":"nonpayable","type":"function"}]`}, []string{`[{"anonymous":false,"inputs":[{"components":[{"internalType":"uint256","name":"a","type":"uint256"},{"internalType":"uint256","name":"b","type":"uint256"}],"indexed":false,"internalType":"struct Test.MyStruct","name":"s","type":"tuple"}],"name":"StructEvent","type":"event"},{"inputs":[],"name":"TestEvent","outputs":[],"stateMutability":"nonpayable","type":"function"}]`},
` `
"context"
"math/big" "math/big"
"time"
"github.com/ethereum/go-ethereum/accounts/abi/bind" "github.com/ethereum/go-ethereum/accounts/abi/bind"
"github.com/ethereum/go-ethereum/accounts/abi/bind/backends" "github.com/ethereum/go-ethereum/accounts/abi/bind/backends"
@ -1828,12 +1830,23 @@ var bindTests = []struct {
} }
sim.Commit() sim.Commit()
_, err = d.TestEvent(user) tx, err := d.TestEvent(user)
if err != nil { if err != nil {
t.Fatalf("Failed to call contract %v", err) t.Fatalf("Failed to call contract %v", err)
} }
sim.Commit() sim.Commit()
// Wait for the transaction to be mined
ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)
defer cancel()
receipt, err := bind.WaitMined(ctx, sim, tx)
if err != nil {
t.Fatalf("Failed to wait for tx to be mined: %v", err)
}
if receipt.Status != types.ReceiptStatusSuccessful {
t.Fatal("Transaction failed")
}
it, err := d.FilterStructEvent(nil) it, err := d.FilterStructEvent(nil)
if err != nil { if err != nil {
t.Fatalf("Failed to filter contract event %v", err) t.Fatalf("Failed to filter contract event %v", err)
@ -1862,7 +1875,7 @@ var bindTests = []struct {
`NewErrors`, `NewErrors`,
` `
pragma solidity >0.8.4; pragma solidity >0.8.4;
contract NewErrors { contract NewErrors {
error MyError(uint256); error MyError(uint256);
error MyError1(uint256); error MyError1(uint256);
@ -1878,7 +1891,7 @@ var bindTests = []struct {
` `
"context" "context"
"math/big" "math/big"
"github.com/ethereum/go-ethereum/accounts/abi/bind" "github.com/ethereum/go-ethereum/accounts/abi/bind"
"github.com/ethereum/go-ethereum/accounts/abi/bind/backends" "github.com/ethereum/go-ethereum/accounts/abi/bind/backends"
"github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/core/types"
@ -1892,7 +1905,7 @@ var bindTests = []struct {
sim = backends.NewSimulatedBackend(types.GenesisAlloc{user.From: {Balance: big.NewInt(1000000000000000000)}}, ethconfig.Defaults.Miner.GasCeil) sim = backends.NewSimulatedBackend(types.GenesisAlloc{user.From: {Balance: big.NewInt(1000000000000000000)}}, ethconfig.Defaults.Miner.GasCeil)
) )
defer sim.Close() defer sim.Close()
_, tx, contract, err := DeployNewErrors(user, sim) _, tx, contract, err := DeployNewErrors(user, sim)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
@ -1917,12 +1930,12 @@ var bindTests = []struct {
name: `ConstructorWithStructParam`, name: `ConstructorWithStructParam`,
contract: ` contract: `
pragma solidity >=0.8.0 <0.9.0; pragma solidity >=0.8.0 <0.9.0;
contract ConstructorWithStructParam { contract ConstructorWithStructParam {
struct StructType { struct StructType {
uint256 field; uint256 field;
} }
constructor(StructType memory st) {} constructor(StructType memory st) {}
} }
`, `,
@ -1951,7 +1964,7 @@ var bindTests = []struct {
t.Fatalf("DeployConstructorWithStructParam() got err %v; want nil err", err) t.Fatalf("DeployConstructorWithStructParam() got err %v; want nil err", err)
} }
sim.Commit() sim.Commit()
if _, err = bind.WaitDeployed(context.Background(), sim, tx); err != nil { if _, err = bind.WaitDeployed(context.Background(), sim, tx); err != nil {
t.Logf("Deployment tx: %+v", tx) t.Logf("Deployment tx: %+v", tx)
t.Errorf("bind.WaitDeployed(nil, %T, <deployment tx>) got err %v; want nil err", sim, err) t.Errorf("bind.WaitDeployed(nil, %T, <deployment tx>) got err %v; want nil err", sim, err)
@ -2000,7 +2013,7 @@ var bindTests = []struct {
t.Fatalf("DeployNameConflict() got err %v; want nil err", err) t.Fatalf("DeployNameConflict() got err %v; want nil err", err)
} }
sim.Commit() sim.Commit()
if _, err = bind.WaitDeployed(context.Background(), sim, tx); err != nil { if _, err = bind.WaitDeployed(context.Background(), sim, tx); err != nil {
t.Logf("Deployment tx: %+v", tx) t.Logf("Deployment tx: %+v", tx)
t.Errorf("bind.WaitDeployed(nil, %T, <deployment tx>) got err %v; want nil err", sim, err) t.Errorf("bind.WaitDeployed(nil, %T, <deployment tx>) got err %v; want nil err", sim, err)

View file

@ -383,13 +383,14 @@ func (c *BoundContract) estimateGasLimit(opts *TransactOpts, contract *common.Ad
} }
} }
msg := ethereum.CallMsg{ msg := ethereum.CallMsg{
From: opts.From, From: opts.From,
To: contract, To: contract,
GasPrice: gasPrice, GasPrice: gasPrice,
GasTipCap: gasTipCap, GasTipCap: gasTipCap,
GasFeeCap: gasFeeCap, GasFeeCap: gasFeeCap,
Value: value, Value: value,
Data: input, Data: input,
AccessList: opts.AccessList,
} }
return c.transactor.EstimateGas(ensureContext(opts.Context), msg) return c.transactor.EstimateGas(ensureContext(opts.Context), msg)
} }

View file

@ -0,0 +1 @@
0xf5606a019f0f1006e9ec2070695045f4334450362a48da4c5965314510e0b4c3

View file

@ -0,0 +1 @@
0x3c0cb4aa83beded1803d262664ba4392b1023f334d9cca02dc3a6925987ebe91

View file

@ -0,0 +1 @@
0xa8d56457aa414523d93659aef1f7409bbfb72ad75e94d917c8c0b1baa38153ef

View file

@ -17,14 +17,25 @@
package params package params
import ( import (
_ "embed"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
) )
//go:embed checkpoint_mainnet.hex
var checkpointMainnet string
//go:embed checkpoint_sepolia.hex
var checkpointSepolia string
//go:embed checkpoint_holesky.hex
var checkpointHolesky string
var ( var (
MainnetLightConfig = (&ChainConfig{ MainnetLightConfig = (&ChainConfig{
GenesisValidatorsRoot: common.HexToHash("0x4b363db94e286120d76eb905340fdd4e54bfe9f06bf33ff6cf5ad27f511bfe95"), GenesisValidatorsRoot: common.HexToHash("0x4b363db94e286120d76eb905340fdd4e54bfe9f06bf33ff6cf5ad27f511bfe95"),
GenesisTime: 1606824023, GenesisTime: 1606824023,
Checkpoint: common.HexToHash("0x6509b691f4de4f7b083f2784938fd52f0e131675432b3fd85ea549af9aebd3d0"), Checkpoint: common.HexToHash(checkpointMainnet),
}). }).
AddFork("GENESIS", 0, []byte{0, 0, 0, 0}). AddFork("GENESIS", 0, []byte{0, 0, 0, 0}).
AddFork("ALTAIR", 74240, []byte{1, 0, 0, 0}). AddFork("ALTAIR", 74240, []byte{1, 0, 0, 0}).
@ -35,7 +46,7 @@ var (
SepoliaLightConfig = (&ChainConfig{ SepoliaLightConfig = (&ChainConfig{
GenesisValidatorsRoot: common.HexToHash("0xd8ea171f3c94aea21ebc42a1ed61052acf3f9209c00e4efbaaddac09ed9b8078"), GenesisValidatorsRoot: common.HexToHash("0xd8ea171f3c94aea21ebc42a1ed61052acf3f9209c00e4efbaaddac09ed9b8078"),
GenesisTime: 1655733600, GenesisTime: 1655733600,
Checkpoint: common.HexToHash("0x456e85f5608afab3465a0580bff8572255f6d97af0c5f939e3f7536b5edb2d3f"), Checkpoint: common.HexToHash(checkpointSepolia),
}). }).
AddFork("GENESIS", 0, []byte{144, 0, 0, 105}). AddFork("GENESIS", 0, []byte{144, 0, 0, 105}).
AddFork("ALTAIR", 50, []byte{144, 0, 0, 112}). AddFork("ALTAIR", 50, []byte{144, 0, 0, 112}).
@ -47,7 +58,7 @@ var (
HoleskyLightConfig = (&ChainConfig{ HoleskyLightConfig = (&ChainConfig{
GenesisValidatorsRoot: common.HexToHash("0x9143aa7c615a7f7115e2b6aac319c03529df8242ae705fba9df39b79c59fa8b1"), GenesisValidatorsRoot: common.HexToHash("0x9143aa7c615a7f7115e2b6aac319c03529df8242ae705fba9df39b79c59fa8b1"),
GenesisTime: 1695902400, GenesisTime: 1695902400,
Checkpoint: common.HexToHash("0x6456a1317f54d4b4f2cb5bc9d153b5af0988fe767ef0609f0236cf29030bcff7"), Checkpoint: common.HexToHash(checkpointHolesky),
}). }).
AddFork("GENESIS", 0, []byte{1, 1, 112, 0}). AddFork("GENESIS", 0, []byte{1, 1, 112, 0}).
AddFork("ALTAIR", 0, []byte{2, 1, 112, 0}). AddFork("ALTAIR", 0, []byte{2, 1, 112, 0}).

View file

@ -1,6 +1,6 @@
# Clef # Clef
Clef can be used to sign transactions and data and is meant as a(n eventual) replacement for Geth's account management. This allows DApps to not depend on Geth's account management. When a DApp wants to sign data (or a transaction), it can send the content to Clef, which will then provide the user with context and asks for permission to sign the content. If the users grants the signing request, Clef will send the signature back to the DApp. Clef can be used to sign transactions and data and is meant as a(n eventual) replacement for Geth's account management. This allows DApps to not depend on Geth's account management. When a DApp wants to sign data (or a transaction), it can send the content to Clef, which will then provide the user with context and ask for permission to sign the content. If the user grants the signing request, Clef will send the signature back to the DApp.
This setup allows a DApp to connect to a remote Ethereum node and send transactions that are locally signed. This can help in situations when a DApp is connected to an untrusted remote Ethereum node, because a local one is not available, not synchronized with the chain, or is a node that has no built-in (or limited) account management. This setup allows a DApp to connect to a remote Ethereum node and send transactions that are locally signed. This can help in situations when a DApp is connected to an untrusted remote Ethereum node, because a local one is not available, not synchronized with the chain, or is a node that has no built-in (or limited) account management.
@ -123,7 +123,7 @@ The External API is **untrusted**: it does not accept credentials, nor does it e
Clef has one native console-based UI, for operation without any standalone tools. However, there is also an API to communicate with an external UI. To enable that UI, the signer needs to be executed with the `--stdio-ui` option, which allocates `stdin` / `stdout` for the UI API. Clef has one native console-based UI, for operation without any standalone tools. However, there is also an API to communicate with an external UI. To enable that UI, the signer needs to be executed with the `--stdio-ui` option, which allocates `stdin` / `stdout` for the UI API.
An example (insecure) proof-of-concept of has been implemented in `pythonsigner.py`. An example (insecure) proof-of-concept has been implemented in `pythonsigner.py`.
The model is as follows: The model is as follows:
@ -335,7 +335,7 @@ Bash example:
#### Arguments #### Arguments
- content type [string]: type of signed data - content type [string]: type of signed data
- `text/validator`: hex data with custom validator defined in a contract - `text/validator`: hex data with a custom validator defined in a contract
- `application/clique`: [clique](https://github.com/ethereum/EIPs/issues/225) headers - `application/clique`: [clique](https://github.com/ethereum/EIPs/issues/225) headers
- `text/plain`: simple hex data validated by `account_ecRecover` - `text/plain`: simple hex data validated by `account_ecRecover`
- account [address]: account to sign with - account [address]: account to sign with

View file

@ -2,7 +2,7 @@
The `signer` binary contains a ruleset engine, implemented with [OttoVM](https://github.com/robertkrimen/otto) The `signer` binary contains a ruleset engine, implemented with [OttoVM](https://github.com/robertkrimen/otto)
It enables usecases like the following: It enables use cases like the following:
* I want to auto-approve transactions with contract `CasinoDapp`, with up to `0.05 ether` in value to maximum `1 ether` per 24h period * I want to auto-approve transactions with contract `CasinoDapp`, with up to `0.05 ether` in value to maximum `1 ether` per 24h period
* I want to auto-approve transaction to contract `EthAlarmClock` with `data`=`0xdeadbeef`, if `value=0`, `gas < 44k` and `gasPrice < 40Gwei` * I want to auto-approve transaction to contract `EthAlarmClock` with `data`=`0xdeadbeef`, if `value=0`, `gas < 44k` and `gasPrice < 40Gwei`

View file

@ -16,7 +16,7 @@ which can
1. Take a prestate, including 1. Take a prestate, including
- Accounts, - Accounts,
- Block context information, - Block context information,
- Previous blockshashes (*optional) - Previous block hashes (*optional)
2. Apply a set of transactions, 2. Apply a set of transactions,
3. Apply a mining-reward (*optional), 3. Apply a mining-reward (*optional),
4. And generate a post-state, including 4. And generate a post-state, including

View file

@ -22,7 +22,7 @@ import "regexp"
// within its filename by the execution spec test (EEST). // within its filename by the execution spec test (EEST).
type testMetadata struct { type testMetadata struct {
fork string fork string
module string // which python module gnerated the test, e.g. eip7702 module string // which python module generated the test, e.g. eip7702
file string // exact file the test came from, e.g. test_gas.py file string // exact file the test came from, e.g. test_gas.py
function string // func that created the test, e.g. test_valid_mcopy_operations function string // func that created the test, e.g. test_valid_mcopy_operations
parameters string // the name of the parameters which were used to fill the test, e.g. zero_inputs parameters string // the name of the parameters which were used to fill the test, e.g. zero_inputs

View file

@ -37,7 +37,9 @@ import (
"github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/eth/ethconfig" "github.com/ethereum/go-ethereum/eth/ethconfig"
"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/era" "github.com/ethereum/go-ethereum/internal/era"
"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"
"github.com/urfave/cli/v2" "github.com/urfave/cli/v2"
@ -66,7 +68,7 @@ It expects the genesis file as argument.`,
Name: "dumpgenesis", Name: "dumpgenesis",
Usage: "Dumps genesis block JSON configuration to stdout", Usage: "Dumps genesis block JSON configuration to stdout",
ArgsUsage: "", ArgsUsage: "",
Flags: append([]cli.Flag{utils.DataDirFlag}, utils.NetworkFlags...), Flags: slices.Concat([]cli.Flag{utils.DataDirFlag}, utils.NetworkFlags),
Description: ` Description: `
The dumpgenesis command prints the genesis configuration of the network preset The dumpgenesis command prints the genesis configuration of the network preset
if one is set. Otherwise it prints the genesis from the datadir.`, if one is set. Otherwise it prints the genesis from the datadir.`,
@ -78,11 +80,11 @@ if one is set. Otherwise it prints the genesis from the datadir.`,
ArgsUsage: "<filename> (<filename 2> ... <filename N>) ", ArgsUsage: "<filename> (<filename 2> ... <filename N>) ",
Flags: slices.Concat([]cli.Flag{ Flags: slices.Concat([]cli.Flag{
utils.CacheFlag, utils.CacheFlag,
utils.SyncModeFlag,
utils.GCModeFlag, utils.GCModeFlag,
utils.SnapshotFlag, utils.SnapshotFlag,
utils.CacheDatabaseFlag, utils.CacheDatabaseFlag,
utils.CacheGCFlag, utils.CacheGCFlag,
utils.NoCompactionFlag,
utils.MetricsEnabledFlag, utils.MetricsEnabledFlag,
utils.MetricsEnabledExpensiveFlag, utils.MetricsEnabledExpensiveFlag,
utils.MetricsHTTPFlag, utils.MetricsHTTPFlag,
@ -105,7 +107,11 @@ if one is set. Otherwise it prints the genesis from the datadir.`,
utils.LogNoHistoryFlag, utils.LogNoHistoryFlag,
utils.LogExportCheckpointsFlag, utils.LogExportCheckpointsFlag,
utils.StateHistoryFlag, utils.StateHistoryFlag,
}, utils.DatabaseFlags), }, utils.DatabaseFlags, debug.Flags),
Before: func(ctx *cli.Context) error {
flags.MigrateGlobalFlags(ctx)
return debug.Setup(ctx)
},
Description: ` Description: `
The import command allows the import of blocks from an RLP-encoded format. This format can be a single file The import command allows the import of blocks from an RLP-encoded format. This format can be a single file
containing multiple RLP-encoded blocks, or multiple files can be given. containing multiple RLP-encoded blocks, or multiple files can be given.
@ -119,10 +125,7 @@ to import successfully.`,
Name: "export", Name: "export",
Usage: "Export blockchain into file", Usage: "Export blockchain into file",
ArgsUsage: "<filename> [<blockNumFirst> <blockNumLast>]", ArgsUsage: "<filename> [<blockNumFirst> <blockNumLast>]",
Flags: slices.Concat([]cli.Flag{ Flags: slices.Concat([]cli.Flag{utils.CacheFlag}, utils.DatabaseFlags),
utils.CacheFlag,
utils.SyncModeFlag,
}, utils.DatabaseFlags),
Description: ` Description: `
Requires a first argument of the file to write to. Requires a first argument of the file to write to.
Optional second and third arguments control the first and Optional second and third arguments control the first and
@ -135,12 +138,7 @@ be gzipped.`,
Name: "import-history", Name: "import-history",
Usage: "Import an Era archive", Usage: "Import an Era archive",
ArgsUsage: "<dir>", ArgsUsage: "<dir>",
Flags: slices.Concat([]cli.Flag{ Flags: slices.Concat([]cli.Flag{utils.TxLookupLimitFlag, utils.TransactionHistoryFlag}, utils.DatabaseFlags, utils.NetworkFlags),
utils.TxLookupLimitFlag,
},
utils.DatabaseFlags,
utils.NetworkFlags,
),
Description: ` Description: `
The import-history command will import blocks and their corresponding receipts The import-history command will import blocks and their corresponding receipts
from Era archives. from Era archives.
@ -151,7 +149,7 @@ from Era archives.
Name: "export-history", Name: "export-history",
Usage: "Export blockchain history to Era archives", Usage: "Export blockchain history to Era archives",
ArgsUsage: "<dir> <first> <last>", ArgsUsage: "<dir> <first> <last>",
Flags: slices.Concat(utils.DatabaseFlags), Flags: utils.DatabaseFlags,
Description: ` Description: `
The export-history command will export blocks and their corresponding receipts The export-history command will export blocks and their corresponding receipts
into Era archives. Eras are typically packaged in steps of 8192 blocks. into Era archives. Eras are typically packaged in steps of 8192 blocks.
@ -162,10 +160,7 @@ into Era archives. Eras are typically packaged in steps of 8192 blocks.
Name: "import-preimages", Name: "import-preimages",
Usage: "Import the preimage database from an RLP stream", Usage: "Import the preimage database from an RLP stream",
ArgsUsage: "<datafile>", ArgsUsage: "<datafile>",
Flags: slices.Concat([]cli.Flag{ Flags: slices.Concat([]cli.Flag{utils.CacheFlag}, utils.DatabaseFlags),
utils.CacheFlag,
utils.SyncModeFlag,
}, utils.DatabaseFlags),
Description: ` Description: `
The import-preimages command imports hash preimages from an RLP encoded stream. The import-preimages command imports hash preimages from an RLP encoded stream.
It's deprecated, please use "geth db import" instead. It's deprecated, please use "geth db import" instead.
@ -435,6 +430,10 @@ func importHistory(ctx *cli.Context) error {
network = "mainnet" network = "mainnet"
case ctx.Bool(utils.SepoliaFlag.Name): case ctx.Bool(utils.SepoliaFlag.Name):
network = "sepolia" network = "sepolia"
case ctx.Bool(utils.HoleskyFlag.Name):
network = "holesky"
case ctx.Bool(utils.HoodiFlag.Name):
network = "hoodi"
} }
} else { } else {
// No network flag set, try to determine network based on files // No network flag set, try to determine network based on files

View file

@ -86,12 +86,10 @@ Remove blockchain and state databases`,
}, },
} }
dbInspectCmd = &cli.Command{ dbInspectCmd = &cli.Command{
Action: inspect, Action: inspect,
Name: "inspect", Name: "inspect",
ArgsUsage: "<prefix> <start>", ArgsUsage: "<prefix> <start>",
Flags: slices.Concat([]cli.Flag{ Flags: slices.Concat(utils.NetworkFlags, utils.DatabaseFlags),
utils.SyncModeFlag,
}, utils.NetworkFlags, utils.DatabaseFlags),
Usage: "Inspect the storage size for each type of data in the database", Usage: "Inspect the storage size for each type of data in the database",
Description: `This commands iterates the entire database. If the optional 'prefix' and 'start' arguments are provided, then the iteration is limited to the given subset of data.`, Description: `This commands iterates the entire database. If the optional 'prefix' and 'start' arguments are provided, then the iteration is limited to the given subset of data.`,
} }
@ -109,16 +107,13 @@ a data corruption.`,
Action: dbStats, Action: dbStats,
Name: "stats", Name: "stats",
Usage: "Print leveldb statistics", Usage: "Print leveldb statistics",
Flags: slices.Concat([]cli.Flag{ Flags: slices.Concat(utils.NetworkFlags, utils.DatabaseFlags),
utils.SyncModeFlag,
}, utils.NetworkFlags, utils.DatabaseFlags),
} }
dbCompactCmd = &cli.Command{ dbCompactCmd = &cli.Command{
Action: dbCompact, Action: dbCompact,
Name: "compact", Name: "compact",
Usage: "Compact leveldb database. WARNING: May take a very long time", Usage: "Compact leveldb database. WARNING: May take a very long time",
Flags: slices.Concat([]cli.Flag{ Flags: slices.Concat([]cli.Flag{
utils.SyncModeFlag,
utils.CacheFlag, utils.CacheFlag,
utils.CacheDatabaseFlag, utils.CacheDatabaseFlag,
}, utils.NetworkFlags, utils.DatabaseFlags), }, utils.NetworkFlags, utils.DatabaseFlags),
@ -127,13 +122,11 @@ WARNING: This operation may take a very long time to finish, and may cause datab
corruption if it is aborted during execution'!`, corruption if it is aborted during execution'!`,
} }
dbGetCmd = &cli.Command{ dbGetCmd = &cli.Command{
Action: dbGet, Action: dbGet,
Name: "get", Name: "get",
Usage: "Show the value of a database key", Usage: "Show the value of a database key",
ArgsUsage: "<hex-encoded key>", ArgsUsage: "<hex-encoded key>",
Flags: slices.Concat([]cli.Flag{ Flags: slices.Concat(utils.NetworkFlags, utils.DatabaseFlags),
utils.SyncModeFlag,
}, utils.NetworkFlags, utils.DatabaseFlags),
Description: "This command looks up the specified database key from the database.", Description: "This command looks up the specified database key from the database.",
} }
dbDeleteCmd = &cli.Command{ dbDeleteCmd = &cli.Command{
@ -141,9 +134,7 @@ corruption if it is aborted during execution'!`,
Name: "delete", Name: "delete",
Usage: "Delete a database key (WARNING: may corrupt your database)", Usage: "Delete a database key (WARNING: may corrupt your database)",
ArgsUsage: "<hex-encoded key>", ArgsUsage: "<hex-encoded key>",
Flags: slices.Concat([]cli.Flag{ Flags: slices.Concat(utils.NetworkFlags, utils.DatabaseFlags),
utils.SyncModeFlag,
}, utils.NetworkFlags, utils.DatabaseFlags),
Description: `This command deletes the specified database key from the database. Description: `This command deletes the specified database key from the database.
WARNING: This is a low-level operation which may cause database corruption!`, WARNING: This is a low-level operation which may cause database corruption!`,
} }
@ -152,59 +143,47 @@ WARNING: This is a low-level operation which may cause database corruption!`,
Name: "put", Name: "put",
Usage: "Set the value of a database key (WARNING: may corrupt your database)", Usage: "Set the value of a database key (WARNING: may corrupt your database)",
ArgsUsage: "<hex-encoded key> <hex-encoded value>", ArgsUsage: "<hex-encoded key> <hex-encoded value>",
Flags: slices.Concat([]cli.Flag{ Flags: slices.Concat(utils.NetworkFlags, utils.DatabaseFlags),
utils.SyncModeFlag,
}, utils.NetworkFlags, utils.DatabaseFlags),
Description: `This command sets a given database key to the given value. Description: `This command sets a given database key to the given value.
WARNING: This is a low-level operation which may cause database corruption!`, WARNING: This is a low-level operation which may cause database corruption!`,
} }
dbGetSlotsCmd = &cli.Command{ dbGetSlotsCmd = &cli.Command{
Action: dbDumpTrie, Action: dbDumpTrie,
Name: "dumptrie", Name: "dumptrie",
Usage: "Show the storage key/values of a given storage trie", Usage: "Show the storage key/values of a given storage trie",
ArgsUsage: "<hex-encoded state root> <hex-encoded account hash> <hex-encoded storage trie root> <hex-encoded start (optional)> <int max elements (optional)>", ArgsUsage: "<hex-encoded state root> <hex-encoded account hash> <hex-encoded storage trie root> <hex-encoded start (optional)> <int max elements (optional)>",
Flags: slices.Concat([]cli.Flag{ Flags: slices.Concat(utils.NetworkFlags, utils.DatabaseFlags),
utils.SyncModeFlag,
}, utils.NetworkFlags, utils.DatabaseFlags),
Description: "This command looks up the specified database key from the database.", Description: "This command looks up the specified database key from the database.",
} }
dbDumpFreezerIndex = &cli.Command{ dbDumpFreezerIndex = &cli.Command{
Action: freezerInspect, Action: freezerInspect,
Name: "freezer-index", Name: "freezer-index",
Usage: "Dump out the index of a specific freezer table", Usage: "Dump out the index of a specific freezer table",
ArgsUsage: "<freezer-type> <table-type> <start (int)> <end (int)>", ArgsUsage: "<freezer-type> <table-type> <start (int)> <end (int)>",
Flags: slices.Concat([]cli.Flag{ Flags: slices.Concat(utils.NetworkFlags, utils.DatabaseFlags),
utils.SyncModeFlag,
}, utils.NetworkFlags, utils.DatabaseFlags),
Description: "This command displays information about the freezer index.", Description: "This command displays information about the freezer index.",
} }
dbImportCmd = &cli.Command{ dbImportCmd = &cli.Command{
Action: importLDBdata, Action: importLDBdata,
Name: "import", Name: "import",
Usage: "Imports leveldb-data from an exported RLP dump.", Usage: "Imports leveldb-data from an exported RLP dump.",
ArgsUsage: "<dumpfile> <start (optional)", ArgsUsage: "<dumpfile> <start (optional)",
Flags: slices.Concat([]cli.Flag{ Flags: slices.Concat(utils.NetworkFlags, utils.DatabaseFlags),
utils.SyncModeFlag,
}, utils.NetworkFlags, utils.DatabaseFlags),
Description: "The import command imports the specific chain data from an RLP encoded stream.", Description: "The import command imports the specific chain data from an RLP encoded stream.",
} }
dbExportCmd = &cli.Command{ dbExportCmd = &cli.Command{
Action: exportChaindata, Action: exportChaindata,
Name: "export", Name: "export",
Usage: "Exports the chain data into an RLP dump. If the <dumpfile> has .gz suffix, gzip compression will be used.", Usage: "Exports the chain data into an RLP dump. If the <dumpfile> has .gz suffix, gzip compression will be used.",
ArgsUsage: "<type> <dumpfile>", ArgsUsage: "<type> <dumpfile>",
Flags: slices.Concat([]cli.Flag{ Flags: slices.Concat(utils.NetworkFlags, utils.DatabaseFlags),
utils.SyncModeFlag,
}, utils.NetworkFlags, utils.DatabaseFlags),
Description: "Exports the specified chain data to an RLP encoded stream, optionally gzip-compressed.", Description: "Exports the specified chain data to an RLP encoded stream, optionally gzip-compressed.",
} }
dbMetadataCmd = &cli.Command{ dbMetadataCmd = &cli.Command{
Action: showMetaData, Action: showMetaData,
Name: "metadata", Name: "metadata",
Usage: "Shows metadata about the chain status.", Usage: "Shows metadata about the chain status.",
Flags: slices.Concat([]cli.Flag{ Flags: slices.Concat(utils.NetworkFlags, utils.DatabaseFlags),
utils.SyncModeFlag,
}, utils.NetworkFlags, utils.DatabaseFlags),
Description: "Shows metadata about the chain status.", Description: "Shows metadata about the chain status.",
} }
dbInspectHistoryCmd = &cli.Command{ dbInspectHistoryCmd = &cli.Command{
@ -213,7 +192,6 @@ WARNING: This is a low-level operation which may cause database corruption!`,
Usage: "Inspect the state history within block range", Usage: "Inspect the state history within block range",
ArgsUsage: "<address> [OPTIONAL <storage-slot>]", ArgsUsage: "<address> [OPTIONAL <storage-slot>]",
Flags: slices.Concat([]cli.Flag{ Flags: slices.Concat([]cli.Flag{
utils.SyncModeFlag,
&cli.Uint64Flag{ &cli.Uint64Flag{
Name: "start", Name: "start",
Usage: "block number of the range start, zero means earliest history", Usage: "block number of the range start, zero means earliest history",

View file

@ -142,7 +142,6 @@ var (
utils.VMTraceJsonConfigFlag, utils.VMTraceJsonConfigFlag,
utils.NetworkIdFlag, utils.NetworkIdFlag,
utils.EthStatsURLFlag, utils.EthStatsURLFlag,
utils.NoCompactionFlag,
utils.GpoBlocksFlag, utils.GpoBlocksFlag,
utils.GpoPercentileFlag, utils.GpoPercentileFlag,
utils.GpoMaxGasPriceFlag, utils.GpoMaxGasPriceFlag,

View file

@ -54,8 +54,8 @@ func (iter *testIterator) Next() (byte, []byte, []byte, bool) {
if iter.index == 42 { if iter.index == 42 {
iter.index += 1 iter.index += 1
} }
return OpBatchAdd, []byte(fmt.Sprintf("key-%04d", iter.index)), return OpBatchAdd, fmt.Appendf(nil, "key-%04d", iter.index),
[]byte(fmt.Sprintf("value %d", iter.index)), true fmt.Appendf(nil, "value %d", iter.index), true
} }
func (iter *testIterator) Release() {} func (iter *testIterator) Release() {}
@ -72,7 +72,7 @@ func testExport(t *testing.T, f string) {
} }
// verify // verify
for i := 0; i < 1000; i++ { for i := 0; i < 1000; i++ {
v, err := db.Get([]byte(fmt.Sprintf("key-%04d", i))) v, err := db.Get(fmt.Appendf(nil, "key-%04d", i))
if (i < 5 || i == 42) && err == nil { if (i < 5 || i == 42) && err == nil {
t.Fatalf("expected no element at idx %d, got '%v'", i, string(v)) t.Fatalf("expected no element at idx %d, got '%v'", i, string(v))
} }
@ -85,7 +85,7 @@ func testExport(t *testing.T, f string) {
} }
} }
} }
v, err := db.Get([]byte(fmt.Sprintf("key-%04d", 1000))) v, err := db.Get(fmt.Appendf(nil, "key-%04d", 1000))
if err == nil { if err == nil {
t.Fatalf("expected no element at idx %d, got '%v'", 1000, string(v)) t.Fatalf("expected no element at idx %d, got '%v'", 1000, string(v))
} }
@ -120,7 +120,7 @@ func (iter *deletionIterator) Next() (byte, []byte, []byte, bool) {
if iter.index == 42 { if iter.index == 42 {
iter.index += 1 iter.index += 1
} }
return OpBatchDel, []byte(fmt.Sprintf("key-%04d", iter.index)), nil, true return OpBatchDel, fmt.Appendf(nil, "key-%04d", iter.index), nil, true
} }
func (iter *deletionIterator) Release() {} func (iter *deletionIterator) Release() {}
@ -132,14 +132,14 @@ func testDeletion(t *testing.T, f string) {
} }
db := rawdb.NewMemoryDatabase() db := rawdb.NewMemoryDatabase()
for i := 0; i < 1000; i++ { for i := 0; i < 1000; i++ {
db.Put([]byte(fmt.Sprintf("key-%04d", i)), []byte(fmt.Sprintf("value %d", i))) db.Put(fmt.Appendf(nil, "key-%04d", i), fmt.Appendf(nil, "value %d", i))
} }
err = ImportLDBData(db, f, 5, make(chan struct{})) err = ImportLDBData(db, f, 5, make(chan struct{}))
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
for i := 0; i < 1000; i++ { for i := 0; i < 1000; i++ {
v, err := db.Get([]byte(fmt.Sprintf("key-%04d", i))) v, err := db.Get(fmt.Appendf(nil, "key-%04d", i))
if i < 5 || i == 42 { if i < 5 || i == 42 {
if err != nil { if err != nil {
t.Fatalf("expected element at idx %d, got '%v'", i, err) t.Fatalf("expected element at idx %d, got '%v'", i, err)

View file

@ -2189,6 +2189,8 @@ func MakeChain(ctx *cli.Context, stack *node.Node, readonly bool) (*core.BlockCh
} }
if !ctx.Bool(SnapshotFlag.Name) { if !ctx.Bool(SnapshotFlag.Name) {
cache.SnapshotLimit = 0 // Disabled cache.SnapshotLimit = 0 // Disabled
} else if ctx.IsSet(CacheFlag.Name) || ctx.IsSet(CacheSnapshotFlag.Name) {
cache.SnapshotLimit = ctx.Int(CacheFlag.Name) * ctx.Int(CacheSnapshotFlag.Name) / 100
} }
// If we're in readonly, do not bother generating snapshot data. // If we're in readonly, do not bother generating snapshot data.
if readonly { if readonly {

View file

@ -72,7 +72,7 @@ func (i *HexOrDecimal256) MarshalText() ([]byte, error) {
if i == nil { if i == nil {
return []byte("0x0"), nil return []byte("0x0"), nil
} }
return []byte(fmt.Sprintf("%#x", (*big.Int)(i))), nil return fmt.Appendf(nil, "%#x", (*big.Int)(i)), nil
} }
// Decimal256 unmarshals big.Int as a decimal string. When unmarshalling, // Decimal256 unmarshals big.Int as a decimal string. When unmarshalling,

View file

@ -48,7 +48,7 @@ func (i *HexOrDecimal64) UnmarshalText(input []byte) error {
// MarshalText implements encoding.TextMarshaler. // MarshalText implements encoding.TextMarshaler.
func (i HexOrDecimal64) MarshalText() ([]byte, error) { func (i HexOrDecimal64) MarshalText() ([]byte, error) {
return []byte(fmt.Sprintf("%#x", uint64(i))), nil return fmt.Appendf(nil, "%#x", uint64(i)), nil
} }
// ParseUint64 parses s as an integer in decimal or hexadecimal syntax. // ParseUint64 parses s as an integer in decimal or hexadecimal syntax.

View file

@ -86,6 +86,11 @@ func (bc *BlockChain) GetHeaderByNumber(number uint64) *types.Header {
return bc.hc.GetHeaderByNumber(number) return bc.hc.GetHeaderByNumber(number)
} }
// GetBlockNumber retrieves the block number associated with a block hash.
func (bc *BlockChain) GetBlockNumber(hash common.Hash) *uint64 {
return bc.hc.GetBlockNumber(hash)
}
// GetHeadersFrom returns a contiguous segment of headers, in rlp-form, going // GetHeadersFrom returns a contiguous segment of headers, in rlp-form, going
// backwards from the given number. // backwards from the given number.
func (bc *BlockChain) GetHeadersFrom(number, count uint64) []rlp.RawValue { func (bc *BlockChain) GetHeadersFrom(number, count uint64) []rlp.RawValue {

View file

@ -504,6 +504,13 @@ func GenerateVerkleChain(config *params.ChainConfig, parent *types.Block, engine
if gen != nil { if gen != nil {
gen(i, b) gen(i, b)
} }
requests := b.collectRequests(false)
if requests != nil {
reqHash := types.CalcRequestsHash(requests)
b.header.RequestsHash = &reqHash
}
body := &types.Body{ body := &types.Body{
Transactions: b.txs, Transactions: b.txs,
Uncles: b.uncles, Uncles: b.uncles,

View file

@ -83,7 +83,11 @@ func (cv *ChainView) getReceipts(number uint64) types.Receipts {
if number > cv.headNumber { if number > cv.headNumber {
panic("invalid block number") panic("invalid block number")
} }
return cv.chain.GetReceiptsByHash(cv.blockHash(number)) blockHash := cv.blockHash(number)
if blockHash == (common.Hash{}) {
log.Error("Chain view: block hash unavailable", "number", number, "head", cv.headNumber)
}
return cv.chain.GetReceiptsByHash(blockHash)
} }
// limitedView returns a new chain view that is a truncated version of the parent view. // limitedView returns a new chain view that is a truncated version of the parent view.

View file

@ -29,8 +29,24 @@ import (
"github.com/ethereum/go-ethereum/core/rawdb" "github.com/ethereum/go-ethereum/core/rawdb"
"github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/ethdb" "github.com/ethereum/go-ethereum/ethdb"
"github.com/ethereum/go-ethereum/ethdb/leveldb"
"github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/log"
"github.com/ethereum/go-ethereum/metrics"
)
var (
mapCountGauge = metrics.NewRegisteredGauge("filtermaps/maps/count", nil) // actual number of rendered maps
mapLogValueMeter = metrics.NewRegisteredMeter("filtermaps/maps/logvalues", nil) // number of log values processed
mapBlockMeter = metrics.NewRegisteredMeter("filtermaps/maps/blocks", nil) // number of block delimiters processed
mapRenderTimer = metrics.NewRegisteredTimer("filtermaps/maps/rendertime", nil) // time elapsed while rendering a single map
mapWriteTimer = metrics.NewRegisteredTimer("filtermaps/maps/writetime", nil) // time elapsed while writing a batch of finished maps to db
matchRequestTimer = metrics.NewRegisteredTimer("filtermaps/match/requesttime", nil) // processing time a matching request in a single epoch
matchEpochTimer = metrics.NewRegisteredTimer("filtermaps/match/epochtime", nil) // total processing time a matching request
matchBaseRowAccessMeter = metrics.NewRegisteredMeter("filtermaps/match/baserowaccess", nil) // number of accessed rows on layer 0
matchBaseRowSizeMeter = metrics.NewRegisteredMeter("filtermaps/match/baserowsize", nil) // size of accessed rows on layer 0
matchExtRowAccessMeter = metrics.NewRegisteredMeter("filtermaps/match/extrowaccess", nil) // number of accessed rows on higher layers
matchExtRowSizeMeter = metrics.NewRegisteredMeter("filtermaps/match/extrowsize", nil) // size of accessed rows on higher layers
matchLogLookup = metrics.NewRegisteredMeter("filtermaps/match/loglookup", nil) // number of log lookups based on potential matches
matchAllMeter = metrics.NewRegisteredMeter("filtermaps/match/matchall", nil) // number of requests returned with ErrMatchAll
) )
const ( const (
@ -53,11 +69,13 @@ type FilterMaps struct {
// This is configured by the --history.logs.disable Geth flag. // This is configured by the --history.logs.disable Geth flag.
// We chose to implement disabling this way because it requires less special // We chose to implement disabling this way because it requires less special
// case logic in eth/filters. // case logic in eth/filters.
disabled bool disabled bool
disabledCh chan struct{} // closed by indexer if disabled
closeCh chan struct{} closeCh chan struct{}
closeWg sync.WaitGroup closeWg sync.WaitGroup
history uint64 history uint64
hashScheme bool // use hashdb-safe delete range method
exportFileName string exportFileName string
Params Params
@ -66,10 +84,11 @@ 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
indexedView *ChainView // always consistent with the log index cleanedEpochsBefore uint32 // all unindexed data cleaned before this point
hasTempRange bool indexedView *ChainView // always consistent with the log index
hasTempRange bool
// 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]
@ -179,6 +198,10 @@ type Config struct {
// This option enables the checkpoint JSON file generator. // This option enables the checkpoint JSON file generator.
// If set, the given file will be updated with checkpoint information. // If set, the given file will be updated with checkpoint information.
ExportFileName string ExportFileName string
// expect trie nodes of hash based state scheme in the filtermaps key range;
// use safe iterator based implementation of DeleteRange that skips them
HashScheme bool
} }
// NewFilterMaps creates a new FilterMaps and starts the indexer. // NewFilterMaps creates a new FilterMaps and starts the indexer.
@ -196,6 +219,8 @@ func NewFilterMaps(db ethdb.KeyValueStore, initView *ChainView, historyCutoff, f
blockProcessingCh: make(chan bool, 1), blockProcessingCh: make(chan bool, 1),
history: config.History, history: config.History,
disabled: config.Disabled, disabled: config.Disabled,
hashScheme: config.HashScheme,
disabledCh: make(chan struct{}),
exportFileName: config.ExportFileName, exportFileName: config.ExportFileName,
Params: params, Params: params,
indexedRange: filterMapsRange{ indexedRange: filterMapsRange{
@ -206,13 +231,17 @@ func NewFilterMaps(db ethdb.KeyValueStore, initView *ChainView, historyCutoff, f
maps: common.NewRange(rs.MapsFirst, rs.MapsAfterLast-rs.MapsFirst), maps: common.NewRange(rs.MapsFirst, rs.MapsAfterLast-rs.MapsFirst),
tailPartialEpoch: rs.TailPartialEpoch, tailPartialEpoch: rs.TailPartialEpoch,
}, },
matcherSyncCh: make(chan *FilterMapsMatcherBackend), // deleting last unindexed epoch might have been interrupted by shutdown
matchers: make(map[*FilterMapsMatcherBackend]struct{}), cleanedEpochsBefore: max(rs.MapsFirst>>params.logMapsPerEpoch, 1) - 1,
filterMapCache: lru.NewCache[uint32, filterMap](cachedFilterMaps), historyCutoff: historyCutoff,
lastBlockCache: lru.NewCache[uint32, lastBlockOfMap](cachedLastBlocks), finalBlock: finalBlock,
lvPointerCache: lru.NewCache[uint64, uint64](cachedLvPointers), matcherSyncCh: make(chan *FilterMapsMatcherBackend),
baseRowsCache: lru.NewCache[uint64, [][]uint32](cachedBaseRows), matchers: make(map[*FilterMapsMatcherBackend]struct{}),
renderSnapshots: lru.NewCache[uint64, *renderedMap](cachedRenderSnapshots), 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. // Set initial indexer target.
@ -278,8 +307,13 @@ func (f *FilterMaps) initChainView(chainView *ChainView) *ChainView {
} }
// reset un-initializes the FilterMaps structure and removes all related data from // reset un-initializes the FilterMaps structure and removes all related data from
// the database. The function returns true if everything was successfully removed. // the database.
func (f *FilterMaps) reset() bool { // Note that in case of leveldb database the fallback implementation of DeleteRange
// might take a long time to finish and deleting the entire database may be
// interrupted by a shutdown. Deleting the filterMapsRange entry first does
// guarantee though that the next init() will not return successfully until the
// entire database has been cleaned.
func (f *FilterMaps) reset() {
f.indexLock.Lock() f.indexLock.Lock()
f.indexedRange = filterMapsRange{} f.indexedRange = filterMapsRange{}
f.indexedView = nil f.indexedView = nil
@ -292,11 +326,26 @@ func (f *FilterMaps) reset() bool {
// deleting the range first ensures that resetDb will be called again at next // deleting the range first ensures that resetDb will be called again at next
// startup and any leftover data will be removed even if it cannot finish now. // startup and any leftover data will be removed even if it cannot finish now.
rawdb.DeleteFilterMapsRange(f.db) rawdb.DeleteFilterMapsRange(f.db)
return f.safeDeleteRange(rawdb.DeleteFilterMapsDb, "Resetting log index database") f.safeDeleteWithLogs(rawdb.DeleteFilterMapsDb, "Resetting log index database", f.isShuttingDown)
}
// isShuttingDown returns true if FilterMaps is shutting down.
func (f *FilterMaps) isShuttingDown() bool {
select {
case <-f.closeCh:
return true
default:
return false
}
} }
// init initializes an empty log index according to the current targetView. // init initializes an empty log index according to the current targetView.
func (f *FilterMaps) init() error { func (f *FilterMaps) init() error {
// ensure that there is no remaining data in the filter maps key range
if err := f.safeDeleteWithLogs(rawdb.DeleteFilterMapsDb, "Resetting log index database", f.isShuttingDown); err != nil {
return err
}
f.indexLock.Lock() f.indexLock.Lock()
defer f.indexLock.Unlock() defer f.indexLock.Unlock()
@ -317,6 +366,13 @@ func (f *FilterMaps) init() error {
bestIdx, bestLen = idx, max bestIdx, bestLen = idx, max
} }
} }
var initBlockNumber uint64
if bestLen > 0 {
initBlockNumber = checkpoints[bestIdx][bestLen-1].BlockNumber
}
if initBlockNumber < f.historyCutoff {
return errors.New("cannot start indexing before history cutoff point")
}
batch := f.db.NewBatch() batch := f.db.NewBatch()
for epoch := range bestLen { for epoch := range bestLen {
cp := checkpoints[bestIdx][epoch] cp := checkpoints[bestIdx][epoch]
@ -337,38 +393,37 @@ func (f *FilterMaps) init() error {
// removeBloomBits removes old bloom bits data from the database. // removeBloomBits removes old bloom bits data from the database.
func (f *FilterMaps) removeBloomBits() { func (f *FilterMaps) removeBloomBits() {
f.safeDeleteRange(rawdb.DeleteBloomBitsDb, "Removing old bloom bits database") f.safeDeleteWithLogs(rawdb.DeleteBloomBitsDb, "Removing old bloom bits database", f.isShuttingDown)
f.closeWg.Done() f.closeWg.Done()
} }
// safeDeleteRange calls the specified database range deleter function // safeDeleteWithLogs is a wrapper for a function that performs a safe range
// repeatedly as long as it returns leveldb.ErrTooManyKeys. // delete operation using rawdb.SafeDeleteRange. It emits log messages if the
// This wrapper is necessary because of the leveldb fallback implementation // process takes long enough to call the stop callback.
// of DeleteRange. func (f *FilterMaps) safeDeleteWithLogs(deleteFn func(db ethdb.KeyValueStore, hashScheme bool, stopCb func(bool) bool) error, action string, stopCb func() bool) error {
func (f *FilterMaps) safeDeleteRange(removeFn func(ethdb.KeyValueRangeDeleter) error, action string) bool { var (
start := time.Now() start = time.Now()
var retry bool logPrinted bool
for { lastLogPrinted = start
err := removeFn(f.db) )
if err == nil { switch err := deleteFn(f.db, f.hashScheme, func(deleted bool) bool {
if retry { if deleted && !logPrinted || time.Since(lastLogPrinted) > time.Second*10 {
log.Info(action+" finished", "elapsed", time.Since(start)) log.Info(action+" in progress...", "elapsed", common.PrettyDuration(time.Since(start)))
} logPrinted, lastLogPrinted = true, time.Now()
return true
} }
if err != leveldb.ErrTooManyKeys { return stopCb()
log.Error(action+" failed", "error", err) }); {
return false case err == nil:
} if logPrinted {
select { log.Info(action+" finished", "elapsed", common.PrettyDuration(time.Since(start)))
case <-f.closeCh:
return false
default:
}
if !retry {
log.Info(action+" in progress...", "elapsed", time.Since(start))
retry = true
} }
return nil
case errors.Is(err, rawdb.ErrDeleteRangeInterrupted):
log.Warn(action+" interrupted", "elapsed", common.PrettyDuration(time.Since(start)))
return err
default:
log.Error(action+" failed", "error", err)
return err
} }
} }
@ -391,8 +446,12 @@ func (f *FilterMaps) setRange(batch ethdb.KeyValueWriter, newView *ChainView, ne
TailPartialEpoch: newRange.tailPartialEpoch, TailPartialEpoch: newRange.tailPartialEpoch,
} }
rawdb.WriteFilterMapsRange(batch, rs) rawdb.WriteFilterMapsRange(batch, rs)
if !isTempRange {
mapCountGauge.Update(int64(newRange.maps.Count() + newRange.tailPartialEpoch))
}
} else { } else {
rawdb.DeleteFilterMapsRange(batch) rawdb.DeleteFilterMapsRange(batch)
mapCountGauge.Update(0)
} }
} }
@ -637,54 +696,97 @@ func (f *FilterMaps) deleteLastBlockOfMap(batch ethdb.Batch, mapIndex uint32) {
rawdb.DeleteFilterMapLastBlock(batch, mapIndex) rawdb.DeleteFilterMapLastBlock(batch, mapIndex)
} }
// deleteTailEpoch deletes index data from the earliest, either fully or partially // deleteTailEpoch deletes index data from the specified epoch. The last block
// indexed epoch. The last block pointer for the last map of the epoch and the // pointer for the last map of the epoch and the corresponding block log value
// corresponding block log value pointer are retained as these are always assumed // pointer are retained as these are always assumed to be available for each
// to be available for each epoch. // epoch as boundary markers.
func (f *FilterMaps) deleteTailEpoch(epoch uint32) error { // The function returns true if all index data related to the epoch (except for
// the boundary markers) has been fully removed.
func (f *FilterMaps) deleteTailEpoch(epoch uint32) (bool, error) {
f.indexLock.Lock() f.indexLock.Lock()
defer f.indexLock.Unlock() defer f.indexLock.Unlock()
// determine epoch boundaries
firstMap := epoch << f.logMapsPerEpoch firstMap := epoch << f.logMapsPerEpoch
lastBlock, _, err := f.getLastBlockOfMap(firstMap + f.mapsPerEpoch - 1) lastBlock, _, err := f.getLastBlockOfMap(firstMap + f.mapsPerEpoch - 1)
if err != nil { if err != nil {
return fmt.Errorf("failed to retrieve last block of deleted epoch %d: %v", epoch, err) return false, fmt.Errorf("failed to retrieve last block of deleted epoch %d: %v", epoch, err)
} }
var firstBlock uint64 var firstBlock uint64
if epoch > 0 { if epoch > 0 {
firstBlock, _, err = f.getLastBlockOfMap(firstMap - 1) firstBlock, _, err = f.getLastBlockOfMap(firstMap - 1)
if err != nil { if err != nil {
return fmt.Errorf("failed to retrieve last block before deleted epoch %d: %v", epoch, err) return false, fmt.Errorf("failed to retrieve last block before deleted epoch %d: %v", epoch, err)
} }
firstBlock++ firstBlock++
} }
fmr := f.indexedRange // update rendered range if necessary
if f.indexedRange.maps.First() == firstMap && var (
f.indexedRange.maps.AfterLast() > firstMap+f.mapsPerEpoch && fmr = f.indexedRange
f.indexedRange.tailPartialEpoch == 0 { firstEpoch = f.indexedRange.maps.First() >> f.logMapsPerEpoch
fmr.maps.SetFirst(firstMap + f.mapsPerEpoch) afterLastEpoch = (f.indexedRange.maps.AfterLast() + f.mapsPerEpoch - 1) >> f.logMapsPerEpoch
fmr.blocks.SetFirst(lastBlock + 1) )
} else if f.indexedRange.maps.First() == firstMap+f.mapsPerEpoch { if f.indexedRange.tailPartialEpoch != 0 && firstEpoch > 0 {
firstEpoch--
}
switch {
case epoch < firstEpoch:
// cleanup of already unindexed epoch; range not affected
case epoch == firstEpoch && epoch+1 < afterLastEpoch:
// first fully or partially rendered epoch and there is at least one
// rendered map in the next epoch; remove from indexed range
fmr.tailPartialEpoch = 0 fmr.tailPartialEpoch = 0
fmr.maps.SetFirst((epoch + 1) << f.logMapsPerEpoch)
fmr.blocks.SetFirst(lastBlock + 1)
f.setRange(f.db, f.indexedView, fmr, false)
default:
// cannot be cleaned or unindexed; return with error
return false, errors.New("invalid tail epoch number")
}
// remove index data
if err := f.safeDeleteWithLogs(func(db ethdb.KeyValueStore, hashScheme bool, stopCb func(bool) bool) error {
first := f.mapRowIndex(firstMap, 0)
count := f.mapRowIndex(firstMap+f.mapsPerEpoch, 0) - first
if err := rawdb.DeleteFilterMapRows(f.db, common.NewRange(first, count), hashScheme, stopCb); err != nil {
return err
}
for mapIndex := firstMap; mapIndex < firstMap+f.mapsPerEpoch; mapIndex++ {
f.filterMapCache.Remove(mapIndex)
}
delMapRange := common.NewRange(firstMap, f.mapsPerEpoch-1) // keep last entry
if err := rawdb.DeleteFilterMapLastBlocks(f.db, delMapRange, hashScheme, stopCb); err != nil {
return err
}
for mapIndex := firstMap; mapIndex < firstMap+f.mapsPerEpoch-1; mapIndex++ {
f.lastBlockCache.Remove(mapIndex)
}
delBlockRange := common.NewRange(firstBlock, lastBlock-firstBlock) // keep last entry
if err := rawdb.DeleteBlockLvPointers(f.db, delBlockRange, hashScheme, stopCb); err != nil {
return err
}
for blockNumber := firstBlock; blockNumber < lastBlock; blockNumber++ {
f.lvPointerCache.Remove(blockNumber)
}
return nil
}, fmt.Sprintf("Deleting tail epoch #%d", epoch), func() bool {
f.processEvents()
return f.stop || !f.targetHeadIndexed()
}); err == nil {
// everything removed; mark as cleaned and report success
if f.cleanedEpochsBefore == epoch {
f.cleanedEpochsBefore = epoch + 1
}
return true, nil
} else { } else {
return errors.New("invalid tail epoch number") // more data left in epoch range; mark as dirty and report unfinished
if f.cleanedEpochsBefore > epoch {
f.cleanedEpochsBefore = epoch
}
if errors.Is(err, rawdb.ErrDeleteRangeInterrupted) {
return false, nil
}
return false, err
} }
f.setRange(f.db, f.indexedView, fmr, false)
first := f.mapRowIndex(firstMap, 0)
count := f.mapRowIndex(firstMap+f.mapsPerEpoch, 0) - first
rawdb.DeleteFilterMapRows(f.db, common.NewRange(first, count))
for mapIndex := firstMap; mapIndex < firstMap+f.mapsPerEpoch; mapIndex++ {
f.filterMapCache.Remove(mapIndex)
}
rawdb.DeleteFilterMapLastBlocks(f.db, common.NewRange(firstMap, f.mapsPerEpoch-1)) // keep last enrty
for mapIndex := firstMap; mapIndex < firstMap+f.mapsPerEpoch-1; mapIndex++ {
f.lastBlockCache.Remove(mapIndex)
}
rawdb.DeleteBlockLvPointers(f.db, common.NewRange(firstBlock, lastBlock-firstBlock)) // keep last enrty
for blockNumber := firstBlock; blockNumber < lastBlock; blockNumber++ {
f.lvPointerCache.Remove(blockNumber)
}
return nil
} }
// exportCheckpoints exports epoch checkpoints in the format used by checkpoints.go. // exportCheckpoints exports epoch checkpoints in the format used by checkpoints.go.

View file

@ -17,6 +17,7 @@
package filtermaps package filtermaps
import ( import (
"errors"
"math" "math"
"time" "time"
@ -36,26 +37,28 @@ func (f *FilterMaps) indexerLoop() {
if f.disabled { if f.disabled {
f.reset() f.reset()
close(f.disabledCh)
return return
} }
log.Info("Started log indexer") log.Info("Started log indexer")
for !f.stop { for !f.stop {
if !f.indexedRange.initialized { if !f.indexedRange.initialized {
if err := f.init(); err != nil { if f.targetView.headNumber == 0 {
log.Error("Error initializing log index", "error", err) // initialize when chain head is available
// unexpected error; there is not a lot we can do here, maybe it
// recovers, maybe not. Calling event processing here ensures
// that we can still properly shutdown in case of an infinite loop.
f.processSingleEvent(true) f.processSingleEvent(true)
continue continue
} }
if err := f.init(); err != nil {
f.disableForError("initialization", err)
f.reset() // remove broken index from DB
return
}
} }
if !f.targetHeadIndexed() { if !f.targetHeadIndexed() {
if !f.tryIndexHead() { if err := f.tryIndexHead(); err != nil && err != errChainUpdate {
// either shutdown or unexpected error; in the latter case ensure f.disableForError("head rendering", err)
// that proper shutdown is still possible. return
f.processSingleEvent(true)
} }
} else { } else {
if f.finalBlock != f.lastFinal { if f.finalBlock != f.lastFinal {
@ -64,13 +67,39 @@ func (f *FilterMaps) indexerLoop() {
} }
f.lastFinal = f.finalBlock f.lastFinal = f.finalBlock
} }
if f.tryIndexTail() && f.tryUnindexTail() { // always attempt unindexing before indexing the tail in order to
f.waitForNewHead() // ensure that a potentially dirty previously unindexed epoch is
// always cleaned up before any new maps are rendered.
if done, err := f.tryUnindexTail(); err != nil {
f.disableForError("tail unindexing", err)
return
} else if !done {
continue
} }
if done, err := f.tryIndexTail(); err != nil {
f.disableForError("tail rendering", err)
return
} else if !done {
continue
}
// tail indexing/unindexing is done; if head is also indexed then
// wait here until there is a new head
f.waitForNewHead()
} }
} }
} }
// disableForError is called when the indexer encounters a database error, for example a
// missing receipt. We can't continue operating when the database is broken, so the
// indexer goes into disabled state.
// Note that the partial index is left in disk; maybe a client update can fix the
// issue without reindexing.
func (f *FilterMaps) disableForError(op string, err error) {
log.Error("Log index "+op+" failed, reverting to unindexed mode", "error", err)
f.disabled = true
close(f.disabledCh)
}
type targetUpdate struct { type targetUpdate struct {
targetView *ChainView targetView *ChainView
historyCutoff, finalBlock uint64 historyCutoff, finalBlock uint64
@ -111,14 +140,15 @@ func (f *FilterMaps) SetBlockProcessing(blockProcessing bool) {
// WaitIdle blocks until the indexer is in an idle state while synced up to the // WaitIdle blocks until the indexer is in an idle state while synced up to the
// latest targetView. // latest targetView.
func (f *FilterMaps) WaitIdle() { func (f *FilterMaps) WaitIdle() {
if f.disabled {
f.closeWg.Wait()
return
}
for { for {
ch := make(chan bool) ch := make(chan bool)
f.waitIdleCh <- ch select {
if <-ch { case f.waitIdleCh <- ch:
if <-ch {
return
}
case <-f.disabledCh:
f.closeWg.Wait()
return return
} }
} }
@ -191,16 +221,16 @@ func (f *FilterMaps) setTarget(target targetUpdate) {
f.finalBlock = target.finalBlock f.finalBlock = target.finalBlock
} }
// tryIndexHead tries to render head maps according to the current targetView // tryIndexHead tries to render head maps according to the current targetView.
// and returns true if successful. // Should be called when targetHeadIndexed returns false. If this function
func (f *FilterMaps) tryIndexHead() bool { // returns no error then either stop is true or head indexing is finished.
func (f *FilterMaps) tryIndexHead() error {
headRenderer, err := f.renderMapsBefore(math.MaxUint32) headRenderer, err := f.renderMapsBefore(math.MaxUint32)
if err != nil { if err != nil {
log.Error("Error creating log index head renderer", "error", err) return err
return false
} }
if headRenderer == nil { if headRenderer == nil {
return true return errors.New("head indexer has nothing to do") // tryIndexHead should be called when head is not indexed
} }
if !f.startedHeadIndex { if !f.startedHeadIndex {
f.lastLogHeadIndex = time.Now() f.lastLogHeadIndex = time.Now()
@ -225,8 +255,7 @@ func (f *FilterMaps) tryIndexHead() bool {
f.lastLogHeadIndex = time.Now() f.lastLogHeadIndex = time.Now()
} }
}); err != nil { }); err != nil {
log.Error("Log index head rendering failed", "error", err) return err
return false
} }
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",
@ -235,7 +264,7 @@ func (f *FilterMaps) tryIndexHead() bool {
"elapsed", common.PrettyDuration(time.Since(f.startedHeadIndexAt))) "elapsed", common.PrettyDuration(time.Since(f.startedHeadIndexAt)))
} }
f.loggedHeadIndex, f.startedHeadIndex = false, false f.loggedHeadIndex, f.startedHeadIndex = false, false
return true return nil
} }
// tryIndexTail tries to render tail epochs until the tail target block is // tryIndexTail tries to render tail epochs until the tail target block is
@ -243,7 +272,7 @@ func (f *FilterMaps) tryIndexHead() bool {
// Note that tail indexing is only started if the log index head is fully // Note that tail indexing is only started if the log index head is fully
// rendered according to targetView and is suspended as soon as the targetView // rendered according to targetView and is suspended as soon as the targetView
// is changed. // is changed.
func (f *FilterMaps) tryIndexTail() bool { func (f *FilterMaps) tryIndexTail() (bool, error) {
for { for {
firstEpoch := f.indexedRange.maps.First() >> f.logMapsPerEpoch firstEpoch := f.indexedRange.maps.First() >> f.logMapsPerEpoch
if firstEpoch == 0 || !f.needTailEpoch(firstEpoch-1) { if firstEpoch == 0 || !f.needTailEpoch(firstEpoch-1) {
@ -251,7 +280,7 @@ func (f *FilterMaps) tryIndexTail() bool {
} }
f.processEvents() f.processEvents()
if f.stop || !f.targetHeadIndexed() { if f.stop || !f.targetHeadIndexed() {
return false return false, nil
} }
// resume process if tail rendering was interrupted because of head rendering // resume process if tail rendering was interrupted because of head rendering
tailRenderer := f.tailRenderer tailRenderer := f.tailRenderer
@ -263,8 +292,7 @@ func (f *FilterMaps) tryIndexTail() bool {
var err error var err error
tailRenderer, err = f.renderMapsBefore(f.indexedRange.maps.First()) tailRenderer, err = f.renderMapsBefore(f.indexedRange.maps.First())
if err != nil { if err != nil {
log.Error("Error creating log index tail renderer", "error", err) return false, err
return false
} }
} }
if tailRenderer == nil { if tailRenderer == nil {
@ -297,13 +325,16 @@ func (f *FilterMaps) tryIndexTail() bool {
f.lastLogTailIndex = time.Now() f.lastLogTailIndex = time.Now()
} }
}) })
if err != nil && f.needTailEpoch(firstEpoch-1) { if err != nil && !f.needTailEpoch(firstEpoch-1) {
// stop silently if cutoff point has move beyond epoch boundary while rendering // stop silently if cutoff point has move beyond epoch boundary while rendering
log.Error("Log index tail rendering failed", "error", err) return true, nil
}
if err != nil {
return false, err
} }
if !done { if !done {
f.tailRenderer = tailRenderer // only keep tail renderer if interrupted by stopCb f.tailRenderer = tailRenderer // only keep tail renderer if interrupted by stopCb
return false return false, nil
} }
} }
if f.loggedTailIndex && f.indexedRange.hasIndexedBlocks() { if f.loggedTailIndex && f.indexedRange.hasIndexedBlocks() {
@ -313,32 +344,31 @@ func (f *FilterMaps) tryIndexTail() bool {
"elapsed", common.PrettyDuration(time.Since(f.startedTailIndexAt))) "elapsed", common.PrettyDuration(time.Since(f.startedTailIndexAt)))
f.loggedTailIndex = false f.loggedTailIndex = false
} }
return true return true, nil
} }
// tryUnindexTail removes entire epochs of log index data as long as the first // tryUnindexTail removes entire epochs of log index data as long as the first
// fully indexed block is at least as old as the tail target. // fully indexed block is at least as old as the tail target.
// Note that unindexing is very quick as it only removes continuous ranges of // Note that unindexing is very quick as it only removes continuous ranges of
// data from the database and is also called while running head indexing. // data from the database and is also called while running head indexing.
func (f *FilterMaps) tryUnindexTail() bool { func (f *FilterMaps) tryUnindexTail() (bool, error) {
for { firstEpoch := f.indexedRange.maps.First() >> f.logMapsPerEpoch
firstEpoch := (f.indexedRange.maps.First() - f.indexedRange.tailPartialEpoch) >> f.logMapsPerEpoch if f.indexedRange.tailPartialEpoch > 0 && firstEpoch > 0 {
if f.needTailEpoch(firstEpoch) { firstEpoch--
break }
} for epoch := min(firstEpoch, f.cleanedEpochsBefore); !f.needTailEpoch(epoch); epoch++ {
f.processEvents()
if f.stop {
return false
}
if !f.startedTailUnindex { if !f.startedTailUnindex {
f.startedTailUnindexAt = time.Now() f.startedTailUnindexAt = time.Now()
f.startedTailUnindex = true f.startedTailUnindex = true
f.ptrTailUnindexMap = f.indexedRange.maps.First() - f.indexedRange.tailPartialEpoch f.ptrTailUnindexMap = f.indexedRange.maps.First() - f.indexedRange.tailPartialEpoch
f.ptrTailUnindexBlock = f.indexedRange.blocks.First() - f.tailPartialBlocks() f.ptrTailUnindexBlock = f.indexedRange.blocks.First() - f.tailPartialBlocks()
} }
if err := f.deleteTailEpoch(firstEpoch); err != nil { if done, err := f.deleteTailEpoch(epoch); !done {
log.Error("Log index tail epoch unindexing failed", "error", err) return false, err
return false }
f.processEvents()
if f.stop || !f.targetHeadIndexed() {
return false, nil
} }
} }
if f.startedTailUnindex && f.indexedRange.hasIndexedBlocks() { if f.startedTailUnindex && f.indexedRange.hasIndexedBlocks() {
@ -349,7 +379,7 @@ func (f *FilterMaps) tryUnindexTail() bool {
"elapsed", common.PrettyDuration(time.Since(f.startedTailUnindexAt))) "elapsed", common.PrettyDuration(time.Since(f.startedTailUnindexAt)))
f.startedTailUnindex = false f.startedTailUnindex = false
} }
return true return true, nil
} }
// needTailEpoch returns true if the given tail epoch needs to be kept // needTailEpoch returns true if the given tail epoch needs to be kept

View file

@ -21,6 +21,7 @@ import (
"fmt" "fmt"
"math" "math"
"sort" "sort"
"time"
"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"
@ -301,6 +302,11 @@ func (r *mapRenderer) run(stopCb func() bool, writeCb func()) (bool, error) {
// renderCurrentMap renders a single map. // renderCurrentMap renders a single map.
func (r *mapRenderer) renderCurrentMap(stopCb func() bool) (bool, error) { func (r *mapRenderer) renderCurrentMap(stopCb func() bool) (bool, error) {
var (
totalTime time.Duration
logValuesProcessed, blocksProcessed int64
)
start := time.Now()
if !r.iterator.updateChainView(r.f.targetView) { if !r.iterator.updateChainView(r.f.targetView) {
return false, errChainUpdate return false, errChainUpdate
} }
@ -316,9 +322,11 @@ func (r *mapRenderer) renderCurrentMap(stopCb func() bool) (bool, error) {
for r.iterator.lvIndex < uint64(r.currentMap.mapIndex+1)<<r.f.logValuesPerMap && !r.iterator.finished { for r.iterator.lvIndex < uint64(r.currentMap.mapIndex+1)<<r.f.logValuesPerMap && !r.iterator.finished {
waitCnt++ waitCnt++
if waitCnt >= valuesPerCallback { if waitCnt >= valuesPerCallback {
totalTime += time.Since(start)
if stopCb() { if stopCb() {
return false, nil return false, nil
} }
start = time.Now()
if !r.iterator.updateChainView(r.f.targetView) { if !r.iterator.updateChainView(r.f.targetView) {
return false, errChainUpdate return false, errChainUpdate
} }
@ -343,8 +351,10 @@ func (r *mapRenderer) renderCurrentMap(stopCb func() bool) (bool, error) {
return false, fmt.Errorf("failed to advance log iterator at %d while rendering map %d: %v", r.iterator.lvIndex, r.currentMap.mapIndex, err) return false, fmt.Errorf("failed to advance log iterator at %d while rendering map %d: %v", r.iterator.lvIndex, r.currentMap.mapIndex, err)
} }
if !r.iterator.skipToBoundary { if !r.iterator.skipToBoundary {
logValuesProcessed++
r.currentMap.lastBlock = r.iterator.blockNumber r.currentMap.lastBlock = r.iterator.blockNumber
if r.iterator.blockStart { if r.iterator.blockStart {
blocksProcessed++
r.currentMap.blockLvPtrs = append(r.currentMap.blockLvPtrs, r.iterator.lvIndex) r.currentMap.blockLvPtrs = append(r.currentMap.blockLvPtrs, r.iterator.lvIndex)
} }
if !r.f.testDisableSnapshots && r.renderBefore >= r.f.indexedRange.maps.AfterLast() && if !r.f.testDisableSnapshots && r.renderBefore >= r.f.indexedRange.maps.AfterLast() &&
@ -358,12 +368,18 @@ func (r *mapRenderer) renderCurrentMap(stopCb func() bool) (bool, error) {
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.getBlockId(r.currentMap.lastBlock)
totalTime += time.Since(start)
mapRenderTimer.Update(totalTime)
mapLogValueMeter.Mark(logValuesProcessed)
mapBlockMeter.Mark(blocksProcessed)
return true, nil return true, nil
} }
// writeFinishedMaps writes rendered maps to the database and updates // writeFinishedMaps writes rendered maps to the database and updates
// filterMapsRange and indexedView accordingly. // filterMapsRange and indexedView accordingly.
func (r *mapRenderer) writeFinishedMaps(pauseCb func() bool) error { func (r *mapRenderer) writeFinishedMaps(pauseCb func() bool) error {
var totalTime time.Duration
start := time.Now()
if len(r.finishedMaps) == 0 { if len(r.finishedMaps) == 0 {
return nil return nil
} }
@ -379,7 +395,7 @@ func (r *mapRenderer) writeFinishedMaps(pauseCb func() bool) error {
if err != nil { if err != nil {
return fmt.Errorf("failed to get updated rendered range: %v", err) return fmt.Errorf("failed to get updated rendered range: %v", err)
} }
renderedView := r.f.targetView // stopCb callback might still change targetView while writing finished maps renderedView := r.f.targetView // pauseCb callback might still change targetView while writing finished maps
batch := r.f.db.NewBatch() batch := r.f.db.NewBatch()
var writeCnt int var writeCnt int
@ -393,7 +409,9 @@ func (r *mapRenderer) writeFinishedMaps(pauseCb func() bool) error {
// do not exit while in partially written state but do allow processing // do not exit while in partially written state but do allow processing
// events and pausing while block processing is in progress // events and pausing while block processing is in progress
r.f.indexLock.Unlock() r.f.indexLock.Unlock()
totalTime += time.Since(start)
pauseCb() pauseCb()
start = time.Now()
r.f.indexLock.Lock() r.f.indexLock.Lock()
batch = r.f.db.NewBatch() batch = r.f.db.NewBatch()
} }
@ -477,6 +495,8 @@ func (r *mapRenderer) writeFinishedMaps(pauseCb func() bool) error {
if err := batch.Write(); err != nil { if err := batch.Write(); err != nil {
log.Crit("Error writing log index update batch", "error", err) log.Crit("Error writing log index update batch", "error", err)
} }
totalTime += time.Since(start)
mapWriteTimer.Update(totalTime)
return nil return nil
} }

View file

@ -125,6 +125,7 @@ func GetPotentialMatches(ctx context.Context, backend MatcherBackend, firstBlock
start := time.Now() start := time.Now()
res, err := m.process() res, err := m.process()
matchRequestTimer.Update(time.Since(start))
if doRuntimeStats { if doRuntimeStats {
log.Info("Log search finished", "elapsed", time.Since(start)) log.Info("Log search finished", "elapsed", time.Since(start))
@ -202,6 +203,7 @@ func (m *matcherEnv) process() ([]*types.Log, error) {
logs = append(logs, tasks[waitEpoch].logs...) logs = append(logs, tasks[waitEpoch].logs...)
if err := tasks[waitEpoch].err; err != nil { if err := tasks[waitEpoch].err; err != nil {
if err == ErrMatchAll { if err == ErrMatchAll {
matchAllMeter.Mark(1)
return logs, err return logs, err
} }
return logs, fmt.Errorf("failed to process log index epoch %d: %v", waitEpoch, err) return logs, fmt.Errorf("failed to process log index epoch %d: %v", waitEpoch, err)
@ -220,6 +222,7 @@ func (m *matcherEnv) process() ([]*types.Log, error) {
// processEpoch returns the potentially matching logs from the given epoch. // processEpoch returns the potentially matching logs from the given epoch.
func (m *matcherEnv) processEpoch(epochIndex uint32) ([]*types.Log, error) { func (m *matcherEnv) processEpoch(epochIndex uint32) ([]*types.Log, error) {
start := time.Now()
var logs []*types.Log var logs []*types.Log
// create a list of map indices to process // create a list of map indices to process
fm, lm := epochIndex<<m.params.logMapsPerEpoch, (epochIndex+1)<<m.params.logMapsPerEpoch-1 fm, lm := epochIndex<<m.params.logMapsPerEpoch, (epochIndex+1)<<m.params.logMapsPerEpoch-1
@ -254,6 +257,7 @@ func (m *matcherEnv) processEpoch(epochIndex uint32) ([]*types.Log, error) {
logs = append(logs, mlogs...) logs = append(logs, mlogs...)
} }
m.getLogStats.addAmount(st, int64(len(logs))) m.getLogStats.addAmount(st, int64(len(logs)))
matchEpochTimer.Update(time.Since(start))
return logs, nil return logs, nil
} }
@ -273,6 +277,7 @@ func (m *matcherEnv) getLogsFromMatches(matches potentialMatches) ([]*types.Log,
if log != nil { if log != nil {
logs = append(logs, log) logs = append(logs, log)
} }
matchLogLookup.Mark(1)
} }
return logs, nil return logs, nil
} }
@ -381,6 +386,13 @@ func (m *singleMatcherInstance) getMatchesForLayer(ctx context.Context, layerInd
m.stats.setState(&st, stNone) m.stats.setState(&st, stNone)
return nil, fmt.Errorf("failed to retrieve filter map %d row %d: %v", mapIndex, rowIndex, err) return nil, fmt.Errorf("failed to retrieve filter map %d row %d: %v", mapIndex, rowIndex, err)
} }
if layerIndex == 0 {
matchBaseRowAccessMeter.Mark(1)
matchBaseRowSizeMeter.Mark(int64(len(filterRow)))
} else {
matchExtRowAccessMeter.Mark(1)
matchExtRowSizeMeter.Mark(int64(len(filterRow)))
}
m.stats.addAmount(st, int64(len(filterRow))) m.stats.addAmount(st, int64(len(filterRow)))
m.stats.setState(&st, stOther) m.stats.setState(&st, stOther)
filterRows = append(filterRows, filterRow) filterRows = append(filterRows, filterRow)

View file

@ -141,10 +141,6 @@ func (fm *FilterMapsMatcherBackend) synced() {
// range that has not been changed and has been consistent with all states of the // range that has not been changed and has been consistent with all states of the
// chain since the previous SyncLogIndex or the creation of the matcher backend. // chain since the previous SyncLogIndex or the creation of the matcher backend.
func (fm *FilterMapsMatcherBackend) SyncLogIndex(ctx context.Context) (SyncRange, error) { func (fm *FilterMapsMatcherBackend) SyncLogIndex(ctx context.Context) (SyncRange, error) {
if fm.f.disabled {
return SyncRange{HeadNumber: fm.f.targetView.headNumber}, nil
}
syncCh := make(chan SyncRange, 1) syncCh := make(chan SyncRange, 1)
fm.f.matchersLock.Lock() fm.f.matchersLock.Lock()
fm.syncCh = syncCh fm.syncCh = syncCh
@ -154,12 +150,16 @@ func (fm *FilterMapsMatcherBackend) SyncLogIndex(ctx context.Context) (SyncRange
case fm.f.matcherSyncCh <- fm: case fm.f.matcherSyncCh <- fm:
case <-ctx.Done(): case <-ctx.Done():
return SyncRange{}, ctx.Err() return SyncRange{}, ctx.Err()
case <-fm.f.disabledCh:
return SyncRange{HeadNumber: fm.f.targetView.headNumber}, nil
} }
select { select {
case vr := <-syncCh: case vr := <-syncCh:
return vr, nil return vr, nil
case <-ctx.Done(): case <-ctx.Done():
return SyncRange{}, ctx.Err() return SyncRange{}, ctx.Err()
case <-fm.f.disabledCh:
return SyncRange{HeadNumber: fm.f.targetView.headNumber}, nil
} }
} }

View file

@ -354,10 +354,8 @@ func WriteFilterMapBaseRows(db ethdb.KeyValueWriter, mapRowIndex uint64, rows []
} }
} }
func DeleteFilterMapRows(db ethdb.KeyValueRangeDeleter, mapRows common.Range[uint64]) { func DeleteFilterMapRows(db ethdb.KeyValueStore, mapRows common.Range[uint64], hashScheme bool, stopCallback func(bool) bool) error {
if err := db.DeleteRange(filterMapRowKey(mapRows.First(), false), filterMapRowKey(mapRows.AfterLast(), false)); err != nil { return SafeDeleteRange(db, filterMapRowKey(mapRows.First(), false), filterMapRowKey(mapRows.AfterLast(), false), hashScheme, stopCallback)
log.Crit("Failed to delete range of filter map rows", "err", err)
}
} }
// ReadFilterMapLastBlock retrieves the number of the block that generated the // ReadFilterMapLastBlock retrieves the number of the block that generated the
@ -368,7 +366,7 @@ func ReadFilterMapLastBlock(db ethdb.KeyValueReader, mapIndex uint32) (uint64, c
return 0, common.Hash{}, err return 0, common.Hash{}, err
} }
if len(enc) != 40 { if len(enc) != 40 {
return 0, common.Hash{}, errors.New("Invalid block number and id encoding") return 0, common.Hash{}, errors.New("invalid block number and id encoding")
} }
var id common.Hash var id common.Hash
copy(id[:], enc[8:]) copy(id[:], enc[8:])
@ -394,10 +392,8 @@ func DeleteFilterMapLastBlock(db ethdb.KeyValueWriter, mapIndex uint32) {
} }
} }
func DeleteFilterMapLastBlocks(db ethdb.KeyValueRangeDeleter, maps common.Range[uint32]) { func DeleteFilterMapLastBlocks(db ethdb.KeyValueStore, maps common.Range[uint32], hashScheme bool, stopCallback func(bool) bool) error {
if err := db.DeleteRange(filterMapLastBlockKey(maps.First()), filterMapLastBlockKey(maps.AfterLast())); err != nil { return SafeDeleteRange(db, filterMapLastBlockKey(maps.First()), filterMapLastBlockKey(maps.AfterLast()), hashScheme, stopCallback)
log.Crit("Failed to delete range of filter map last block pointers", "err", err)
}
} }
// ReadBlockLvPointer retrieves the starting log value index where the log values // ReadBlockLvPointer retrieves the starting log value index where the log values
@ -408,7 +404,7 @@ func ReadBlockLvPointer(db ethdb.KeyValueReader, blockNumber uint64) (uint64, er
return 0, err return 0, err
} }
if len(encPtr) != 8 { if len(encPtr) != 8 {
return 0, errors.New("Invalid log value pointer encoding") return 0, errors.New("invalid log value pointer encoding")
} }
return binary.BigEndian.Uint64(encPtr), nil return binary.BigEndian.Uint64(encPtr), nil
} }
@ -431,10 +427,8 @@ func DeleteBlockLvPointer(db ethdb.KeyValueWriter, blockNumber uint64) {
} }
} }
func DeleteBlockLvPointers(db ethdb.KeyValueRangeDeleter, blocks common.Range[uint64]) { func DeleteBlockLvPointers(db ethdb.KeyValueStore, blocks common.Range[uint64], hashScheme bool, stopCallback func(bool) bool) error {
if err := db.DeleteRange(filterMapBlockLVKey(blocks.First()), filterMapBlockLVKey(blocks.AfterLast())); err != nil { return SafeDeleteRange(db, filterMapBlockLVKey(blocks.First()), filterMapBlockLVKey(blocks.AfterLast()), hashScheme, stopCallback)
log.Crit("Failed to delete range of block log value pointers", "err", err)
}
} }
// FilterMapsRange is a storage representation of the block range covered by the // FilterMapsRange is a storage representation of the block range covered by the
@ -485,22 +479,22 @@ func DeleteFilterMapsRange(db ethdb.KeyValueWriter) {
} }
// deletePrefixRange deletes everything with the given prefix from the database. // deletePrefixRange deletes everything with the given prefix from the database.
func deletePrefixRange(db ethdb.KeyValueRangeDeleter, prefix []byte) error { func deletePrefixRange(db ethdb.KeyValueStore, prefix []byte, hashScheme bool, stopCallback func(bool) bool) error {
end := bytes.Clone(prefix) end := bytes.Clone(prefix)
end[len(end)-1]++ end[len(end)-1]++
return db.DeleteRange(prefix, end) return SafeDeleteRange(db, prefix, end, hashScheme, stopCallback)
} }
// DeleteFilterMapsDb removes the entire filter maps database // DeleteFilterMapsDb removes the entire filter maps database
func DeleteFilterMapsDb(db ethdb.KeyValueRangeDeleter) error { func DeleteFilterMapsDb(db ethdb.KeyValueStore, hashScheme bool, stopCallback func(bool) bool) error {
return deletePrefixRange(db, []byte(filterMapsPrefix)) return deletePrefixRange(db, []byte(filterMapsPrefix), hashScheme, stopCallback)
} }
// DeleteFilterMapsDb removes the old bloombits database and the associated // DeleteBloomBitsDb removes the old bloombits database and the associated
// chain indexer database. // chain indexer database.
func DeleteBloomBitsDb(db ethdb.KeyValueRangeDeleter) error { func DeleteBloomBitsDb(db ethdb.KeyValueStore, hashScheme bool, stopCallback func(bool) bool) error {
if err := deletePrefixRange(db, bloomBitsPrefix); err != nil { if err := deletePrefixRange(db, bloomBitsPrefix, hashScheme, stopCallback); err != nil {
return err return err
} }
return deletePrefixRange(db, bloomBitsIndexPrefix) return deletePrefixRange(db, bloomBitsMetaPrefix, hashScheme, stopCallback)
} }

View file

@ -20,18 +20,23 @@ import (
"bytes" "bytes"
"errors" "errors"
"fmt" "fmt"
"maps"
"os" "os"
"path/filepath" "path/filepath"
"slices"
"strings" "strings"
"time" "time"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/ethdb" "github.com/ethereum/go-ethereum/ethdb"
"github.com/ethereum/go-ethereum/ethdb/memorydb" "github.com/ethereum/go-ethereum/ethdb/memorydb"
"github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/log"
"github.com/olekukonko/tablewriter" "github.com/olekukonko/tablewriter"
) )
var ErrDeleteRangeInterrupted = errors.New("safe delete range operation interrupted")
// freezerdb is a database wrapper that enables ancient chain segment freezing. // freezerdb is a database wrapper that enables ancient chain segment freezing.
type freezerdb struct { type freezerdb struct {
ethdb.KeyValueStore ethdb.KeyValueStore
@ -360,39 +365,43 @@ func InspectDatabase(db ethdb.Database, keyPrefix, keyStart []byte) error {
logged = time.Now() logged = time.Now()
// Key-value store statistics // Key-value store statistics
headers stat headers stat
bodies stat bodies stat
receipts stat receipts stat
tds stat tds stat
numHashPairings stat numHashPairings stat
hashNumPairings stat hashNumPairings stat
legacyTries stat legacyTries stat
stateLookups stat stateLookups stat
accountTries stat accountTries stat
storageTries stat storageTries stat
codes stat codes stat
txLookups stat txLookups stat
accountSnaps stat accountSnaps stat
storageSnaps stat storageSnaps stat
preimages stat preimages stat
filterMaps stat beaconHeaders stat
beaconHeaders stat cliqueSnaps stat
cliqueSnaps stat bloomBits stat
filterMapRows stat
filterMapLastBlock stat
filterMapBlockLV stat
// Verkle statistics // Verkle statistics
verkleTries stat verkleTries stat
verkleStateLookups stat verkleStateLookups stat
// Les statistic
chtTrieNodes stat
bloomTrieNodes stat
// Meta- and unaccounted data // Meta- and unaccounted data
metadata stat metadata stat
unaccounted stat unaccounted stat
// Totals // Totals
total common.StorageSize total common.StorageSize
// This map tracks example keys for unaccounted data.
// For each unique two-byte prefix, the first unaccounted key encountered
// by the iterator will be stored.
unaccountedKeys = make(map[[2]byte][]byte)
) )
// Inspect key-value database first. // Inspect key-value database first.
for it.Next() { for it.Next() {
@ -436,20 +445,24 @@ func InspectDatabase(db ethdb.Database, keyPrefix, keyStart []byte) error {
metadata.Add(size) metadata.Add(size)
case bytes.HasPrefix(key, genesisPrefix) && len(key) == (len(genesisPrefix)+common.HashLength): case bytes.HasPrefix(key, genesisPrefix) && len(key) == (len(genesisPrefix)+common.HashLength):
metadata.Add(size) metadata.Add(size)
case bytes.HasPrefix(key, []byte(filterMapsPrefix)):
filterMaps.Add(size)
case bytes.HasPrefix(key, skeletonHeaderPrefix) && len(key) == (len(skeletonHeaderPrefix)+8): case bytes.HasPrefix(key, skeletonHeaderPrefix) && len(key) == (len(skeletonHeaderPrefix)+8):
beaconHeaders.Add(size) beaconHeaders.Add(size)
case bytes.HasPrefix(key, CliqueSnapshotPrefix) && len(key) == 7+common.HashLength: case bytes.HasPrefix(key, CliqueSnapshotPrefix) && len(key) == 7+common.HashLength:
cliqueSnaps.Add(size) cliqueSnaps.Add(size)
case bytes.HasPrefix(key, ChtTablePrefix) ||
bytes.HasPrefix(key, ChtIndexTablePrefix) || // new log index
bytes.HasPrefix(key, ChtPrefix): // Canonical hash trie case bytes.HasPrefix(key, filterMapRowPrefix) && len(key) <= len(filterMapRowPrefix)+9:
chtTrieNodes.Add(size) filterMapRows.Add(size)
case bytes.HasPrefix(key, BloomTrieTablePrefix) || case bytes.HasPrefix(key, filterMapLastBlockPrefix) && len(key) == len(filterMapLastBlockPrefix)+4:
bytes.HasPrefix(key, BloomTrieIndexPrefix) || filterMapLastBlock.Add(size)
bytes.HasPrefix(key, BloomTriePrefix): // Bloomtrie sub case bytes.HasPrefix(key, filterMapBlockLVPrefix) && len(key) == len(filterMapBlockLVPrefix)+8:
bloomTrieNodes.Add(size) filterMapBlockLV.Add(size)
// old log index (deprecated)
case bytes.HasPrefix(key, bloomBitsPrefix) && len(key) == (len(bloomBitsPrefix)+10+common.HashLength):
bloomBits.Add(size)
case bytes.HasPrefix(key, bloomBitsMetaPrefix) && len(key) < len(bloomBitsMetaPrefix)+8:
bloomBits.Add(size)
// Verkle trie data is detected, determine the sub-category // Verkle trie data is detected, determine the sub-category
case bytes.HasPrefix(key, VerklePrefix): case bytes.HasPrefix(key, VerklePrefix):
@ -468,24 +481,19 @@ func InspectDatabase(db ethdb.Database, keyPrefix, keyStart []byte) error {
default: default:
unaccounted.Add(size) unaccounted.Add(size)
} }
// Metadata keys
case slices.ContainsFunc(knownMetadataKeys, func(x []byte) bool { return bytes.Equal(x, key) }):
metadata.Add(size)
default: default:
var accounted bool unaccounted.Add(size)
for _, meta := range [][]byte{ if len(key) >= 2 {
databaseVersionKey, headHeaderKey, headBlockKey, headFastBlockKey, headFinalizedBlockKey, prefix := [2]byte(key[:2])
lastPivotKey, fastTrieProgressKey, snapshotDisabledKey, SnapshotRootKey, snapshotJournalKey, if _, ok := unaccountedKeys[prefix]; !ok {
snapshotGeneratorKey, snapshotRecoveryKey, txIndexTailKey, fastTxLookupLimitKey, unaccountedKeys[prefix] = bytes.Clone(key)
uncleanShutdownKey, badBlockKey, transitionStatusKey, skeletonSyncStatusKey,
persistentStateIDKey, trieJournalKey, snapshotSyncStatusKey, snapSyncStatusFlagKey,
} {
if bytes.Equal(key, meta) {
metadata.Add(size)
accounted = true
break
} }
} }
if !accounted {
unaccounted.Add(size)
}
} }
count++ count++
if count%1000 == 0 && time.Since(logged) > 8*time.Second { if count%1000 == 0 && time.Since(logged) > 8*time.Second {
@ -502,7 +510,10 @@ func InspectDatabase(db ethdb.Database, keyPrefix, keyStart []byte) error {
{"Key-Value store", "Block number->hash", numHashPairings.Size(), numHashPairings.Count()}, {"Key-Value store", "Block number->hash", numHashPairings.Size(), numHashPairings.Count()},
{"Key-Value store", "Block hash->number", hashNumPairings.Size(), hashNumPairings.Count()}, {"Key-Value store", "Block hash->number", hashNumPairings.Size(), hashNumPairings.Count()},
{"Key-Value store", "Transaction index", txLookups.Size(), txLookups.Count()}, {"Key-Value store", "Transaction index", txLookups.Size(), txLookups.Count()},
{"Key-Value store", "Log search index", filterMaps.Size(), filterMaps.Count()}, {"Key-Value store", "Log index filter-map rows", filterMapRows.Size(), filterMapRows.Count()},
{"Key-Value store", "Log index last-block-of-map", filterMapLastBlock.Size(), filterMapLastBlock.Count()},
{"Key-Value store", "Log index block-lv", filterMapBlockLV.Size(), filterMapBlockLV.Count()},
{"Key-Value store", "Log bloombits (deprecated)", bloomBits.Size(), bloomBits.Count()},
{"Key-Value store", "Contract codes", codes.Size(), codes.Count()}, {"Key-Value store", "Contract codes", codes.Size(), codes.Count()},
{"Key-Value store", "Hash trie nodes", legacyTries.Size(), legacyTries.Count()}, {"Key-Value store", "Hash trie nodes", legacyTries.Size(), legacyTries.Count()},
{"Key-Value store", "Path trie state lookups", stateLookups.Size(), stateLookups.Count()}, {"Key-Value store", "Path trie state lookups", stateLookups.Size(), stateLookups.Count()},
@ -516,8 +527,6 @@ func InspectDatabase(db ethdb.Database, keyPrefix, keyStart []byte) error {
{"Key-Value store", "Beacon sync headers", beaconHeaders.Size(), beaconHeaders.Count()}, {"Key-Value store", "Beacon sync headers", beaconHeaders.Size(), beaconHeaders.Count()},
{"Key-Value store", "Clique snapshots", cliqueSnaps.Size(), cliqueSnaps.Count()}, {"Key-Value store", "Clique snapshots", cliqueSnaps.Size(), cliqueSnaps.Count()},
{"Key-Value store", "Singleton metadata", metadata.Size(), metadata.Count()}, {"Key-Value store", "Singleton metadata", metadata.Size(), metadata.Count()},
{"Light client", "CHT trie nodes", chtTrieNodes.Size(), chtTrieNodes.Count()},
{"Light client", "Bloom trie nodes", bloomTrieNodes.Size(), bloomTrieNodes.Count()},
} }
// Inspect all registered append-only file store then. // Inspect all registered append-only file store then.
ancients, err := inspectFreezers(db) ancients, err := inspectFreezers(db)
@ -543,10 +552,23 @@ func InspectDatabase(db ethdb.Database, keyPrefix, keyStart []byte) error {
if unaccounted.size > 0 { if unaccounted.size > 0 {
log.Error("Database contains unaccounted data", "size", unaccounted.size, "count", unaccounted.count) log.Error("Database contains unaccounted data", "size", unaccounted.size, "count", unaccounted.count)
for _, e := range slices.SortedFunc(maps.Values(unaccountedKeys), bytes.Compare) {
log.Error(fmt.Sprintf(" example key: %x", e))
}
} }
return nil return nil
} }
// This is the list of known 'metadata' keys stored in the databasse.
var knownMetadataKeys = [][]byte{
databaseVersionKey, headHeaderKey, headBlockKey, headFastBlockKey, headFinalizedBlockKey,
lastPivotKey, fastTrieProgressKey, snapshotDisabledKey, SnapshotRootKey, snapshotJournalKey,
snapshotGeneratorKey, snapshotRecoveryKey, txIndexTailKey, fastTxLookupLimitKey,
uncleanShutdownKey, badBlockKey, transitionStatusKey, skeletonSyncStatusKey,
persistentStateIDKey, trieJournalKey, snapshotSyncStatusKey, snapSyncStatusFlagKey,
filterMapsRangeKey,
}
// printChainMetadata prints out chain metadata to stderr. // printChainMetadata prints out chain metadata to stderr.
func printChainMetadata(db ethdb.KeyValueStore) { func printChainMetadata(db ethdb.KeyValueStore) {
fmt.Fprintf(os.Stderr, "Chain metadata\n") fmt.Fprintf(os.Stderr, "Chain metadata\n")
@ -566,6 +588,7 @@ func ReadChainMetadata(db ethdb.KeyValueStore) [][]string {
} }
return fmt.Sprintf("%d (%#x)", *val, *val) return fmt.Sprintf("%d (%#x)", *val, *val)
} }
data := [][]string{ data := [][]string{
{"databaseVersion", pp(ReadDatabaseVersion(db))}, {"databaseVersion", pp(ReadDatabaseVersion(db))},
{"headBlockHash", fmt.Sprintf("%v", ReadHeadBlockHash(db))}, {"headBlockHash", fmt.Sprintf("%v", ReadHeadBlockHash(db))},
@ -582,5 +605,78 @@ func ReadChainMetadata(db ethdb.KeyValueStore) [][]string {
if b := ReadSkeletonSyncStatus(db); b != nil { if b := ReadSkeletonSyncStatus(db); b != nil {
data = append(data, []string{"SkeletonSyncStatus", string(b)}) data = append(data, []string{"SkeletonSyncStatus", string(b)})
} }
if fmr, ok, _ := ReadFilterMapsRange(db); ok {
data = append(data, []string{"filterMapsRange", fmt.Sprintf("%+v", fmr)})
}
return data return data
} }
// SafeDeleteRange deletes all of the keys (and values) in the range
// [start,end) (inclusive on start, exclusive on end).
// If hashScheme is true then it always uses an iterator and skips hashdb trie
// node entries. If it is false and the backing db is pebble db then it uses
// the fast native range delete.
// In case of fallback mode (hashdb or leveldb) the range deletion might be
// very slow depending on the number of entries. In this case stopCallback
// is periodically called and if it returns an error then SafeDeleteRange
// stops and also returns that error. The callback is not called if native
// range delete is used or there are a small number of keys only. The bool
// argument passed to the callback is true if enrties have actually been
// deleted already.
func SafeDeleteRange(db ethdb.KeyValueStore, start, end []byte, hashScheme bool, stopCallback func(bool) bool) error {
if !hashScheme {
// delete entire range; use fast native range delete on pebble db
for {
switch err := db.DeleteRange(start, end); {
case err == nil:
return nil
case errors.Is(err, ethdb.ErrTooManyKeys):
if stopCallback(true) {
return ErrDeleteRangeInterrupted
}
default:
return err
}
}
}
var (
count, deleted, skipped int
buff = crypto.NewKeccakState()
startTime = time.Now()
)
batch := db.NewBatch()
it := db.NewIterator(nil, start)
defer func() {
it.Release() // it might be replaced during the process
log.Debug("SafeDeleteRange finished", "deleted", deleted, "skipped", skipped, "elapsed", common.PrettyDuration(time.Since(startTime)))
}()
for it.Next() && bytes.Compare(end, it.Key()) > 0 {
// Prevent deletion for trie nodes in hash mode
if len(it.Key()) != 32 || crypto.HashData(buff, it.Value()) != common.BytesToHash(it.Key()) {
if err := batch.Delete(it.Key()); err != nil {
return err
}
deleted++
} else {
skipped++
}
count++
if count > 10000 { // should not block for more than a second
if err := batch.Write(); err != nil {
return err
}
if stopCallback(deleted != 0) {
return ErrDeleteRangeInterrupted
}
start = append(bytes.Clone(it.Key()), 0) // appending a zero gives us the next possible key
it.Release()
batch = db.NewBatch()
it = db.NewIterator(nil, start)
count = 0
}
}
return batch.Write()
}

View file

@ -128,29 +128,22 @@ var (
configPrefix = []byte("ethereum-config-") // config prefix for the db configPrefix = []byte("ethereum-config-") // config prefix for the db
genesisPrefix = []byte("ethereum-genesis-") // genesis state prefix for the db genesisPrefix = []byte("ethereum-genesis-") // genesis state prefix for the db
// bloomBitsIndexPrefix is the data table of a chain indexer to track its progress
bloomBitsIndexPrefix = []byte("iB")
ChtPrefix = []byte("chtRootV2-") // ChtPrefix + chtNum (uint64 big endian) -> trie root hash
ChtTablePrefix = []byte("cht-")
ChtIndexTablePrefix = []byte("chtIndexV2-")
BloomTriePrefix = []byte("bltRoot-") // BloomTriePrefix + bloomTrieNum (uint64 big endian) -> trie root hash
BloomTrieTablePrefix = []byte("blt-")
BloomTrieIndexPrefix = []byte("bltIndex-")
CliqueSnapshotPrefix = []byte("clique-") CliqueSnapshotPrefix = []byte("clique-")
BestUpdateKey = []byte("update-") // bigEndian64(syncPeriod) -> RLP(types.LightClientUpdate) (nextCommittee only referenced by root hash) BestUpdateKey = []byte("update-") // bigEndian64(syncPeriod) -> RLP(types.LightClientUpdate) (nextCommittee only referenced by root hash)
FixedCommitteeRootKey = []byte("fixedRoot-") // bigEndian64(syncPeriod) -> committee root hash FixedCommitteeRootKey = []byte("fixedRoot-") // bigEndian64(syncPeriod) -> committee root hash
SyncCommitteeKey = []byte("committee-") // bigEndian64(syncPeriod) -> serialized committee SyncCommitteeKey = []byte("committee-") // bigEndian64(syncPeriod) -> serialized committee
// new log index
filterMapsPrefix = "fm-" filterMapsPrefix = "fm-"
filterMapsRangeKey = []byte(filterMapsPrefix + "R") filterMapsRangeKey = []byte(filterMapsPrefix + "R")
filterMapRowPrefix = []byte(filterMapsPrefix + "r") // filterMapRowPrefix + mapRowIndex (uint64 big endian) -> filter row filterMapRowPrefix = []byte(filterMapsPrefix + "r") // filterMapRowPrefix + mapRowIndex (uint64 big endian) -> filter row
filterMapLastBlockPrefix = []byte(filterMapsPrefix + "b") // filterMapLastBlockPrefix + mapIndex (uint32 big endian) -> block number (uint64 big endian) filterMapLastBlockPrefix = []byte(filterMapsPrefix + "b") // filterMapLastBlockPrefix + mapIndex (uint32 big endian) -> block number (uint64 big endian)
filterMapBlockLVPrefix = []byte(filterMapsPrefix + "p") // filterMapBlockLVPrefix + num (uint64 big endian) -> log value pointer (uint64 big endian) filterMapBlockLVPrefix = []byte(filterMapsPrefix + "p") // filterMapBlockLVPrefix + num (uint64 big endian) -> log value pointer (uint64 big endian)
// old log index
bloomBitsMetaPrefix = []byte("iB")
preimageCounter = metrics.NewRegisteredCounter("db/preimage/total", nil) preimageCounter = metrics.NewRegisteredCounter("db/preimage/total", nil)
preimageHitsCounter = metrics.NewRegisteredCounter("db/preimage/hits", nil) preimageHitsCounter = metrics.NewRegisteredCounter("db/preimage/hits", nil)
preimageMissCounter = metrics.NewRegisteredCounter("db/preimage/miss", nil) preimageMissCounter = metrics.NewRegisteredCounter("db/preimage/miss", nil)

View file

@ -655,7 +655,7 @@ func testGenerateWithManyExtraAccounts(t *testing.T, scheme string) {
for i := 0; i < 1000; i++ { for i := 0; i < 1000; i++ {
acc := &types.StateAccount{Balance: uint256.NewInt(uint64(i)), Root: types.EmptyRootHash, CodeHash: types.EmptyCodeHash.Bytes()} acc := &types.StateAccount{Balance: uint256.NewInt(uint64(i)), Root: types.EmptyRootHash, CodeHash: types.EmptyCodeHash.Bytes()}
val, _ := rlp.EncodeToBytes(acc) val, _ := rlp.EncodeToBytes(acc)
key := hashData([]byte(fmt.Sprintf("acc-%d", i))) key := hashData(fmt.Appendf(nil, "acc-%d", i))
rawdb.WriteAccountSnapshot(helper.diskdb, key, val) rawdb.WriteAccountSnapshot(helper.diskdb, key, val)
} }
} }

View file

@ -329,27 +329,27 @@ func TestAccountIteratorTraversalValues(t *testing.T) {
h = make(map[common.Hash][]byte) h = make(map[common.Hash][]byte)
) )
for i := byte(2); i < 0xff; i++ { for i := byte(2); i < 0xff; i++ {
a[common.Hash{i}] = []byte(fmt.Sprintf("layer-%d, key %d", 0, i)) a[common.Hash{i}] = fmt.Appendf(nil, "layer-%d, key %d", 0, i)
if i > 20 && i%2 == 0 { if i > 20 && i%2 == 0 {
b[common.Hash{i}] = []byte(fmt.Sprintf("layer-%d, key %d", 1, i)) b[common.Hash{i}] = fmt.Appendf(nil, "layer-%d, key %d", 1, i)
} }
if i%4 == 0 { if i%4 == 0 {
c[common.Hash{i}] = []byte(fmt.Sprintf("layer-%d, key %d", 2, i)) c[common.Hash{i}] = fmt.Appendf(nil, "layer-%d, key %d", 2, i)
} }
if i%7 == 0 { if i%7 == 0 {
d[common.Hash{i}] = []byte(fmt.Sprintf("layer-%d, key %d", 3, i)) d[common.Hash{i}] = fmt.Appendf(nil, "layer-%d, key %d", 3, i)
} }
if i%8 == 0 { if i%8 == 0 {
e[common.Hash{i}] = []byte(fmt.Sprintf("layer-%d, key %d", 4, i)) e[common.Hash{i}] = fmt.Appendf(nil, "layer-%d, key %d", 4, i)
} }
if i > 50 || i < 85 { if i > 50 || i < 85 {
f[common.Hash{i}] = []byte(fmt.Sprintf("layer-%d, key %d", 5, i)) f[common.Hash{i}] = fmt.Appendf(nil, "layer-%d, key %d", 5, i)
} }
if i%64 == 0 { if i%64 == 0 {
g[common.Hash{i}] = []byte(fmt.Sprintf("layer-%d, key %d", 6, i)) g[common.Hash{i}] = fmt.Appendf(nil, "layer-%d, key %d", 6, i)
} }
if i%128 == 0 { if i%128 == 0 {
h[common.Hash{i}] = []byte(fmt.Sprintf("layer-%d, key %d", 7, i)) h[common.Hash{i}] = fmt.Appendf(nil, "layer-%d, key %d", 7, i)
} }
} }
// Assemble a stack of snapshots from the account layers // Assemble a stack of snapshots from the account layers
@ -428,27 +428,27 @@ func TestStorageIteratorTraversalValues(t *testing.T) {
h = make(map[common.Hash][]byte) h = make(map[common.Hash][]byte)
) )
for i := byte(2); i < 0xff; i++ { for i := byte(2); i < 0xff; i++ {
a[common.Hash{i}] = []byte(fmt.Sprintf("layer-%d, key %d", 0, i)) a[common.Hash{i}] = fmt.Appendf(nil, "layer-%d, key %d", 0, i)
if i > 20 && i%2 == 0 { if i > 20 && i%2 == 0 {
b[common.Hash{i}] = []byte(fmt.Sprintf("layer-%d, key %d", 1, i)) b[common.Hash{i}] = fmt.Appendf(nil, "layer-%d, key %d", 1, i)
} }
if i%4 == 0 { if i%4 == 0 {
c[common.Hash{i}] = []byte(fmt.Sprintf("layer-%d, key %d", 2, i)) c[common.Hash{i}] = fmt.Appendf(nil, "layer-%d, key %d", 2, i)
} }
if i%7 == 0 { if i%7 == 0 {
d[common.Hash{i}] = []byte(fmt.Sprintf("layer-%d, key %d", 3, i)) d[common.Hash{i}] = fmt.Appendf(nil, "layer-%d, key %d", 3, i)
} }
if i%8 == 0 { if i%8 == 0 {
e[common.Hash{i}] = []byte(fmt.Sprintf("layer-%d, key %d", 4, i)) e[common.Hash{i}] = fmt.Appendf(nil, "layer-%d, key %d", 4, i)
} }
if i > 50 || i < 85 { if i > 50 || i < 85 {
f[common.Hash{i}] = []byte(fmt.Sprintf("layer-%d, key %d", 5, i)) f[common.Hash{i}] = fmt.Appendf(nil, "layer-%d, key %d", 5, i)
} }
if i%64 == 0 { if i%64 == 0 {
g[common.Hash{i}] = []byte(fmt.Sprintf("layer-%d, key %d", 6, i)) g[common.Hash{i}] = fmt.Appendf(nil, "layer-%d, key %d", 6, i)
} }
if i%128 == 0 { if i%128 == 0 {
h[common.Hash{i}] = []byte(fmt.Sprintf("layer-%d, key %d", 7, i)) h[common.Hash{i}] = fmt.Appendf(nil, "layer-%d, key %d", 7, i)
} }
} }
// Assemble a stack of snapshots from the account layers // Assemble a stack of snapshots from the account layers

View file

@ -600,7 +600,6 @@ func (s *StateDB) getStateObject(addr common.Address) *stateObject {
// Insert into the live set // Insert into the live set
obj := newObject(s, addr, acct) obj := newObject(s, addr, acct)
s.setStateObject(obj) s.setStateObject(obj)
s.AccountLoaded++
return obj return obj
} }

View file

@ -87,8 +87,9 @@ type blobTxMeta struct {
hash common.Hash // Transaction hash to maintain the lookup table hash common.Hash // Transaction hash to maintain the lookup table
vhashes []common.Hash // Blob versioned hashes to maintain the lookup table vhashes []common.Hash // Blob versioned hashes to maintain the lookup table
id uint64 // Storage ID in the pool's persistent store id uint64 // Storage ID in the pool's persistent store
size uint32 // Byte size in the pool's persistent store storageSize uint32 // Byte size in the pool's persistent store
size uint64 // RLP-encoded size of transaction including the attached blob
nonce uint64 // Needed to prioritize inclusion order within an account nonce uint64 // Needed to prioritize inclusion order within an account
costCap *uint256.Int // Needed to validate cumulative balance sufficiency costCap *uint256.Int // Needed to validate cumulative balance sufficiency
@ -108,19 +109,20 @@ type blobTxMeta struct {
// newBlobTxMeta retrieves the indexed metadata fields from a blob transaction // newBlobTxMeta retrieves the indexed metadata fields from a blob transaction
// and assembles a helper struct to track in memory. // and assembles a helper struct to track in memory.
func newBlobTxMeta(id uint64, size uint32, tx *types.Transaction) *blobTxMeta { func newBlobTxMeta(id uint64, size uint64, storageSize uint32, tx *types.Transaction) *blobTxMeta {
meta := &blobTxMeta{ meta := &blobTxMeta{
hash: tx.Hash(), hash: tx.Hash(),
vhashes: tx.BlobHashes(), vhashes: tx.BlobHashes(),
id: id, id: id,
size: size, storageSize: storageSize,
nonce: tx.Nonce(), size: size,
costCap: uint256.MustFromBig(tx.Cost()), nonce: tx.Nonce(),
execTipCap: uint256.MustFromBig(tx.GasTipCap()), costCap: uint256.MustFromBig(tx.Cost()),
execFeeCap: uint256.MustFromBig(tx.GasFeeCap()), execTipCap: uint256.MustFromBig(tx.GasTipCap()),
blobFeeCap: uint256.MustFromBig(tx.BlobGasFeeCap()), execFeeCap: uint256.MustFromBig(tx.GasFeeCap()),
execGas: tx.Gas(), blobFeeCap: uint256.MustFromBig(tx.BlobGasFeeCap()),
blobGas: tx.BlobGas(), execGas: tx.Gas(),
blobGas: tx.BlobGas(),
} }
meta.basefeeJumps = dynamicFeeJumps(meta.execFeeCap) meta.basefeeJumps = dynamicFeeJumps(meta.execFeeCap)
meta.blobfeeJumps = dynamicFeeJumps(meta.blobFeeCap) meta.blobfeeJumps = dynamicFeeJumps(meta.blobFeeCap)
@ -480,7 +482,7 @@ func (p *BlobPool) parseTransaction(id uint64, size uint32, blob []byte) error {
return errors.New("missing blob sidecar") return errors.New("missing blob sidecar")
} }
meta := newBlobTxMeta(id, size, tx) meta := newBlobTxMeta(id, tx.Size(), size, tx)
if p.lookup.exists(meta.hash) { if p.lookup.exists(meta.hash) {
// This path is only possible after a crash, where deleted items are not // This path is only possible after a crash, where deleted items are not
// removed via the normal shutdown-startup procedure and thus may get // removed via the normal shutdown-startup procedure and thus may get
@ -507,7 +509,7 @@ func (p *BlobPool) parseTransaction(id uint64, size uint32, blob []byte) error {
p.spent[sender] = new(uint256.Int).Add(p.spent[sender], meta.costCap) p.spent[sender] = new(uint256.Int).Add(p.spent[sender], meta.costCap)
p.lookup.track(meta) p.lookup.track(meta)
p.stored += uint64(meta.size) p.stored += uint64(meta.storageSize)
return nil return nil
} }
@ -539,7 +541,7 @@ func (p *BlobPool) recheck(addr common.Address, inclusions map[common.Hash]uint6
ids = append(ids, txs[i].id) ids = append(ids, txs[i].id)
nonces = append(nonces, txs[i].nonce) nonces = append(nonces, txs[i].nonce)
p.stored -= uint64(txs[i].size) p.stored -= uint64(txs[i].storageSize)
p.lookup.untrack(txs[i]) p.lookup.untrack(txs[i])
// Included transactions blobs need to be moved to the limbo // Included transactions blobs need to be moved to the limbo
@ -580,7 +582,7 @@ func (p *BlobPool) recheck(addr common.Address, inclusions map[common.Hash]uint6
nonces = append(nonces, txs[0].nonce) nonces = append(nonces, txs[0].nonce)
p.spent[addr] = new(uint256.Int).Sub(p.spent[addr], txs[0].costCap) p.spent[addr] = new(uint256.Int).Sub(p.spent[addr], txs[0].costCap)
p.stored -= uint64(txs[0].size) p.stored -= uint64(txs[0].storageSize)
p.lookup.untrack(txs[0]) p.lookup.untrack(txs[0])
// Included transactions blobs need to be moved to the limbo // Included transactions blobs need to be moved to the limbo
@ -636,7 +638,7 @@ func (p *BlobPool) recheck(addr common.Address, inclusions map[common.Hash]uint6
dropRepeatedMeter.Mark(1) dropRepeatedMeter.Mark(1)
p.spent[addr] = new(uint256.Int).Sub(p.spent[addr], txs[i].costCap) p.spent[addr] = new(uint256.Int).Sub(p.spent[addr], txs[i].costCap)
p.stored -= uint64(txs[i].size) p.stored -= uint64(txs[i].storageSize)
p.lookup.untrack(txs[i]) p.lookup.untrack(txs[i])
if err := p.store.Delete(id); err != nil { if err := p.store.Delete(id); err != nil {
@ -658,7 +660,7 @@ func (p *BlobPool) recheck(addr common.Address, inclusions map[common.Hash]uint6
nonces = append(nonces, txs[j].nonce) nonces = append(nonces, txs[j].nonce)
p.spent[addr] = new(uint256.Int).Sub(p.spent[addr], txs[j].costCap) p.spent[addr] = new(uint256.Int).Sub(p.spent[addr], txs[j].costCap)
p.stored -= uint64(txs[j].size) p.stored -= uint64(txs[j].storageSize)
p.lookup.untrack(txs[j]) p.lookup.untrack(txs[j])
} }
txs = txs[:i] txs = txs[:i]
@ -696,7 +698,7 @@ func (p *BlobPool) recheck(addr common.Address, inclusions map[common.Hash]uint6
nonces = append(nonces, last.nonce) nonces = append(nonces, last.nonce)
p.spent[addr] = new(uint256.Int).Sub(p.spent[addr], last.costCap) p.spent[addr] = new(uint256.Int).Sub(p.spent[addr], last.costCap)
p.stored -= uint64(last.size) p.stored -= uint64(last.storageSize)
p.lookup.untrack(last) p.lookup.untrack(last)
} }
if len(txs) == 0 { if len(txs) == 0 {
@ -736,7 +738,7 @@ func (p *BlobPool) recheck(addr common.Address, inclusions map[common.Hash]uint6
nonces = append(nonces, last.nonce) nonces = append(nonces, last.nonce)
p.spent[addr] = new(uint256.Int).Sub(p.spent[addr], last.costCap) p.spent[addr] = new(uint256.Int).Sub(p.spent[addr], last.costCap)
p.stored -= uint64(last.size) p.stored -= uint64(last.storageSize)
p.lookup.untrack(last) p.lookup.untrack(last)
} }
p.index[addr] = txs p.index[addr] = txs
@ -1002,7 +1004,7 @@ func (p *BlobPool) reinject(addr common.Address, txhash common.Hash) error {
} }
// Update the indices and metrics // Update the indices and metrics
meta := newBlobTxMeta(id, p.store.Size(id), tx) meta := newBlobTxMeta(id, tx.Size(), p.store.Size(id), tx)
if _, ok := p.index[addr]; !ok { if _, ok := p.index[addr]; !ok {
if err := p.reserve(addr, true); err != nil { if err := p.reserve(addr, true); err != nil {
log.Warn("Failed to reserve account for blob pool", "tx", tx.Hash(), "from", addr, "err", err) log.Warn("Failed to reserve account for blob pool", "tx", tx.Hash(), "from", addr, "err", err)
@ -1016,7 +1018,7 @@ func (p *BlobPool) reinject(addr common.Address, txhash common.Hash) error {
p.spent[addr] = new(uint256.Int).Add(p.spent[addr], meta.costCap) p.spent[addr] = new(uint256.Int).Add(p.spent[addr], meta.costCap)
} }
p.lookup.track(meta) p.lookup.track(meta)
p.stored += uint64(meta.size) p.stored += uint64(meta.storageSize)
return nil return nil
} }
@ -1041,7 +1043,7 @@ func (p *BlobPool) SetGasTip(tip *big.Int) {
nonces = []uint64{tx.nonce} nonces = []uint64{tx.nonce}
) )
p.spent[addr] = new(uint256.Int).Sub(p.spent[addr], txs[i].costCap) p.spent[addr] = new(uint256.Int).Sub(p.spent[addr], txs[i].costCap)
p.stored -= uint64(tx.size) p.stored -= uint64(tx.storageSize)
p.lookup.untrack(tx) p.lookup.untrack(tx)
txs[i] = nil txs[i] = nil
@ -1051,7 +1053,7 @@ func (p *BlobPool) SetGasTip(tip *big.Int) {
nonces = append(nonces, tx.nonce) nonces = append(nonces, tx.nonce)
p.spent[addr] = new(uint256.Int).Sub(p.spent[addr], tx.costCap) p.spent[addr] = new(uint256.Int).Sub(p.spent[addr], tx.costCap)
p.stored -= uint64(tx.size) p.stored -= uint64(tx.storageSize)
p.lookup.untrack(tx) p.lookup.untrack(tx)
txs[i+1+j] = nil txs[i+1+j] = nil
} }
@ -1236,6 +1238,25 @@ func (p *BlobPool) GetRLP(hash common.Hash) []byte {
return p.getRLP(hash) return p.getRLP(hash)
} }
// GetMetadata returns the transaction type and transaction size with the
// given transaction hash.
//
// The size refers the length of the 'rlp encoding' of a blob transaction
// including the attached blobs.
func (p *BlobPool) GetMetadata(hash common.Hash) *txpool.TxMetadata {
p.lock.RLock()
defer p.lock.RUnlock()
size, ok := p.lookup.sizeOfTx(hash)
if !ok {
return nil
}
return &txpool.TxMetadata{
Type: types.BlobTxType,
Size: size,
}
}
// GetBlobs returns a number of blobs are proofs for the given versioned hashes. // GetBlobs returns a number of blobs are proofs for the given versioned hashes.
// This is a utility method for the engine API, enabling consensus clients to // This is a utility method for the engine API, enabling consensus clients to
// retrieve blobs from the pools directly instead of the network. // retrieve blobs from the pools directly instead of the network.
@ -1375,7 +1396,7 @@ func (p *BlobPool) add(tx *types.Transaction) (err error) {
if err != nil { if err != nil {
return err return err
} }
meta := newBlobTxMeta(id, p.store.Size(id), tx) meta := newBlobTxMeta(id, tx.Size(), p.store.Size(id), tx)
var ( var (
next = p.state.GetNonce(from) next = p.state.GetNonce(from)
@ -1403,7 +1424,7 @@ func (p *BlobPool) add(tx *types.Transaction) (err error) {
p.lookup.untrack(prev) p.lookup.untrack(prev)
p.lookup.track(meta) p.lookup.track(meta)
p.stored += uint64(meta.size) - uint64(prev.size) p.stored += uint64(meta.storageSize) - uint64(prev.storageSize)
} else { } else {
// Transaction extends previously scheduled ones // Transaction extends previously scheduled ones
p.index[from] = append(p.index[from], meta) p.index[from] = append(p.index[from], meta)
@ -1413,7 +1434,7 @@ func (p *BlobPool) add(tx *types.Transaction) (err error) {
} }
p.spent[from] = new(uint256.Int).Add(p.spent[from], meta.costCap) p.spent[from] = new(uint256.Int).Add(p.spent[from], meta.costCap)
p.lookup.track(meta) p.lookup.track(meta)
p.stored += uint64(meta.size) p.stored += uint64(meta.storageSize)
} }
// Recompute the rolling eviction fields. In case of a replacement, this will // Recompute the rolling eviction fields. In case of a replacement, this will
// recompute all subsequent fields. In case of an append, this will only do // recompute all subsequent fields. In case of an append, this will only do
@ -1500,7 +1521,7 @@ func (p *BlobPool) drop() {
p.index[from] = txs p.index[from] = txs
p.spent[from] = new(uint256.Int).Sub(p.spent[from], drop.costCap) p.spent[from] = new(uint256.Int).Sub(p.spent[from], drop.costCap)
} }
p.stored -= uint64(drop.size) p.stored -= uint64(drop.storageSize)
p.lookup.untrack(drop) p.lookup.untrack(drop)
// Remove the transaction from the pool's eviction heap: // Remove the transaction from the pool's eviction heap:

View file

@ -376,7 +376,7 @@ func verifyPoolInternals(t *testing.T, pool *BlobPool) {
var stored uint64 var stored uint64
for _, txs := range pool.index { for _, txs := range pool.index {
for _, tx := range txs { for _, tx := range txs {
stored += uint64(tx.size) stored += uint64(tx.storageSize)
} }
} }
if pool.stored != stored { if pool.stored != stored {
@ -1553,6 +1553,16 @@ func TestAdd(t *testing.T) {
if err := pool.add(signed); !errors.Is(err, add.err) { if err := pool.add(signed); !errors.Is(err, add.err) {
t.Errorf("test %d, tx %d: adding transaction error mismatch: have %v, want %v", i, j, err, add.err) t.Errorf("test %d, tx %d: adding transaction error mismatch: have %v, want %v", i, j, err, add.err)
} }
if add.err == nil {
size, exist := pool.lookup.sizeOfTx(signed.Hash())
if !exist {
t.Errorf("test %d, tx %d: failed to lookup transaction's size", i, j)
}
if size != signed.Size() {
t.Errorf("test %d, tx %d: transaction's size mismatches: have %v, want %v",
i, j, size, signed.Size())
}
}
verifyPoolInternals(t, pool) verifyPoolInternals(t, pool)
} }
verifyPoolInternals(t, pool) verifyPoolInternals(t, pool)

View file

@ -146,7 +146,7 @@ func TestPriceHeapSorting(t *testing.T) {
) )
index[addr] = []*blobTxMeta{{ index[addr] = []*blobTxMeta{{
id: uint64(j), id: uint64(j),
size: 128 * 1024, storageSize: 128 * 1024,
nonce: 0, nonce: 0,
execTipCap: execTip, execTipCap: execTip,
execFeeCap: execFee, execFeeCap: execFee,
@ -205,7 +205,7 @@ func benchmarkPriceHeapReinit(b *testing.B, datacap uint64) {
) )
index[addr] = []*blobTxMeta{{ index[addr] = []*blobTxMeta{{
id: uint64(i), id: uint64(i),
size: 128 * 1024, storageSize: 128 * 1024,
nonce: 0, nonce: 0,
execTipCap: execTip, execTipCap: execTip,
execFeeCap: execFee, execFeeCap: execFee,
@ -281,7 +281,7 @@ func benchmarkPriceHeapOverflow(b *testing.B, datacap uint64) {
) )
index[addr] = []*blobTxMeta{{ index[addr] = []*blobTxMeta{{
id: uint64(i), id: uint64(i),
size: 128 * 1024, storageSize: 128 * 1024,
nonce: 0, nonce: 0,
execTipCap: execTip, execTipCap: execTip,
execFeeCap: execFee, execFeeCap: execFee,
@ -312,7 +312,7 @@ func benchmarkPriceHeapOverflow(b *testing.B, datacap uint64) {
) )
metas[i] = &blobTxMeta{ metas[i] = &blobTxMeta{
id: uint64(int(blobs) + i), id: uint64(int(blobs) + i),
size: 128 * 1024, storageSize: 128 * 1024,
nonce: 0, nonce: 0,
execTipCap: execTip, execTipCap: execTip,
execFeeCap: execFee, execFeeCap: execFee,

View file

@ -20,18 +20,24 @@ import (
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
) )
type txMetadata struct {
id uint64 // the billy id of transction
size uint64 // the RLP encoded size of transaction (blobs are included)
}
// lookup maps blob versioned hashes to transaction hashes that include them, // lookup maps blob versioned hashes to transaction hashes that include them,
// and transaction hashes to billy entries that include them. // transaction hashes to billy entries that include them, transaction hashes
// to the transaction size
type lookup struct { type lookup struct {
blobIndex map[common.Hash]map[common.Hash]struct{} blobIndex map[common.Hash]map[common.Hash]struct{}
txIndex map[common.Hash]uint64 txIndex map[common.Hash]*txMetadata
} }
// newLookup creates a new index for tracking blob to tx; and tx to billy mappings. // newLookup creates a new index for tracking blob to tx; and tx to billy mappings.
func newLookup() *lookup { func newLookup() *lookup {
return &lookup{ return &lookup{
blobIndex: make(map[common.Hash]map[common.Hash]struct{}), blobIndex: make(map[common.Hash]map[common.Hash]struct{}),
txIndex: make(map[common.Hash]uint64), txIndex: make(map[common.Hash]*txMetadata),
} }
} }
@ -43,8 +49,11 @@ func (l *lookup) exists(txhash common.Hash) bool {
// storeidOfTx returns the datastore storage item id of a transaction. // storeidOfTx returns the datastore storage item id of a transaction.
func (l *lookup) storeidOfTx(txhash common.Hash) (uint64, bool) { func (l *lookup) storeidOfTx(txhash common.Hash) (uint64, bool) {
id, ok := l.txIndex[txhash] meta, ok := l.txIndex[txhash]
return id, ok if !ok {
return 0, false
}
return meta.id, true
} }
// storeidOfBlob returns the datastore storage item id of a blob. // storeidOfBlob returns the datastore storage item id of a blob.
@ -61,6 +70,15 @@ func (l *lookup) storeidOfBlob(vhash common.Hash) (uint64, bool) {
return 0, false // Weird, don't choke return 0, false // Weird, don't choke
} }
// sizeOfTx returns the RLP-encoded size of transaction
func (l *lookup) sizeOfTx(txhash common.Hash) (uint64, bool) {
meta, ok := l.txIndex[txhash]
if !ok {
return 0, false
}
return meta.size, true
}
// track inserts a new set of mappings from blob versioned hashes to transaction // track inserts a new set of mappings from blob versioned hashes to transaction
// hashes; and from transaction hashes to datastore storage item ids. // hashes; and from transaction hashes to datastore storage item ids.
func (l *lookup) track(tx *blobTxMeta) { func (l *lookup) track(tx *blobTxMeta) {
@ -71,8 +89,11 @@ func (l *lookup) track(tx *blobTxMeta) {
} }
l.blobIndex[vhash][tx.hash] = struct{}{} // may be double mapped if a tx contains the same blob twice l.blobIndex[vhash][tx.hash] = struct{}{} // may be double mapped if a tx contains the same blob twice
} }
// Map the transaction hash to the datastore id // Map the transaction hash to the datastore id and RLP-encoded transaction size
l.txIndex[tx.hash] = tx.id l.txIndex[tx.hash] = &txMetadata{
id: tx.id,
size: tx.size,
}
} }
// untrack removes a set of mappings from blob versioned hashes to transaction // untrack removes a set of mappings from blob versioned hashes to transaction

View file

@ -618,7 +618,7 @@ func (pool *LegacyPool) checkDelegationLimit(tx *types.Transaction) error {
from, _ := types.Sender(pool.signer, tx) // validated from, _ := types.Sender(pool.signer, tx) // validated
// Short circuit if the sender has neither delegation nor pending delegation. // Short circuit if the sender has neither delegation nor pending delegation.
if pool.currentState.GetCodeHash(from) == types.EmptyCodeHash && len(pool.all.auths[from]) == 0 { if pool.currentState.GetCodeHash(from) == types.EmptyCodeHash && pool.all.delegationTxsCount(from) == 0 {
return nil return nil
} }
pending := pool.pending[from] pending := pool.pending[from]
@ -1035,6 +1035,19 @@ func (pool *LegacyPool) GetRLP(hash common.Hash) []byte {
return encoded return encoded
} }
// GetMetadata returns the transaction type and transaction size with the
// given transaction hash.
func (pool *LegacyPool) GetMetadata(hash common.Hash) *txpool.TxMetadata {
tx := pool.all.Get(hash)
if tx == nil {
return nil
}
return &txpool.TxMetadata{
Type: tx.Type(),
Size: tx.Size(),
}
}
// GetBlobs is not supported by the legacy transaction pool, it is just here to // GetBlobs is not supported by the legacy transaction pool, it is just here to
// implement the txpool.SubPool interface. // implement the txpool.SubPool interface.
func (pool *LegacyPool) GetBlobs(vhashes []common.Hash) ([]*kzg4844.Blob, []*kzg4844.Proof) { func (pool *LegacyPool) GetBlobs(vhashes []common.Hash) ([]*kzg4844.Blob, []*kzg4844.Proof) {
@ -1849,6 +1862,13 @@ func (t *lookup) removeAuthorities(tx *types.Transaction) {
} }
} }
// delegationTxsCount returns the number of pending authorizations for the specified address.
func (t *lookup) delegationTxsCount(addr common.Address) int {
t.lock.RLock()
defer t.lock.RUnlock()
return len(t.auths[addr])
}
// numSlots calculates the number of slots needed for a single transaction. // numSlots calculates the number of slots needed for a single transaction.
func numSlots(tx *types.Transaction) int { func numSlots(tx *types.Transaction) int {
return int((tx.Size() + txSlotSize - 1) / txSlotSize) return int((tx.Size() + txSlotSize - 1) / txSlotSize)

View file

@ -0,0 +1,218 @@
// Copyright 2025 The go-ethereum Authors
// This file is part of the go-ethereum library.
//
// The go-ethereum library is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// The go-ethereum library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public License
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
package locals
import (
"errors"
"math/big"
"testing"
"time"
"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/txpool"
"github.com/ethereum/go-ethereum/core/txpool/legacypool"
"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/ethdb"
"github.com/ethereum/go-ethereum/params"
)
var (
key, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291")
address = crypto.PubkeyToAddress(key.PublicKey)
funds = big.NewInt(1000000000000000)
gspec = &core.Genesis{
Config: params.TestChainConfig,
Alloc: types.GenesisAlloc{
address: {Balance: funds},
},
BaseFee: big.NewInt(params.InitialBaseFee),
}
signer = types.LatestSigner(gspec.Config)
)
type testEnv struct {
chain *core.BlockChain
pool *txpool.TxPool
tracker *TxTracker
genDb ethdb.Database
}
func newTestEnv(t *testing.T, n int, gasTip uint64, journal string) *testEnv {
genDb, blocks, _ := core.GenerateChainWithGenesis(gspec, ethash.NewFaker(), n, func(i int, gen *core.BlockGen) {
tx, err := types.SignTx(types.NewTransaction(gen.TxNonce(address), common.Address{0x00}, big.NewInt(1000), params.TxGas, gen.BaseFee(), nil), signer, key)
if err != nil {
panic(err)
}
gen.AddTx(tx)
})
db := rawdb.NewMemoryDatabase()
chain, _ := core.NewBlockChain(db, nil, gspec, nil, ethash.NewFaker(), vm.Config{}, nil)
legacyPool := legacypool.New(legacypool.DefaultConfig, chain)
pool, err := txpool.New(gasTip, chain, []txpool.SubPool{legacyPool})
if err != nil {
t.Fatalf("Failed to create tx pool: %v", err)
}
if n, err := chain.InsertChain(blocks); err != nil {
t.Fatalf("Failed to process block %d: %v", n, err)
}
if err := pool.Sync(); err != nil {
t.Fatalf("Failed to sync the txpool, %v", err)
}
return &testEnv{
chain: chain,
pool: pool,
tracker: New(journal, time.Minute, gspec.Config, pool),
genDb: genDb,
}
}
func (env *testEnv) close() {
env.chain.Stop()
}
func (env *testEnv) setGasTip(gasTip uint64) {
env.pool.SetGasTip(new(big.Int).SetUint64(gasTip))
}
func (env *testEnv) makeTx(nonce uint64, gasPrice *big.Int) *types.Transaction {
if nonce == 0 {
head := env.chain.CurrentHeader()
state, _ := env.chain.StateAt(head.Root)
nonce = state.GetNonce(address)
}
if gasPrice == nil {
gasPrice = big.NewInt(params.GWei)
}
tx, _ := types.SignTx(types.NewTransaction(nonce, common.Address{0x00}, big.NewInt(1000), params.TxGas, gasPrice, nil), signer, key)
return tx
}
func (env *testEnv) makeTxs(n int) []*types.Transaction {
head := env.chain.CurrentHeader()
state, _ := env.chain.StateAt(head.Root)
nonce := state.GetNonce(address)
var txs []*types.Transaction
for i := 0; i < n; i++ {
tx, _ := types.SignTx(types.NewTransaction(nonce+uint64(i), common.Address{0x00}, big.NewInt(1000), params.TxGas, big.NewInt(params.GWei), nil), signer, key)
txs = append(txs, tx)
}
return txs
}
func (env *testEnv) commit() {
head := env.chain.CurrentBlock()
block := env.chain.GetBlock(head.Hash(), head.Number.Uint64())
blocks, _ := core.GenerateChain(env.chain.Config(), block, ethash.NewFaker(), env.genDb, 1, func(i int, gen *core.BlockGen) {
tx, err := types.SignTx(types.NewTransaction(gen.TxNonce(address), common.Address{0x00}, big.NewInt(1000), params.TxGas, gen.BaseFee(), nil), signer, key)
if err != nil {
panic(err)
}
gen.AddTx(tx)
})
env.chain.InsertChain(blocks)
if err := env.pool.Sync(); err != nil {
panic(err)
}
}
func TestRejectInvalids(t *testing.T) {
env := newTestEnv(t, 10, 0, "")
defer env.close()
var cases = []struct {
gasTip uint64
tx *types.Transaction
expErr error
commit bool
}{
{
tx: env.makeTx(5, nil), // stale
expErr: core.ErrNonceTooLow,
},
{
tx: env.makeTx(11, nil), // future transaction
expErr: nil,
},
{
gasTip: params.GWei,
tx: env.makeTx(0, new(big.Int).SetUint64(params.GWei/2)), // low price
expErr: txpool.ErrUnderpriced,
},
{
tx: types.NewTransaction(10, common.Address{0x00}, big.NewInt(1000), params.TxGas, big.NewInt(params.GWei), nil), // invalid signature
expErr: types.ErrInvalidSig,
},
{
commit: true,
tx: env.makeTx(10, nil), // stale
expErr: core.ErrNonceTooLow,
},
{
tx: env.makeTx(11, nil),
expErr: nil,
},
}
for i, c := range cases {
if c.gasTip != 0 {
env.setGasTip(c.gasTip)
}
if c.commit {
env.commit()
}
gotErr := env.tracker.Track(c.tx)
if c.expErr == nil && gotErr != nil {
t.Fatalf("%d, unexpected error: %v", i, gotErr)
}
if c.expErr != nil && !errors.Is(gotErr, c.expErr) {
t.Fatalf("%d, unexpected error, want: %v, got: %v", i, c.expErr, gotErr)
}
}
}
func TestResubmit(t *testing.T) {
env := newTestEnv(t, 10, 0, "")
defer env.close()
txs := env.makeTxs(10)
txsA := txs[:len(txs)/2]
txsB := txs[len(txs)/2:]
env.pool.Add(txsA, true)
pending, queued := env.pool.ContentFrom(address)
if len(pending) != len(txsA) || len(queued) != 0 {
t.Fatalf("Unexpected txpool content: %d, %d", len(pending), len(queued))
}
env.tracker.TrackAll(txs)
resubmit, all := env.tracker.recheck(true)
if len(resubmit) != len(txsB) {
t.Fatalf("Unexpected transactions to resubmit, got: %d, want: %d", len(resubmit), len(txsB))
}
if len(all) == 0 || len(all[address]) == 0 {
t.Fatalf("Unexpected transactions being tracked, got: %d, want: %d", 0, len(txs))
}
if len(all[address]) != len(txs) {
t.Fatalf("Unexpected transactions being tracked, got: %d, want: %d", len(all[address]), len(txs))
}
}

View file

@ -86,6 +86,12 @@ type PendingFilter struct {
OnlyBlobTxs bool // Return only blob transactions (block blob-space filling) OnlyBlobTxs bool // Return only blob transactions (block blob-space filling)
} }
// TxMetadata denotes the metadata of a transaction.
type TxMetadata struct {
Type uint8 // The type of the transaction
Size uint64 // The length of the 'rlp encoding' of a transaction
}
// SubPool represents a specialized transaction pool that lives on its own (e.g. // SubPool represents a specialized transaction pool that lives on its own (e.g.
// blob pool). Since independent of how many specialized pools we have, they do // blob pool). Since independent of how many specialized pools we have, they do
// need to be updated in lockstep and assemble into one coherent view for block // need to be updated in lockstep and assemble into one coherent view for block
@ -127,6 +133,10 @@ type SubPool interface {
// GetRLP returns a RLP-encoded transaction if it is contained in the pool. // GetRLP returns a RLP-encoded transaction if it is contained in the pool.
GetRLP(hash common.Hash) []byte GetRLP(hash common.Hash) []byte
// GetMetadata returns the transaction type and transaction size with the
// given transaction hash.
GetMetadata(hash common.Hash) *TxMetadata
// GetBlobs returns a number of blobs are proofs for the given versioned hashes. // GetBlobs returns a number of blobs are proofs for the given versioned hashes.
// This is a utility method for the engine API, enabling consensus clients to // This is a utility method for the engine API, enabling consensus clients to
// retrieve blobs from the pools directly instead of the network. // retrieve blobs from the pools directly instead of the network.

View file

@ -24,11 +24,13 @@ import (
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core" "github.com/ethereum/go-ethereum/core"
"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/crypto/kzg4844" "github.com/ethereum/go-ethereum/crypto/kzg4844"
"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/metrics" "github.com/ethereum/go-ethereum/metrics"
"github.com/ethereum/go-ethereum/params"
) )
// TxStatus is the current status of a transaction as seen by the pool. // TxStatus is the current status of a transaction as seen by the pool.
@ -53,11 +55,17 @@ var (
// BlockChain defines the minimal set of methods needed to back a tx pool with // BlockChain defines the minimal set of methods needed to back a tx pool with
// a chain. Exists to allow mocking the live chain out of tests. // a chain. Exists to allow mocking the live chain out of tests.
type BlockChain interface { type BlockChain interface {
// Config retrieves the chain's fork configuration.
Config() *params.ChainConfig
// CurrentBlock returns the current head of the chain. // CurrentBlock returns the current head of the chain.
CurrentBlock() *types.Header CurrentBlock() *types.Header
// SubscribeChainHeadEvent subscribes to new blocks being added to the chain. // SubscribeChainHeadEvent subscribes to new blocks being added to the chain.
SubscribeChainHeadEvent(ch chan<- core.ChainHeadEvent) event.Subscription SubscribeChainHeadEvent(ch chan<- core.ChainHeadEvent) event.Subscription
// StateAt returns a state database for a given root hash (generally the head).
StateAt(root common.Hash) (*state.StateDB, error)
} }
// TxPool is an aggregator for various transaction specific pools, collectively // TxPool is an aggregator for various transaction specific pools, collectively
@ -67,6 +75,11 @@ type BlockChain interface {
// resource constraints. // resource constraints.
type TxPool struct { type TxPool struct {
subpools []SubPool // List of subpools for specialized transaction handling subpools []SubPool // List of subpools for specialized transaction handling
chain BlockChain
signer types.Signer
stateLock sync.RWMutex // The lock for protecting state instance
state *state.StateDB // Current state at the blockchain head
reservations map[common.Address]SubPool // Map with the account to pool reservations reservations map[common.Address]SubPool // Map with the account to pool reservations
reserveLock sync.Mutex // Lock protecting the account reservations reserveLock sync.Mutex // Lock protecting the account reservations
@ -86,8 +99,21 @@ func New(gasTip uint64, chain BlockChain, subpools []SubPool) (*TxPool, error) {
// during initialization. // during initialization.
head := chain.CurrentBlock() head := chain.CurrentBlock()
// Initialize the state with head block, or fallback to empty one in
// case the head state is not available (might occur when node is not
// fully synced).
statedb, err := chain.StateAt(head.Root)
if err != nil {
statedb, err = chain.StateAt(types.EmptyRootHash)
}
if err != nil {
return nil, err
}
pool := &TxPool{ pool := &TxPool{
subpools: subpools, subpools: subpools,
chain: chain,
signer: types.LatestSigner(chain.Config()),
state: statedb,
reservations: make(map[common.Address]SubPool), reservations: make(map[common.Address]SubPool),
quit: make(chan chan error), quit: make(chan chan error),
term: make(chan struct{}), term: make(chan struct{}),
@ -101,7 +127,7 @@ func New(gasTip uint64, chain BlockChain, subpools []SubPool) (*TxPool, error) {
return nil, err return nil, err
} }
} }
go pool.loop(head, chain) go pool.loop(head)
return pool, nil return pool, nil
} }
@ -179,14 +205,14 @@ func (p *TxPool) Close() error {
// loop is the transaction pool's main event loop, waiting for and reacting to // loop is the transaction pool's main event loop, waiting for and reacting to
// outside blockchain events as well as for various reporting and transaction // outside blockchain events as well as for various reporting and transaction
// eviction events. // eviction events.
func (p *TxPool) loop(head *types.Header, chain BlockChain) { func (p *TxPool) loop(head *types.Header) {
// Close the termination marker when the pool stops // Close the termination marker when the pool stops
defer close(p.term) defer close(p.term)
// Subscribe to chain head events to trigger subpool resets // Subscribe to chain head events to trigger subpool resets
var ( var (
newHeadCh = make(chan core.ChainHeadEvent) newHeadCh = make(chan core.ChainHeadEvent)
newHeadSub = chain.SubscribeChainHeadEvent(newHeadCh) newHeadSub = p.chain.SubscribeChainHeadEvent(newHeadCh)
) )
defer newHeadSub.Unsubscribe() defer newHeadSub.Unsubscribe()
@ -219,6 +245,14 @@ func (p *TxPool) loop(head *types.Header, chain BlockChain) {
// Try to inject a busy marker and start a reset if successful // Try to inject a busy marker and start a reset if successful
select { select {
case resetBusy <- struct{}{}: case resetBusy <- struct{}{}:
statedb, err := p.chain.StateAt(newHead.Root)
if err != nil {
log.Crit("Failed to reset txpool state", "err", err)
}
p.stateLock.Lock()
p.state = statedb
p.stateLock.Unlock()
// Busy marker injected, start a new subpool reset // Busy marker injected, start a new subpool reset
go func(oldHead, newHead *types.Header) { go func(oldHead, newHead *types.Header) {
for _, subpool := range p.subpools { for _, subpool := range p.subpools {
@ -320,6 +354,17 @@ func (p *TxPool) GetRLP(hash common.Hash) []byte {
return nil return nil
} }
// GetMetadata returns the transaction type and transaction size with the given
// hash.
func (p *TxPool) GetMetadata(hash common.Hash) *TxMetadata {
for _, subpool := range p.subpools {
if meta := subpool.GetMetadata(hash); meta != nil {
return meta
}
}
return nil
}
// GetBlobs returns a number of blobs are proofs for the given versioned hashes. // GetBlobs returns a number of blobs are proofs for the given versioned hashes.
// This is a utility method for the engine API, enabling consensus clients to // This is a utility method for the engine API, enabling consensus clients to
// retrieve blobs from the pools directly instead of the network. // retrieve blobs from the pools directly instead of the network.
@ -339,6 +384,20 @@ func (p *TxPool) GetBlobs(vhashes []common.Hash) ([]*kzg4844.Blob, []*kzg4844.Pr
// ValidateTxBasics checks whether a transaction is valid according to the consensus // ValidateTxBasics checks whether a transaction is valid according to the consensus
// rules, but does not check state-dependent validation such as sufficient balance. // rules, but does not check state-dependent validation such as sufficient balance.
func (p *TxPool) ValidateTxBasics(tx *types.Transaction) error { func (p *TxPool) ValidateTxBasics(tx *types.Transaction) error {
addr, err := types.Sender(p.signer, tx)
if err != nil {
return err
}
// Reject transactions with stale nonce. Gapped-nonce future transactions
// are considered valid and will be handled by the subpool according to its
// internal policy.
p.stateLock.RLock()
nonce := p.state.GetNonce(addr)
p.stateLock.RUnlock()
if nonce > tx.Nonce() {
return core.ErrNonceTooLow
}
for _, subpool := range p.subpools { for _, subpool := range p.subpools {
if subpool.Filter(tx) { if subpool.Filter(tx) {
return subpool.ValidateTxBasics(tx) return subpool.ValidateTxBasics(tx)
@ -418,9 +477,9 @@ func (p *TxPool) SubscribeTransactions(ch chan<- core.NewTxsEvent, reorgs bool)
return p.subs.Track(event.JoinSubscriptions(subs...)) return p.subs.Track(event.JoinSubscriptions(subs...))
} }
// Nonce returns the next nonce of an account, with all transactions executable // PoolNonce returns the next nonce of an account, with all transactions executable
// by the pool already applied on top. // by the pool already applied on top.
func (p *TxPool) Nonce(addr common.Address) uint64 { func (p *TxPool) PoolNonce(addr common.Address) uint64 {
// Since (for now) accounts are unique to subpools, only one pool will have // Since (for now) accounts are unique to subpools, only one pool will have
// (at max) a non-state nonce. To avoid stateful lookups, just return the // (at max) a non-state nonce. To avoid stateful lookups, just return the
// highest nonce for now. // highest nonce for now.
@ -433,6 +492,15 @@ func (p *TxPool) Nonce(addr common.Address) uint64 {
return nonce return nonce
} }
// Nonce returns the next nonce of an account at the current chain head. Unlike
// PoolNonce, this function does not account for pending executable transactions.
func (p *TxPool) Nonce(addr common.Address) uint64 {
p.stateLock.RLock()
defer p.stateLock.RUnlock()
return p.state.GetNonce(addr)
}
// Stats retrieves the current pool stats, namely the number of pending and the // Stats retrieves the current pool stats, namely the number of pending and the
// number of queued (non-executable) transactions. // number of queued (non-executable) transactions.
func (p *TxPool) Stats() (int, int) { func (p *TxPool) Stats() (int, int) {
@ -508,6 +576,9 @@ func (p *TxPool) Sync() error {
// Clear removes all tracked txs from the subpools. // Clear removes all tracked txs from the subpools.
func (p *TxPool) Clear() { func (p *TxPool) Clear() {
// Invoke Sync to ensure that txs pending addition don't get added to the pool after
// the subpools are subsequently cleared
p.Sync()
for _, subpool := range p.subpools { for _, subpool := range p.subpools {
subpool.Clear() subpool.Clear()
} }

View file

@ -34,6 +34,7 @@ import (
"github.com/ethereum/go-ethereum/core/txpool" "github.com/ethereum/go-ethereum/core/txpool"
"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/eth/ethconfig"
"github.com/ethereum/go-ethereum/eth/gasprice" "github.com/ethereum/go-ethereum/eth/gasprice"
"github.com/ethereum/go-ethereum/eth/tracers" "github.com/ethereum/go-ethereum/eth/tracers"
"github.com/ethereum/go-ethereum/ethdb" "github.com/ethereum/go-ethereum/ethdb"
@ -91,7 +92,13 @@ func (b *EthAPIBackend) HeaderByNumber(ctx context.Context, number rpc.BlockNumb
} }
return block, nil return block, nil
} }
return b.eth.blockchain.GetHeaderByNumber(uint64(number)), nil var bn uint64
if number == rpc.EarliestBlockNumber {
bn = b.eth.blockchain.HistoryPruningCutoff()
} else {
bn = uint64(number)
}
return b.eth.blockchain.GetHeaderByNumber(bn), nil
} }
func (b *EthAPIBackend) HeaderByNumberOrHash(ctx context.Context, blockNrOrHash rpc.BlockNumberOrHash) (*types.Header, error) { func (b *EthAPIBackend) HeaderByNumberOrHash(ctx context.Context, blockNrOrHash rpc.BlockNumberOrHash) (*types.Header, error) {
@ -143,11 +150,27 @@ func (b *EthAPIBackend) BlockByNumber(ctx context.Context, number rpc.BlockNumbe
} }
return b.eth.blockchain.GetBlock(header.Hash(), header.Number.Uint64()), nil return b.eth.blockchain.GetBlock(header.Hash(), header.Number.Uint64()), nil
} }
return b.eth.blockchain.GetBlockByNumber(uint64(number)), nil bn := uint64(number) // the resolved number
if number == rpc.EarliestBlockNumber {
bn = b.eth.blockchain.HistoryPruningCutoff()
}
block := b.eth.blockchain.GetBlockByNumber(bn)
if block == nil && bn < b.eth.blockchain.HistoryPruningCutoff() {
return nil, &ethconfig.PrunedHistoryError{}
}
return block, nil
} }
func (b *EthAPIBackend) BlockByHash(ctx context.Context, hash common.Hash) (*types.Block, error) { func (b *EthAPIBackend) BlockByHash(ctx context.Context, hash common.Hash) (*types.Block, error) {
return b.eth.blockchain.GetBlockByHash(hash), nil number := b.eth.blockchain.GetBlockNumber(hash)
if number == nil {
return nil, nil
}
block := b.eth.blockchain.GetBlock(hash, *number)
if block == nil && *number < b.eth.blockchain.HistoryPruningCutoff() {
return nil, &ethconfig.PrunedHistoryError{}
}
return block, nil
} }
// GetBody returns body of a block. It does not resolve special block numbers. // GetBody returns body of a block. It does not resolve special block numbers.
@ -155,10 +178,14 @@ func (b *EthAPIBackend) GetBody(ctx context.Context, hash common.Hash, number rp
if number < 0 || hash == (common.Hash{}) { if number < 0 || hash == (common.Hash{}) {
return nil, errors.New("invalid arguments; expect hash and no special block numbers") return nil, errors.New("invalid arguments; expect hash and no special block numbers")
} }
if body := b.eth.blockchain.GetBody(hash); body != nil { body := b.eth.blockchain.GetBody(hash)
return body, nil if body == nil {
if uint64(number) < b.eth.blockchain.HistoryPruningCutoff() {
return nil, &ethconfig.PrunedHistoryError{}
}
return nil, errors.New("block body not found")
} }
return nil, errors.New("block body not found") return body, nil
} }
func (b *EthAPIBackend) BlockByNumberOrHash(ctx context.Context, blockNrOrHash rpc.BlockNumberOrHash) (*types.Block, error) { func (b *EthAPIBackend) BlockByNumberOrHash(ctx context.Context, blockNrOrHash rpc.BlockNumberOrHash) (*types.Block, error) {
@ -175,6 +202,9 @@ func (b *EthAPIBackend) BlockByNumberOrHash(ctx context.Context, blockNrOrHash r
} }
block := b.eth.blockchain.GetBlock(hash, header.Number.Uint64()) block := b.eth.blockchain.GetBlock(hash, header.Number.Uint64())
if block == nil { if block == nil {
if header.Number.Uint64() < b.eth.blockchain.HistoryPruningCutoff() {
return nil, &ethconfig.PrunedHistoryError{}
}
return nil, errors.New("header found, but block body is missing") return nil, errors.New("header found, but block body is missing")
} }
return block, nil return block, nil
@ -234,6 +264,10 @@ func (b *EthAPIBackend) StateAndHeaderByNumberOrHash(ctx context.Context, blockN
return nil, nil, errors.New("invalid arguments; neither block nor hash specified") return nil, nil, errors.New("invalid arguments; neither block nor hash specified")
} }
func (b *EthAPIBackend) HistoryPruningCutoff() uint64 {
return b.eth.blockchain.HistoryPruningCutoff()
}
func (b *EthAPIBackend) GetReceipts(ctx context.Context, hash common.Hash) (types.Receipts, error) { func (b *EthAPIBackend) GetReceipts(ctx context.Context, hash common.Hash) (types.Receipts, error) {
return b.eth.blockchain.GetReceiptsByHash(hash), nil return b.eth.blockchain.GetReceiptsByHash(hash), nil
} }
@ -327,7 +361,7 @@ func (b *EthAPIBackend) GetTransaction(ctx context.Context, txHash common.Hash)
} }
func (b *EthAPIBackend) GetPoolNonce(ctx context.Context, addr common.Address) (uint64, error) { func (b *EthAPIBackend) GetPoolNonce(ctx context.Context, addr common.Address) (uint64, error) {
return b.eth.txPool.Nonce(addr), nil return b.eth.txPool.PoolNonce(addr), nil
} }
func (b *EthAPIBackend) Stats() (runnable int, blocked int) { func (b *EthAPIBackend) Stats() (runnable int, blocked int) {

View file

@ -239,9 +239,19 @@ func New(stack *node.Node, config *ethconfig.Config) (*Ethereum, error) {
if err != nil { if err != nil {
return nil, err return nil, err
} }
fmConfig := filtermaps.Config{History: config.LogHistory, Disabled: config.LogNoHistory, ExportFileName: config.LogExportCheckpoints} fmConfig := filtermaps.Config{
History: config.LogHistory,
Disabled: config.LogNoHistory,
ExportFileName: config.LogExportCheckpoints,
HashScheme: scheme == rawdb.HashScheme,
}
chainView := eth.newChainView(eth.blockchain.CurrentBlock()) chainView := eth.newChainView(eth.blockchain.CurrentBlock())
eth.filterMaps = filtermaps.NewFilterMaps(chainDb, chainView, 0, 0, filtermaps.DefaultParams, fmConfig) historyCutoff := eth.blockchain.HistoryPruningCutoff()
var finalBlock uint64
if fb := eth.blockchain.CurrentFinalBlock(); fb != nil {
finalBlock = fb.Number.Uint64()
}
eth.filterMaps = filtermaps.NewFilterMaps(chainDb, chainView, historyCutoff, finalBlock, filtermaps.DefaultParams, fmConfig)
eth.closeFilterMaps = make(chan chan struct{}) eth.closeFilterMaps = make(chan chan struct{})
if config.BlobPool.Datadir != "" { if config.BlobPool.Datadir != "" {

View file

@ -275,7 +275,7 @@ func (s *skeleton) startup() {
for { for {
// If the sync cycle terminated or was terminated, propagate up when // If the sync cycle terminated or was terminated, propagate up when
// higher layers request termination. There's no fancy explicit error // higher layers request termination. There's no fancy explicit error
// signalling as the sync loop should never terminate (TM). // signaling as the sync loop should never terminate (TM).
newhead, err := s.sync(head) newhead, err := s.sync(head)
switch { switch {
case err == errSyncLinked: case err == errSyncLinked:

View file

@ -90,3 +90,9 @@ var HistoryPrunePoints = map[common.Hash]*HistoryPrunePoint{
BlockHash: common.HexToHash("0x229f6b18ca1552f1d5146deceb5387333f40dc6275aebee3f2c5c4ece07d02db"), BlockHash: common.HexToHash("0x229f6b18ca1552f1d5146deceb5387333f40dc6275aebee3f2c5c4ece07d02db"),
}, },
} }
// PrunedHistoryError is returned when the requested history is pruned.
type PrunedHistoryError struct{}
func (e *PrunedHistoryError) Error() string { return "pruned history unavailable" }
func (e *PrunedHistoryError) ErrorCode() int { return 4444 }

View file

@ -29,6 +29,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/core/types" "github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/eth/ethconfig"
"github.com/ethereum/go-ethereum/internal/ethapi" "github.com/ethereum/go-ethereum/internal/ethapi"
"github.com/ethereum/go-ethereum/rpc" "github.com/ethereum/go-ethereum/rpc"
) )
@ -354,9 +355,13 @@ func (api *FilterAPI) GetLogs(ctx context.Context, crit FilterCriteria) ([]*type
if crit.ToBlock != nil { if crit.ToBlock != nil {
end = crit.ToBlock.Int64() end = crit.ToBlock.Int64()
} }
// Block numbers below 0 are special cases.
if begin > 0 && end > 0 && begin > end { if begin > 0 && end > 0 && begin > end {
return nil, errInvalidBlockRange return nil, errInvalidBlockRange
} }
if begin > 0 && begin < int64(api.events.backend.HistoryPruningCutoff()) {
return nil, &ethconfig.PrunedHistoryError{}
}
// Construct the range filter // Construct the range filter
filter = api.sys.NewRangeFilter(begin, end, crit.Addresses, crit.Topics) filter = api.sys.NewRangeFilter(begin, end, crit.Addresses, crit.Topics)
} }

View file

@ -27,6 +27,7 @@ import (
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/filtermaps" "github.com/ethereum/go-ethereum/core/filtermaps"
"github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/eth/ethconfig"
"github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/log"
"github.com/ethereum/go-ethereum/rpc" "github.com/ethereum/go-ethereum/rpc"
) )
@ -86,6 +87,9 @@ func (f *Filter) Logs(ctx context.Context) ([]*types.Log, error) {
if header == nil { if header == nil {
return nil, errors.New("unknown block") return nil, errors.New("unknown block")
} }
if header.Number.Uint64() < f.sys.backend.HistoryPruningCutoff() {
return nil, &ethconfig.PrunedHistoryError{}
}
return f.blockLogs(ctx, header) return f.blockLogs(ctx, header)
} }
@ -114,11 +118,19 @@ func (f *Filter) Logs(ctx context.Context) ([]*types.Log, error) {
return 0, errors.New("safe header not found") return 0, errors.New("safe header not found")
} }
return hdr.Number.Uint64(), nil return hdr.Number.Uint64(), nil
case rpc.EarliestBlockNumber.Int64():
earliest := f.sys.backend.HistoryPruningCutoff()
hdr, _ := f.sys.backend.HeaderByNumber(ctx, rpc.BlockNumber(earliest))
if hdr == nil {
return 0, errors.New("earliest header not found")
}
return hdr.Number.Uint64(), nil
default:
if number < 0 {
return 0, errors.New("negative block number")
}
return uint64(number), nil
} }
if number < 0 {
return 0, errors.New("negative block number")
}
return uint64(number), nil
} }
// range query need to resolve the special begin/end block number // range query need to resolve the special begin/end block number

View file

@ -31,6 +31,7 @@ import (
"github.com/ethereum/go-ethereum/core" "github.com/ethereum/go-ethereum/core"
"github.com/ethereum/go-ethereum/core/filtermaps" "github.com/ethereum/go-ethereum/core/filtermaps"
"github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/eth/ethconfig"
"github.com/ethereum/go-ethereum/ethdb" "github.com/ethereum/go-ethereum/ethdb"
"github.com/ethereum/go-ethereum/event" "github.com/ethereum/go-ethereum/event"
"github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/log"
@ -64,6 +65,7 @@ type Backend interface {
CurrentHeader() *types.Header CurrentHeader() *types.Header
ChainConfig() *params.ChainConfig ChainConfig() *params.ChainConfig
HistoryPruningCutoff() uint64
SubscribeNewTxsEvent(chan<- core.NewTxsEvent) event.Subscription SubscribeNewTxsEvent(chan<- core.NewTxsEvent) event.Subscription
SubscribeChainEvent(ch chan<- core.ChainEvent) event.Subscription SubscribeChainEvent(ch chan<- core.ChainEvent) event.Subscription
SubscribeRemovedLogsEvent(ch chan<- core.RemovedLogsEvent) event.Subscription SubscribeRemovedLogsEvent(ch chan<- core.RemovedLogsEvent) event.Subscription
@ -304,6 +306,14 @@ func (es *EventSystem) SubscribeLogs(crit ethereum.FilterQuery, logs chan []*typ
return nil, errPendingLogsUnsupported return nil, errPendingLogsUnsupported
} }
if from == rpc.EarliestBlockNumber {
from = rpc.BlockNumber(es.backend.HistoryPruningCutoff())
}
// Queries beyond the pruning cutoff are not supported.
if uint64(from) < es.backend.HistoryPruningCutoff() {
return nil, &ethconfig.PrunedHistoryError{}
}
// only interested in new mined logs // only interested in new mined logs
if from == rpc.LatestBlockNumber && to == rpc.LatestBlockNumber { if from == rpc.LatestBlockNumber && to == rpc.LatestBlockNumber {
return es.subscribeLogs(crit, logs), nil return es.subscribeLogs(crit, logs), nil

View file

@ -181,6 +181,10 @@ func (b *testBackend) setPending(block *types.Block, receipts types.Receipts) {
b.pendingReceipts = receipts b.pendingReceipts = receipts
} }
func (b *testBackend) HistoryPruningCutoff() uint64 {
return 0
}
func newTestFilterSystem(db ethdb.Database, cfg Config) (*testBackend, *FilterSystem) { func newTestFilterSystem(db ethdb.Database, cfg Config) (*testBackend, *FilterSystem) {
backend := &testBackend{db: db} backend := &testBackend{db: db}
sys := NewFilterSystem(backend, cfg) sys := NewFilterSystem(backend, cfg)

View file

@ -223,7 +223,9 @@ func run(ctx context.Context, call *core.Message, opts *Options) (*core.Executio
dirtyState = opts.State.Copy() dirtyState = opts.State.Copy()
) )
if opts.BlockOverrides != nil { if opts.BlockOverrides != nil {
opts.BlockOverrides.Apply(&evmContext) if err := opts.BlockOverrides.Apply(&evmContext); err != nil {
return nil, err
}
} }
// Lower the basefee to 0 to avoid breaking EVM // Lower the basefee to 0 to avoid breaking EVM
// invariants (basefee < feecap). // invariants (basefee < feecap).

View file

@ -71,6 +71,10 @@ type txPool interface {
// with given tx hash. // with given tx hash.
GetRLP(hash common.Hash) []byte GetRLP(hash common.Hash) []byte
// GetMetadata returns the transaction type and transaction size with the
// given transaction hash.
GetMetadata(hash common.Hash) *txpool.TxMetadata
// Add should add the given transactions to the pool. // Add should add the given transactions to the pool.
Add(txs []*types.Transaction, sync bool) []error Add(txs []*types.Transaction, sync bool) []error
@ -403,7 +407,7 @@ func (h *handler) unregisterPeer(id string) {
// Abort if the peer does not exist // Abort if the peer does not exist
peer := h.peers.peer(id) peer := h.peers.peer(id)
if peer == nil { if peer == nil {
logger.Error("Ethereum peer removal failed", "err", errPeerNotRegistered) logger.Warn("Ethereum peer removal failed", "err", errPeerNotRegistered)
return return
} }
// Remove the `eth` peer if it exists // Remove the `eth` peer if it exists

View file

@ -93,6 +93,22 @@ func (p *testTxPool) GetRLP(hash common.Hash) []byte {
return nil return nil
} }
// GetMetadata returns the transaction type and transaction size with the given
// hash.
func (p *testTxPool) GetMetadata(hash common.Hash) *txpool.TxMetadata {
p.lock.Lock()
defer p.lock.Unlock()
tx := p.pool[hash]
if tx != nil {
return &txpool.TxMetadata{
Type: tx.Type(),
Size: tx.Size(),
}
}
return nil
}
// Add appends a batch of transactions to the pool, and notifies any // Add appends a batch of transactions to the pool, and notifies any
// listeners if the addition channel is non nil // listeners if the addition channel is non nil
func (p *testTxPool) Add(txs []*types.Transaction, sync bool) []error { func (p *testTxPool) Add(txs []*types.Transaction, sync bool) []error {

View file

@ -116,10 +116,10 @@ func (p *Peer) announceTransactions() {
size common.StorageSize size common.StorageSize
) )
for count = 0; count < len(queue) && size < maxTxPacketSize; count++ { for count = 0; count < len(queue) && size < maxTxPacketSize; count++ {
if tx := p.txpool.Get(queue[count]); tx != nil { if meta := p.txpool.GetMetadata(queue[count]); meta != nil {
pending = append(pending, queue[count]) pending = append(pending, queue[count])
pendingTypes = append(pendingTypes, tx.Type()) pendingTypes = append(pendingTypes, meta.Type)
pendingSizes = append(pendingSizes, uint32(tx.Size())) pendingSizes = append(pendingSizes, uint32(meta.Size))
size += common.HashLength size += common.HashLength
} }
} }

View file

@ -22,6 +22,7 @@ import (
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core" "github.com/ethereum/go-ethereum/core"
"github.com/ethereum/go-ethereum/core/txpool"
"github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/metrics" "github.com/ethereum/go-ethereum/metrics"
"github.com/ethereum/go-ethereum/p2p" "github.com/ethereum/go-ethereum/p2p"
@ -90,6 +91,10 @@ type TxPool interface {
// GetRLP retrieves the RLP-encoded transaction from the local txpool with // GetRLP retrieves the RLP-encoded transaction from the local txpool with
// the given hash. // the given hash.
GetRLP(hash common.Hash) []byte GetRLP(hash common.Hash) []byte
// GetMetadata returns the transaction type and transaction size with the
// given transaction hash.
GetMetadata(hash common.Hash) *txpool.TxMetadata
} }
// MakeProtocols constructs the P2P protocol definitions for `eth`. // MakeProtocols constructs the P2P protocol definitions for `eth`.

View file

@ -950,7 +950,9 @@ func (api *API) TraceCall(ctx context.Context, args ethapi.TransactionArgs, bloc
vmctx := core.NewEVMBlockContext(block.Header(), api.chainContext(ctx), nil) vmctx := core.NewEVMBlockContext(block.Header(), api.chainContext(ctx), nil)
// Apply the customization rules if required. // Apply the customization rules if required.
if config != nil { if config != nil {
config.BlockOverrides.Apply(&vmctx) if overrideErr := config.BlockOverrides.Apply(&vmctx); overrideErr != nil {
return nil, overrideErr
}
rules := api.backend.ChainConfig().Rules(vmctx.BlockNumber, vmctx.Random != nil, vmctx.Time) rules := api.backend.ChainConfig().Rules(vmctx.BlockNumber, vmctx.Random != nil, vmctx.Time)
precompiles = vm.ActivePrecompiledContracts(rules) precompiles = vm.ActivePrecompiledContracts(rules)

View file

@ -103,16 +103,10 @@ type AccessListTracer struct {
// NewAccessListTracer creates a new tracer that can generate AccessLists. // NewAccessListTracer creates a new tracer that can generate AccessLists.
// An optional AccessList can be specified to occupy slots and addresses in // An optional AccessList can be specified to occupy slots and addresses in
// the resulting accesslist. // the resulting accesslist.
func NewAccessListTracer(acl types.AccessList, from, to common.Address, precompiles []common.Address) *AccessListTracer { func NewAccessListTracer(acl types.AccessList, addressesToExclude map[common.Address]struct{}) *AccessListTracer {
excl := map[common.Address]struct{}{
from: {}, to: {},
}
for _, addr := range precompiles {
excl[addr] = struct{}{}
}
list := newAccessList() list := newAccessList()
for _, al := range acl { for _, al := range acl {
if _, ok := excl[al.Address]; !ok { if _, ok := addressesToExclude[al.Address]; !ok {
list.addAddress(al.Address) list.addAddress(al.Address)
} }
for _, slot := range al.StorageKeys { for _, slot := range al.StorageKeys {
@ -120,7 +114,7 @@ func NewAccessListTracer(acl types.AccessList, from, to common.Address, precompi
} }
} }
return &AccessListTracer{ return &AccessListTracer{
excl: excl, excl: addressesToExclude,
list: list, list: list,
} }
} }

View file

@ -17,7 +17,10 @@
// Package ethdb defines the interfaces for an Ethereum data store. // Package ethdb defines the interfaces for an Ethereum data store.
package ethdb package ethdb
import "io" import (
"errors"
"io"
)
// KeyValueReader wraps the Has and Get method of a backing data store. // KeyValueReader wraps the Has and Get method of a backing data store.
type KeyValueReader interface { type KeyValueReader interface {
@ -37,10 +40,14 @@ type KeyValueWriter interface {
Delete(key []byte) error Delete(key []byte) error
} }
var ErrTooManyKeys = errors.New("too many keys in deleted range")
// KeyValueRangeDeleter wraps the DeleteRange method of a backing data store. // KeyValueRangeDeleter wraps the DeleteRange method of a backing data store.
type KeyValueRangeDeleter interface { type KeyValueRangeDeleter interface {
// DeleteRange deletes all of the keys (and values) in the range [start,end) // DeleteRange deletes all of the keys (and values) in the range [start,end)
// (inclusive on start, exclusive on end). // (inclusive on start, exclusive on end).
// Some implementations of DeleteRange may return ErrTooManyKeys after
// partially deleting entries in the given range.
DeleteRange(start, end []byte) error DeleteRange(start, end []byte) error
} }

View file

@ -207,8 +207,6 @@ func (db *Database) Delete(key []byte) error {
return db.db.Delete(key, nil) return db.db.Delete(key, nil)
} }
var ErrTooManyKeys = errors.New("too many keys in deleted range")
// DeleteRange deletes all of the keys (and values) in the range [start,end) // DeleteRange deletes all of the keys (and values) in the range [start,end)
// (inclusive on start, exclusive on end). // (inclusive on start, exclusive on end).
// Note that this is a fallback implementation as leveldb does not natively // Note that this is a fallback implementation as leveldb does not natively
@ -228,7 +226,7 @@ func (db *Database) DeleteRange(start, end []byte) error {
if err := batch.Write(); err != nil { if err := batch.Write(); err != nil {
return err return err
} }
return ErrTooManyKeys return ethdb.ErrTooManyKeys
} }
if err := batch.Delete(it.Key()); err != nil { if err := batch.Delete(it.Key()); err != nil {
return err return err

View file

@ -549,21 +549,23 @@ func (api *BlockChainAPI) GetUncleByBlockHashAndIndex(ctx context.Context, block
} }
// GetUncleCountByBlockNumber returns number of uncles in the block for the given block number // GetUncleCountByBlockNumber returns number of uncles in the block for the given block number
func (api *BlockChainAPI) GetUncleCountByBlockNumber(ctx context.Context, blockNr rpc.BlockNumber) *hexutil.Uint { func (api *BlockChainAPI) GetUncleCountByBlockNumber(ctx context.Context, blockNr rpc.BlockNumber) (*hexutil.Uint, error) {
if block, _ := api.b.BlockByNumber(ctx, blockNr); block != nil { block, err := api.b.BlockByNumber(ctx, blockNr)
if block != nil {
n := hexutil.Uint(len(block.Uncles())) n := hexutil.Uint(len(block.Uncles()))
return &n return &n, nil
} }
return nil return nil, err
} }
// GetUncleCountByBlockHash returns number of uncles in the block for the given block hash // GetUncleCountByBlockHash returns number of uncles in the block for the given block hash
func (api *BlockChainAPI) GetUncleCountByBlockHash(ctx context.Context, blockHash common.Hash) *hexutil.Uint { func (api *BlockChainAPI) GetUncleCountByBlockHash(ctx context.Context, blockHash common.Hash) (*hexutil.Uint, error) {
if block, _ := api.b.BlockByHash(ctx, blockHash); block != nil { block, err := api.b.BlockByHash(ctx, blockHash)
if block != nil {
n := hexutil.Uint(len(block.Uncles())) n := hexutil.Uint(len(block.Uncles()))
return &n return &n, nil
} }
return nil return nil, err
} }
// GetCode returns the code stored at the given address in the state for the given block number. // GetCode returns the code stored at the given address in the state for the given block number.
@ -596,9 +598,7 @@ func (api *BlockChainAPI) GetStorageAt(ctx context.Context, address common.Addre
func (api *BlockChainAPI) GetBlockReceipts(ctx context.Context, blockNrOrHash rpc.BlockNumberOrHash) ([]map[string]interface{}, error) { func (api *BlockChainAPI) GetBlockReceipts(ctx context.Context, blockNrOrHash rpc.BlockNumberOrHash) ([]map[string]interface{}, error) {
block, err := api.b.BlockByNumberOrHash(ctx, blockNrOrHash) block, err := api.b.BlockByNumberOrHash(ctx, blockNrOrHash)
if block == nil || err != nil { if block == nil || err != nil {
// When the block doesn't exist, the RPC method should return JSON null return nil, err
// as per specification.
return nil, nil
} }
receipts, err := api.b.GetReceipts(ctx, block.Hash()) receipts, err := api.b.GetReceipts(ctx, block.Hash())
if err != nil { if err != nil {
@ -660,7 +660,9 @@ func (context *ChainContext) Config() *params.ChainConfig {
func doCall(ctx context.Context, b Backend, args TransactionArgs, state *state.StateDB, header *types.Header, overrides *override.StateOverride, blockOverrides *override.BlockOverrides, timeout time.Duration, globalGasCap uint64) (*core.ExecutionResult, error) { func doCall(ctx context.Context, b Backend, args TransactionArgs, state *state.StateDB, header *types.Header, overrides *override.StateOverride, blockOverrides *override.BlockOverrides, timeout time.Duration, globalGasCap uint64) (*core.ExecutionResult, error) {
blockCtx := core.NewEVMBlockContext(header, NewChainContext(ctx, b), nil) blockCtx := core.NewEVMBlockContext(header, NewChainContext(ctx, b), nil)
if blockOverrides != nil { if blockOverrides != nil {
blockOverrides.Apply(&blockCtx) if err := blockOverrides.Apply(&blockCtx); err != nil {
return nil, err
}
} }
rules := b.ChainConfig().Rules(blockCtx.BlockNumber, blockCtx.Random != nil, blockCtx.Time) rules := b.ChainConfig().Rules(blockCtx.BlockNumber, blockCtx.Random != nil, blockCtx.Time)
precompiles := vm.ActivePrecompiledContracts(rules) precompiles := vm.ActivePrecompiledContracts(rules)
@ -1115,12 +1117,13 @@ type accessListResult struct {
// CreateAccessList creates an EIP-2930 type AccessList for the given transaction. // CreateAccessList creates an EIP-2930 type AccessList for the given transaction.
// Reexec and BlockNrOrHash can be specified to create the accessList on top of a certain state. // Reexec and BlockNrOrHash can be specified to create the accessList on top of a certain state.
func (api *BlockChainAPI) CreateAccessList(ctx context.Context, args TransactionArgs, blockNrOrHash *rpc.BlockNumberOrHash) (*accessListResult, error) { // StateOverrides can be used to create the accessList while taking into account state changes from previous transactions.
func (api *BlockChainAPI) CreateAccessList(ctx context.Context, args TransactionArgs, blockNrOrHash *rpc.BlockNumberOrHash, stateOverrides *override.StateOverride) (*accessListResult, error) {
bNrOrHash := rpc.BlockNumberOrHashWithNumber(rpc.LatestBlockNumber) bNrOrHash := rpc.BlockNumberOrHashWithNumber(rpc.LatestBlockNumber)
if blockNrOrHash != nil { if blockNrOrHash != nil {
bNrOrHash = *blockNrOrHash bNrOrHash = *blockNrOrHash
} }
acl, gasUsed, vmerr, err := AccessList(ctx, api.b, bNrOrHash, args) acl, gasUsed, vmerr, err := AccessList(ctx, api.b, bNrOrHash, args, stateOverrides)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -1134,13 +1137,22 @@ func (api *BlockChainAPI) CreateAccessList(ctx context.Context, args Transaction
// AccessList creates an access list for the given transaction. // AccessList creates an access list for the given transaction.
// If the accesslist creation fails an error is returned. // If the accesslist creation fails an error is returned.
// If the transaction itself fails, an vmErr is returned. // If the transaction itself fails, an vmErr is returned.
func AccessList(ctx context.Context, b Backend, blockNrOrHash rpc.BlockNumberOrHash, args TransactionArgs) (acl types.AccessList, gasUsed uint64, vmErr error, err error) { func AccessList(ctx context.Context, b Backend, blockNrOrHash rpc.BlockNumberOrHash, args TransactionArgs, stateOverrides *override.StateOverride) (acl types.AccessList, gasUsed uint64, vmErr error, err error) {
// Retrieve the execution context // Retrieve the execution context
db, header, err := b.StateAndHeaderByNumberOrHash(ctx, blockNrOrHash) db, header, err := b.StateAndHeaderByNumberOrHash(ctx, blockNrOrHash)
if db == nil || err != nil { if db == nil || err != nil {
return nil, 0, nil, err return nil, 0, nil, err
} }
// Apply state overrides immediately after StateAndHeaderByNumberOrHash.
// If not applied here, there could be cases where user-specified overrides (e.g., nonce)
// may conflict with default values from the database, leading to inconsistencies.
if stateOverrides != nil {
if err := stateOverrides.Apply(db, nil); err != nil {
return nil, 0, nil, err
}
}
// Ensure any missing fields are filled, extract the recipient and input data // Ensure any missing fields are filled, extract the recipient and input data
if err = args.setFeeDefaults(ctx, b, header); err != nil { if err = args.setFeeDefaults(ctx, b, header); err != nil {
return nil, 0, nil, err return nil, 0, nil, err
@ -1164,10 +1176,33 @@ func AccessList(ctx context.Context, b Backend, blockNrOrHash rpc.BlockNumberOrH
// Retrieve the precompiles since they don't need to be added to the access list // Retrieve the precompiles since they don't need to be added to the access list
precompiles := vm.ActivePrecompiles(b.ChainConfig().Rules(header.Number, isPostMerge, header.Time)) precompiles := vm.ActivePrecompiles(b.ChainConfig().Rules(header.Number, isPostMerge, header.Time))
// addressesToExclude contains sender, receiver, precompiles and valid authorizations
addressesToExclude := map[common.Address]struct{}{args.from(): {}, to: {}}
for _, addr := range precompiles {
addressesToExclude[addr] = struct{}{}
}
// Prevent redundant operations if args contain more authorizations than EVM may handle
maxAuthorizations := uint64(*args.Gas) / params.CallNewAccountGas
if uint64(len(args.AuthorizationList)) > maxAuthorizations {
return nil, 0, nil, errors.New("insufficient gas to process all authorizations")
}
for _, auth := range args.AuthorizationList {
// Duplicating stateTransition.validateAuthorization() logic
if (!auth.ChainID.IsZero() && auth.ChainID.CmpBig(b.ChainConfig().ChainID) != 0) || auth.Nonce+1 < auth.Nonce {
continue
}
if authority, err := auth.Authority(); err == nil {
addressesToExclude[authority] = struct{}{}
}
}
// Create an initial tracer // Create an initial tracer
prevTracer := logger.NewAccessListTracer(nil, args.from(), to, precompiles) prevTracer := logger.NewAccessListTracer(nil, addressesToExclude)
if args.AccessList != nil { if args.AccessList != nil {
prevTracer = logger.NewAccessListTracer(*args.AccessList, args.from(), to, precompiles) prevTracer = logger.NewAccessListTracer(*args.AccessList, addressesToExclude)
} }
for { for {
if err := ctx.Err(); err != nil { if err := ctx.Err(); err != nil {
@ -1184,7 +1219,7 @@ func AccessList(ctx context.Context, b Backend, blockNrOrHash rpc.BlockNumberOrH
msg := args.ToMessage(header.BaseFee, true, true) msg := args.ToMessage(header.BaseFee, true, true)
// Apply the transaction with the access list tracer // Apply the transaction with the access list tracer
tracer := logger.NewAccessListTracer(accessList, args.from(), to, precompiles) tracer := logger.NewAccessListTracer(accessList, addressesToExclude)
config := vm.Config{Tracer: tracer.Hooks(), NoBaseFee: true} config := vm.Config{Tracer: tracer.Hooks(), NoBaseFee: true}
evm := b.GetEVM(ctx, statedb, header, &config, nil) evm := b.GetEVM(ctx, statedb, header, &config, nil)
@ -1223,37 +1258,41 @@ func NewTransactionAPI(b Backend, nonceLock *AddrLocker) *TransactionAPI {
} }
// GetBlockTransactionCountByNumber returns the number of transactions in the block with the given block number. // GetBlockTransactionCountByNumber returns the number of transactions in the block with the given block number.
func (api *TransactionAPI) GetBlockTransactionCountByNumber(ctx context.Context, blockNr rpc.BlockNumber) *hexutil.Uint { func (api *TransactionAPI) GetBlockTransactionCountByNumber(ctx context.Context, blockNr rpc.BlockNumber) (*hexutil.Uint, error) {
if block, _ := api.b.BlockByNumber(ctx, blockNr); block != nil { block, err := api.b.BlockByNumber(ctx, blockNr)
if block != nil {
n := hexutil.Uint(len(block.Transactions())) n := hexutil.Uint(len(block.Transactions()))
return &n return &n, nil
} }
return nil return nil, err
} }
// GetBlockTransactionCountByHash returns the number of transactions in the block with the given hash. // GetBlockTransactionCountByHash returns the number of transactions in the block with the given hash.
func (api *TransactionAPI) GetBlockTransactionCountByHash(ctx context.Context, blockHash common.Hash) *hexutil.Uint { func (api *TransactionAPI) GetBlockTransactionCountByHash(ctx context.Context, blockHash common.Hash) (*hexutil.Uint, error) {
if block, _ := api.b.BlockByHash(ctx, blockHash); block != nil { block, err := api.b.BlockByHash(ctx, blockHash)
if block != nil {
n := hexutil.Uint(len(block.Transactions())) n := hexutil.Uint(len(block.Transactions()))
return &n return &n, nil
} }
return nil return nil, err
} }
// GetTransactionByBlockNumberAndIndex returns the transaction for the given block number and index. // GetTransactionByBlockNumberAndIndex returns the transaction for the given block number and index.
func (api *TransactionAPI) GetTransactionByBlockNumberAndIndex(ctx context.Context, blockNr rpc.BlockNumber, index hexutil.Uint) *RPCTransaction { func (api *TransactionAPI) GetTransactionByBlockNumberAndIndex(ctx context.Context, blockNr rpc.BlockNumber, index hexutil.Uint) (*RPCTransaction, error) {
if block, _ := api.b.BlockByNumber(ctx, blockNr); block != nil { block, err := api.b.BlockByNumber(ctx, blockNr)
return newRPCTransactionFromBlockIndex(block, uint64(index), api.b.ChainConfig()) if block != nil {
return newRPCTransactionFromBlockIndex(block, uint64(index), api.b.ChainConfig()), nil
} }
return nil return nil, err
} }
// GetTransactionByBlockHashAndIndex returns the transaction for the given block hash and index. // GetTransactionByBlockHashAndIndex returns the transaction for the given block hash and index.
func (api *TransactionAPI) GetTransactionByBlockHashAndIndex(ctx context.Context, blockHash common.Hash, index hexutil.Uint) *RPCTransaction { func (api *TransactionAPI) GetTransactionByBlockHashAndIndex(ctx context.Context, blockHash common.Hash, index hexutil.Uint) (*RPCTransaction, error) {
if block, _ := api.b.BlockByHash(ctx, blockHash); block != nil { block, err := api.b.BlockByHash(ctx, blockHash)
return newRPCTransactionFromBlockIndex(block, uint64(index), api.b.ChainConfig()) if block != nil {
return newRPCTransactionFromBlockIndex(block, uint64(index), api.b.ChainConfig()), nil
} }
return nil return nil, err
} }
// GetRawTransactionByBlockNumberAndIndex returns the bytes of the transaction for the given block number and index. // GetRawTransactionByBlockNumberAndIndex returns the bytes of the transaction for the given block number and index.

View file

@ -520,8 +520,12 @@ func (b testBackend) BlockByNumber(ctx context.Context, number rpc.BlockNumber)
if number == rpc.PendingBlockNumber { if number == rpc.PendingBlockNumber {
return b.pending, nil return b.pending, nil
} }
if number == rpc.EarliestBlockNumber {
number = 0
}
return b.chain.GetBlockByNumber(uint64(number)), nil return b.chain.GetBlockByNumber(uint64(number)), nil
} }
func (b testBackend) BlockByHash(ctx context.Context, hash common.Hash) (*types.Block, error) { func (b testBackend) BlockByHash(ctx context.Context, hash common.Hash) (*types.Block, error) {
return b.chain.GetBlockByHash(hash), nil return b.chain.GetBlockByHash(hash), nil
} }
@ -618,6 +622,9 @@ func (b testBackend) SubscribeLogsEvent(ch chan<- []*types.Log) event.Subscripti
func (b testBackend) NewMatcherBackend() filtermaps.MatcherBackend { func (b testBackend) NewMatcherBackend() filtermaps.MatcherBackend {
panic("implement me") panic("implement me")
} }
func (b testBackend) HistoryPruningCutoff() uint64 { return b.chain.HistoryPruningCutoff() }
func TestEstimateGas(t *testing.T) { func TestEstimateGas(t *testing.T) {
t.Parallel() t.Parallel()
// Initialize test accounts // Initialize test accounts
@ -1134,6 +1141,24 @@ func TestCall(t *testing.T) {
}, },
want: "0x0000000000000000000000000000000000000000000000000000000000000000", want: "0x0000000000000000000000000000000000000000000000000000000000000000",
}, },
{
name: "unsupported block override beaconRoot",
blockNumber: rpc.LatestBlockNumber,
call: TransactionArgs{},
blockOverrides: override.BlockOverrides{
BeaconRoot: &common.Hash{0, 1, 2},
},
expectErr: errors.New(`block override "beaconRoot" is not supported for this RPC method`),
},
{
name: "unsupported block override withdrawals",
blockNumber: rpc.LatestBlockNumber,
call: TransactionArgs{},
blockOverrides: override.BlockOverrides{
Withdrawals: &types.Withdrawals{},
},
expectErr: errors.New(`block override "withdrawals" is not supported for this RPC method`),
},
} }
for _, tc := range testSuite { for _, tc := range testSuite {
result, err := api.Call(context.Background(), tc.call, &rpc.BlockNumberOrHash{BlockNumber: &tc.blockNumber}, &tc.overrides, &tc.blockOverrides) result, err := api.Call(context.Background(), tc.call, &rpc.BlockNumberOrHash{BlockNumber: &tc.blockNumber}, &tc.overrides, &tc.blockOverrides)
@ -3511,3 +3536,76 @@ func testRPCResponseWithFile(t *testing.T, testid int, result interface{}, rpc s
func addressToHash(a common.Address) common.Hash { func addressToHash(a common.Address) common.Hash {
return common.BytesToHash(a.Bytes()) return common.BytesToHash(a.Bytes())
} }
func TestCreateAccessListWithStateOverrides(t *testing.T) {
// Initialize test backend
genesis := &core.Genesis{
Config: params.TestChainConfig,
Alloc: types.GenesisAlloc{
common.HexToAddress("0x71562b71999873db5b286df957af199ec94617f7"): {Balance: big.NewInt(1000000000000000000)},
},
}
backend := newTestBackend(t, 1, genesis, ethash.NewFaker(), nil)
// Create a new BlockChainAPI instance
api := NewBlockChainAPI(backend)
// Create test contract code - a simple storage contract
//
// SPDX-License-Identifier: MIT
// pragma solidity ^0.8.0;
//
// contract SimpleStorage {
// uint256 private value;
//
// function retrieve() public view returns (uint256) {
// return value;
// }
// }
var (
contractCode = hexutil.Bytes(common.Hex2Bytes("6080604052348015600f57600080fd5b506004361060285760003560e01c80632e64cec114602d575b600080fd5b60336047565b604051603e91906067565b60405180910390f35b60008054905090565b6000819050919050565b6061816050565b82525050565b6000602082019050607a6000830184605a565b9291505056"))
// Create state overrides with more complete state
contractAddr = common.HexToAddress("0x1234567890123456789012345678901234567890")
nonce = hexutil.Uint64(1)
overrides = &override.StateOverride{
contractAddr: override.OverrideAccount{
Code: &contractCode,
Balance: (*hexutil.Big)(big.NewInt(1000000000000000000)),
Nonce: &nonce,
State: map[common.Hash]common.Hash{
common.Hash{}: common.HexToHash("0x000000000000000000000000000000000000000000000000000000000000002a"),
},
},
}
)
// Create transaction arguments with gas and value
var (
from = common.HexToAddress("0x71562b71999873db5b286df957af199ec94617f7")
data = hexutil.Bytes(common.Hex2Bytes("2e64cec1")) // retrieve()
gas = hexutil.Uint64(100000)
args = TransactionArgs{
From: &from,
To: &contractAddr,
Data: &data,
Gas: &gas,
Value: new(hexutil.Big),
}
)
// Call CreateAccessList
result, err := api.CreateAccessList(context.Background(), args, nil, overrides)
if err != nil {
t.Fatalf("Failed to create access list: %v", err)
}
if err != nil || result == nil {
t.Fatalf("Failed to create access list: %v", err)
}
require.NotNil(t, result.Accesslist)
// Verify access list contains the contract address and storage slot
expected := &types.AccessList{{
Address: contractAddr,
StorageKeys: []common.Hash{{}},
}}
require.Equal(t, expected, result.Accesslist)
}

View file

@ -85,6 +85,7 @@ type Backend interface {
ChainConfig() *params.ChainConfig ChainConfig() *params.ChainConfig
Engine() consensus.Engine Engine() consensus.Engine
HistoryPruningCutoff() uint64
// This is copied from filters.Backend // This is copied from filters.Backend
// eth/filters needs to be initialized from this backend type, so methods needed by // eth/filters needs to be initialized from this backend type, so methods needed by

View file

@ -17,6 +17,7 @@
package override package override
import ( import (
"errors"
"fmt" "fmt"
"math/big" "math/big"
@ -128,12 +129,20 @@ type BlockOverrides struct {
PrevRandao *common.Hash PrevRandao *common.Hash
BaseFeePerGas *hexutil.Big BaseFeePerGas *hexutil.Big
BlobBaseFee *hexutil.Big BlobBaseFee *hexutil.Big
BeaconRoot *common.Hash
Withdrawals *types.Withdrawals
} }
// Apply overrides the given header fields into the given block context. // Apply overrides the given header fields into the given block context.
func (o *BlockOverrides) Apply(blockCtx *vm.BlockContext) { func (o *BlockOverrides) Apply(blockCtx *vm.BlockContext) error {
if o == nil { if o == nil {
return return nil
}
if o.BeaconRoot != nil {
return errors.New(`block override "beaconRoot" is not supported for this RPC method`)
}
if o.Withdrawals != nil {
return errors.New(`block override "withdrawals" is not supported for this RPC method`)
} }
if o.Number != nil { if o.Number != nil {
blockCtx.BlockNumber = o.Number.ToInt() blockCtx.BlockNumber = o.Number.ToInt()
@ -159,6 +168,7 @@ func (o *BlockOverrides) Apply(blockCtx *vm.BlockContext) {
if o.BlobBaseFee != nil { if o.BlobBaseFee != nil {
blockCtx.BlobBaseFee = o.BlobBaseFee.ToInt() blockCtx.BlobBaseFee = o.BlobBaseFee.ToInt()
} }
return nil
} }
// MakeHeader returns a new header object with the overridden // MakeHeader returns a new header object with the overridden

View file

@ -36,7 +36,6 @@ import (
"github.com/ethereum/go-ethereum/internal/ethapi/override" "github.com/ethereum/go-ethereum/internal/ethapi/override"
"github.com/ethereum/go-ethereum/params" "github.com/ethereum/go-ethereum/params"
"github.com/ethereum/go-ethereum/rpc" "github.com/ethereum/go-ethereum/rpc"
"github.com/ethereum/go-ethereum/trie"
) )
const ( const (
@ -95,6 +94,47 @@ type simOpts struct {
ReturnFullTransactions bool ReturnFullTransactions bool
} }
// simChainHeadReader implements ChainHeaderReader which is needed as input for FinalizeAndAssemble.
type simChainHeadReader struct {
context.Context
Backend
}
func (m *simChainHeadReader) Config() *params.ChainConfig {
return m.Backend.ChainConfig()
}
func (m *simChainHeadReader) CurrentHeader() *types.Header {
return m.Backend.CurrentHeader()
}
func (m *simChainHeadReader) GetHeader(hash common.Hash, number uint64) *types.Header {
header, err := m.Backend.HeaderByNumber(m.Context, rpc.BlockNumber(number))
if err != nil || header == nil {
return nil
}
if header.Hash() != hash {
return nil
}
return header
}
func (m *simChainHeadReader) GetHeaderByNumber(number uint64) *types.Header {
header, err := m.Backend.HeaderByNumber(m.Context, rpc.BlockNumber(number))
if err != nil {
return nil
}
return header
}
func (m *simChainHeadReader) GetHeaderByHash(hash common.Hash) *types.Header {
header, err := m.Backend.HeaderByHash(m.Context, hash)
if err != nil {
return nil
}
return header
}
// simulator is a stateful object that simulates a series of blocks. // simulator is a stateful object that simulates a series of blocks.
// it is not safe for concurrent use. // it is not safe for concurrent use.
type simulator struct { type simulator struct {
@ -209,6 +249,9 @@ func (sim *simulator) processBlock(ctx context.Context, block *simBlock, header,
if sim.chainConfig.IsPrague(header.Number, header.Time) || sim.chainConfig.IsVerkle(header.Number, header.Time) { if sim.chainConfig.IsPrague(header.Number, header.Time) || sim.chainConfig.IsVerkle(header.Number, header.Time) {
core.ProcessParentBlockHash(header.ParentHash, evm) core.ProcessParentBlockHash(header.ParentHash, evm)
} }
if header.ParentBeaconRoot != nil {
core.ProcessBeaconBlockRoot(*header.ParentBeaconRoot, evm)
}
var allLogs []*types.Log var allLogs []*types.Log
for i, call := range block.Calls { for i, call := range block.Calls {
if err := ctx.Err(); err != nil { if err := ctx.Err(); err != nil {
@ -258,6 +301,10 @@ func (sim *simulator) processBlock(ctx context.Context, block *simBlock, header,
} }
callResults[i] = callRes callResults[i] = callRes
} }
header.GasUsed = gasUsed
if sim.chainConfig.IsCancun(header.Number, header.Time) {
header.BlobGasUsed = &blobGasUsed
}
var requests [][]byte var requests [][]byte
// Process EIP-7685 requests // Process EIP-7685 requests
if sim.chainConfig.IsPrague(header.Number, header.Time) { if sim.chainConfig.IsPrague(header.Number, header.Time) {
@ -271,20 +318,16 @@ func (sim *simulator) processBlock(ctx context.Context, block *simBlock, header,
// EIP-7251 // EIP-7251
core.ProcessConsolidationQueue(&requests, evm) core.ProcessConsolidationQueue(&requests, evm)
} }
header.Root = sim.state.IntermediateRoot(true)
header.GasUsed = gasUsed
if sim.chainConfig.IsCancun(header.Number, header.Time) {
header.BlobGasUsed = &blobGasUsed
}
var withdrawals types.Withdrawals
if sim.chainConfig.IsShanghai(header.Number, header.Time) {
withdrawals = make([]*types.Withdrawal, 0)
}
if requests != nil { if requests != nil {
reqHash := types.CalcRequestsHash(requests) reqHash := types.CalcRequestsHash(requests)
header.RequestsHash = &reqHash header.RequestsHash = &reqHash
} }
b := types.NewBlock(header, &types.Body{Transactions: txes, Withdrawals: withdrawals}, receipts, trie.NewStackTrie(nil)) blockBody := &types.Body{Transactions: txes, Withdrawals: *block.BlockOverrides.Withdrawals}
chainHeadReader := &simChainHeadReader{ctx, sim.b}
b, err := sim.b.Engine().FinalizeAndAssemble(chainHeadReader, header, sim.state, blockBody, receipts)
if err != nil {
return nil, nil, err
}
repairLogs(callResults, b.Hash()) repairLogs(callResults, b.Hash())
return b, callResults, nil return b, callResults, nil
} }
@ -346,6 +389,9 @@ func (sim *simulator) sanitizeChain(blocks []simBlock) ([]simBlock, error) {
n := new(big.Int).Add(prevNumber, big.NewInt(1)) n := new(big.Int).Add(prevNumber, big.NewInt(1))
block.BlockOverrides.Number = (*hexutil.Big)(n) block.BlockOverrides.Number = (*hexutil.Big)(n)
} }
if block.BlockOverrides.Withdrawals == nil {
block.BlockOverrides.Withdrawals = &types.Withdrawals{}
}
diff := new(big.Int).Sub(block.BlockOverrides.Number.ToInt(), prevNumber) diff := new(big.Int).Sub(block.BlockOverrides.Number.ToInt(), prevNumber)
if diff.Cmp(common.Big0) <= 0 { if diff.Cmp(common.Big0) <= 0 {
return nil, &invalidBlockNumberError{fmt.Sprintf("block numbers must be in order: %d <= %d", block.BlockOverrides.Number.ToInt().Uint64(), prevNumber)} return nil, &invalidBlockNumberError{fmt.Sprintf("block numbers must be in order: %d <= %d", block.BlockOverrides.Number.ToInt().Uint64(), prevNumber)}
@ -360,7 +406,13 @@ func (sim *simulator) sanitizeChain(blocks []simBlock) ([]simBlock, error) {
for i := uint64(0); i < gap.Uint64(); i++ { for i := uint64(0); i < gap.Uint64(); i++ {
n := new(big.Int).Add(prevNumber, big.NewInt(int64(i+1))) n := new(big.Int).Add(prevNumber, big.NewInt(int64(i+1)))
t := prevTimestamp + timestampIncrement t := prevTimestamp + timestampIncrement
b := simBlock{BlockOverrides: &override.BlockOverrides{Number: (*hexutil.Big)(n), Time: (*hexutil.Uint64)(&t)}} b := simBlock{
BlockOverrides: &override.BlockOverrides{
Number: (*hexutil.Big)(n),
Time: (*hexutil.Uint64)(&t),
Withdrawals: &types.Withdrawals{},
},
}
prevTimestamp = t prevTimestamp = t
res = append(res, b) res = append(res, b)
} }
@ -405,6 +457,9 @@ func (sim *simulator) makeHeaders(blocks []simBlock) ([]*types.Header, error) {
var parentBeaconRoot *common.Hash var parentBeaconRoot *common.Hash
if sim.chainConfig.IsCancun(overrides.Number.ToInt(), (uint64)(*overrides.Time)) { if sim.chainConfig.IsCancun(overrides.Number.ToInt(), (uint64)(*overrides.Time)) {
parentBeaconRoot = &common.Hash{} parentBeaconRoot = &common.Hash{}
if overrides.BeaconRoot != nil {
parentBeaconRoot = overrides.BeaconRoot
}
} }
header = overrides.MakeHeader(&types.Header{ header = overrides.MakeHeader(&types.Header{
UncleHash: types.EmptyUncleHash, UncleHash: types.EmptyUncleHash,

View file

@ -402,3 +402,5 @@ func (b *backendMock) SubscribeRemovedLogsEvent(ch chan<- core.RemovedLogsEvent)
func (b *backendMock) Engine() consensus.Engine { return nil } func (b *backendMock) Engine() consensus.Engine { return nil }
func (b *backendMock) NewMatcherBackend() filtermaps.MatcherBackend { return nil } func (b *backendMock) NewMatcherBackend() filtermaps.MatcherBackend { return nil }
func (b *backendMock) HistoryPruningCutoff() uint64 { return 0 }

View file

@ -15,7 +15,7 @@
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>. // along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
// This file contains the code snippets from the developer's guide embedded into // This file contains the code snippets from the developer's guide embedded into
// Go tests. This ensures that any code published in out guides will not break // Go tests. This ensures that any code published in our guides will not break
// accidentally via some code update. If some API changes nonetheless that needs // accidentally via some code update. If some API changes nonetheless that needs
// modifying this file, please port any modification over into the developer's // modifying this file, please port any modification over into the developer's
// guide wiki pages too! // guide wiki pages too!

View file

@ -39,7 +39,7 @@ const talkHandlerLaunchTimeout = 400 * time.Millisecond
// Note that talk handlers are expected to come up with a response very quickly, within at // Note that talk handlers are expected to come up with a response very quickly, within at
// most 200ms or so. If the handler takes longer than that, the remote end may time out // most 200ms or so. If the handler takes longer than that, the remote end may time out
// and wont receive the response. // and wont receive the response.
type TalkRequestHandler func(enode.ID, *net.UDPAddr, []byte) []byte type TalkRequestHandler func(*enode.Node, *net.UDPAddr, []byte) []byte
type talkSystem struct { type talkSystem struct {
transport *UDPv5 transport *UDPv5
@ -72,13 +72,19 @@ func (t *talkSystem) register(protocol string, handler TalkRequestHandler) {
// handleRequest handles a talk request. // handleRequest handles a talk request.
func (t *talkSystem) handleRequest(id enode.ID, addr netip.AddrPort, req *v5wire.TalkRequest) { func (t *talkSystem) handleRequest(id enode.ID, addr netip.AddrPort, req *v5wire.TalkRequest) {
n := t.transport.codec.SessionNode(id, addr.String())
if n == nil {
// The node must be contained in the session here, since we wouldn't have
// received the request otherwise.
panic("missing node in session")
}
t.mutex.Lock() t.mutex.Lock()
handler, ok := t.handlers[req.Protocol] handler, ok := t.handlers[req.Protocol]
t.mutex.Unlock() t.mutex.Unlock()
if !ok { if !ok {
resp := &v5wire.TalkResponse{ReqID: req.ReqID} resp := &v5wire.TalkResponse{ReqID: req.ReqID}
t.transport.sendResponse(id, addr, resp) t.transport.sendResponse(n.ID(), addr, resp)
return return
} }
@ -90,9 +96,9 @@ func (t *talkSystem) handleRequest(id enode.ID, addr netip.AddrPort, req *v5wire
go func() { go func() {
defer func() { t.slots <- struct{}{} }() defer func() { t.slots <- struct{}{} }()
udpAddr := &net.UDPAddr{IP: addr.Addr().AsSlice(), Port: int(addr.Port())} udpAddr := &net.UDPAddr{IP: addr.Addr().AsSlice(), Port: int(addr.Port())}
respMessage := handler(id, udpAddr, req.Message) respMessage := handler(n, udpAddr, req.Message)
resp := &v5wire.TalkResponse{ReqID: req.ReqID, Message: respMessage} resp := &v5wire.TalkResponse{ReqID: req.ReqID, Message: respMessage}
t.transport.sendFromAnotherThread(id, addr, resp) t.transport.sendFromAnotherThread(n.ID(), addr, resp)
}() }()
case <-timeout.C: case <-timeout.C:
// Couldn't get it in time, drop the request. // Couldn't get it in time, drop the request.

View file

@ -64,6 +64,9 @@ type codecV5 interface {
// CurrentChallenge returns the most recent WHOAREYOU challenge that was encoded to given node. // CurrentChallenge returns the most recent WHOAREYOU challenge that was encoded to given node.
// This will return a non-nil value if there is an active handshake attempt with the node, and nil otherwise. // This will return a non-nil value if there is an active handshake attempt with the node, and nil otherwise.
CurrentChallenge(id enode.ID, addr string) *v5wire.Whoareyou CurrentChallenge(id enode.ID, addr string) *v5wire.Whoareyou
// SessionNode returns a node that has completed the handshake.
SessionNode(id enode.ID, addr string) *enode.Node
} }
// UDPv5 is the implementation of protocol version 5. // UDPv5 is the implementation of protocol version 5.

View file

@ -181,29 +181,35 @@ func TestUDPv5_handshakeRepeatChallenge(t *testing.T) {
nonce1 := v5wire.Nonce{1} nonce1 := v5wire.Nonce{1}
nonce2 := v5wire.Nonce{2} nonce2 := v5wire.Nonce{2}
nonce3 := v5wire.Nonce{3} nonce3 := v5wire.Nonce{3}
check := func(p *v5wire.Whoareyou, wantNonce v5wire.Nonce) { var firstAuthTag *v5wire.Nonce
check := func(p *v5wire.Whoareyou, authTag, wantNonce v5wire.Nonce) {
t.Helper() t.Helper()
if p.Nonce != wantNonce { if p.Nonce != wantNonce {
t.Error("wrong nonce in WHOAREYOU:", p.Nonce, wantNonce) t.Error("wrong nonce in WHOAREYOU:", p.Nonce, "want:", wantNonce)
}
if firstAuthTag == nil {
firstAuthTag = &authTag
} else if authTag != *firstAuthTag {
t.Error("wrong auth tag in WHOAREYOU header:", authTag, "want:", *firstAuthTag)
} }
} }
// Unknown packet from unknown node. // Unknown packet from unknown node.
test.packetIn(&v5wire.Unknown{Nonce: nonce1}) test.packetIn(&v5wire.Unknown{Nonce: nonce1})
test.waitPacketOut(func(p *v5wire.Whoareyou, addr netip.AddrPort, _ v5wire.Nonce) { test.waitPacketOut(func(p *v5wire.Whoareyou, addr netip.AddrPort, authTag v5wire.Nonce) {
check(p, nonce1) check(p, authTag, nonce1)
}) })
// Second unknown packet. Here we expect the response to reference the // Second unknown packet. Here we expect the response to reference the
// first unknown packet. // first unknown packet.
test.packetIn(&v5wire.Unknown{Nonce: nonce2}) test.packetIn(&v5wire.Unknown{Nonce: nonce2})
test.waitPacketOut(func(p *v5wire.Whoareyou, addr netip.AddrPort, _ v5wire.Nonce) { test.waitPacketOut(func(p *v5wire.Whoareyou, addr netip.AddrPort, authTag v5wire.Nonce) {
check(p, nonce1) check(p, authTag, nonce1)
}) })
// Third unknown packet. This should still return the first nonce. // Third unknown packet. This should still return the first nonce.
test.packetIn(&v5wire.Unknown{Nonce: nonce3}) test.packetIn(&v5wire.Unknown{Nonce: nonce3})
test.waitPacketOut(func(p *v5wire.Whoareyou, addr netip.AddrPort, _ v5wire.Nonce) { test.waitPacketOut(func(p *v5wire.Whoareyou, addr netip.AddrPort, authTag v5wire.Nonce) {
check(p, nonce1) check(p, authTag, nonce1)
}) })
} }
@ -486,7 +492,7 @@ func TestUDPv5_talkHandling(t *testing.T) {
defer test.close() defer test.close()
var recvMessage []byte var recvMessage []byte
test.udp.RegisterTalkHandler("test", func(id enode.ID, addr *net.UDPAddr, message []byte) []byte { test.udp.RegisterTalkHandler("test", func(n *enode.Node, addr *net.UDPAddr, message []byte) []byte {
recvMessage = message recvMessage = message
return []byte("test response") return []byte("test response")
}) })
@ -766,20 +772,30 @@ type testCodecFrame struct {
} }
func (c *testCodec) Encode(toID enode.ID, addr string, p v5wire.Packet, _ *v5wire.Whoareyou) ([]byte, v5wire.Nonce, error) { func (c *testCodec) Encode(toID enode.ID, addr string, p v5wire.Packet, _ *v5wire.Whoareyou) ([]byte, v5wire.Nonce, error) {
// To match the behavior of v5wire.Codec, we return the cached encoding of
// WHOAREYOU challenges.
if wp, ok := p.(*v5wire.Whoareyou); ok && len(wp.Encoded) > 0 {
return wp.Encoded, wp.Nonce, nil
}
c.ctr++ c.ctr++
var authTag v5wire.Nonce var authTag v5wire.Nonce
binary.BigEndian.PutUint64(authTag[:], c.ctr) binary.BigEndian.PutUint64(authTag[:], c.ctr)
penc, _ := rlp.EncodeToBytes(p)
frame, err := rlp.EncodeToBytes(testCodecFrame{c.id, authTag, p.Kind(), penc})
if err != nil {
return frame, authTag, err
}
// Store recently sent challenges.
if w, ok := p.(*v5wire.Whoareyou); ok { if w, ok := p.(*v5wire.Whoareyou); ok {
// Store recently sent Whoareyou challenges. w.Nonce = authTag
w.Encoded = frame
if c.sentChallenges == nil { if c.sentChallenges == nil {
c.sentChallenges = make(map[enode.ID]*v5wire.Whoareyou) c.sentChallenges = make(map[enode.ID]*v5wire.Whoareyou)
} }
c.sentChallenges[toID] = w c.sentChallenges[toID] = w
} }
penc, _ := rlp.EncodeToBytes(p)
frame, err := rlp.EncodeToBytes(testCodecFrame{c.id, authTag, p.Kind(), penc})
return frame, authTag, err return frame, authTag, err
} }
@ -795,6 +811,10 @@ func (c *testCodec) Decode(input []byte, addr string) (enode.ID, *enode.Node, v5
return frame.NodeID, nil, p, nil return frame.NodeID, nil, p, nil
} }
func (c *testCodec) SessionNode(id enode.ID, addr string) *enode.Node {
return c.test.nodesByID[id].Node()
}
func (c *testCodec) decodeFrame(input []byte) (frame testCodecFrame, p v5wire.Packet, err error) { func (c *testCodec) decodeFrame(input []byte) (frame testCodecFrame, p v5wire.Packet, err error) {
if err = rlp.DecodeBytes(input, &frame); err != nil { if err = rlp.DecodeBytes(input, &frame); err != nil {
return frame, nil, fmt.Errorf("invalid frame: %v", err) return frame, nil, fmt.Errorf("invalid frame: %v", err)

View file

@ -189,6 +189,11 @@ func (c *Codec) Encode(id enode.ID, addr string, packet Packet, challenge *Whoar
) )
switch { switch {
case packet.Kind() == WhoareyouPacket: case packet.Kind() == WhoareyouPacket:
// just send the WHOAREYOU packet raw again, rather than the re-encoded challenge data
w := packet.(*Whoareyou)
if len(w.Encoded) > 0 {
return w.Encoded, w.Nonce, nil
}
head, err = c.encodeWhoareyou(id, packet.(*Whoareyou)) head, err = c.encodeWhoareyou(id, packet.(*Whoareyou))
case challenge != nil: case challenge != nil:
// We have an unanswered challenge, send handshake. // We have an unanswered challenge, send handshake.
@ -218,15 +223,22 @@ func (c *Codec) Encode(id enode.ID, addr string, packet Packet, challenge *Whoar
// Store sent WHOAREYOU challenges. // Store sent WHOAREYOU challenges.
if challenge, ok := packet.(*Whoareyou); ok { if challenge, ok := packet.(*Whoareyou); ok {
challenge.ChallengeData = bytesCopy(&c.buf) challenge.ChallengeData = bytesCopy(&c.buf)
enc, err := c.EncodeRaw(id, head, msgData)
if err != nil {
return nil, Nonce{}, err
}
challenge.Encoded = bytes.Clone(enc)
c.sc.storeSentHandshake(id, addr, challenge) c.sc.storeSentHandshake(id, addr, challenge)
} else if msgData == nil { return enc, head.Nonce, err
}
if msgData == nil {
headerData := c.buf.Bytes() headerData := c.buf.Bytes()
msgData, err = c.encryptMessage(session, packet, &head, headerData) msgData, err = c.encryptMessage(session, packet, &head, headerData)
if err != nil { if err != nil {
return nil, Nonce{}, err return nil, Nonce{}, err
} }
} }
enc, err := c.EncodeRaw(id, head, msgData) enc, err := c.EncodeRaw(id, head, msgData)
return enc, head.Nonce, err return enc, head.Nonce, err
} }
@ -347,7 +359,7 @@ func (c *Codec) encodeHandshakeHeader(toID enode.ID, addr string, challenge *Who
} }
// TODO: this should happen when the first authenticated message is received // TODO: this should happen when the first authenticated message is received
c.sc.storeNewSession(toID, addr, session) c.sc.storeNewSession(toID, addr, session, challenge.Node)
// Encode the auth header. // Encode the auth header.
var ( var (
@ -522,7 +534,7 @@ func (c *Codec) decodeHandshakeMessage(fromAddr string, head *Header, headerData
} }
// Handshake OK, drop the challenge and store the new session keys. // Handshake OK, drop the challenge and store the new session keys.
c.sc.storeNewSession(auth.h.SrcID, fromAddr, session) c.sc.storeNewSession(auth.h.SrcID, fromAddr, session, node)
c.sc.deleteHandshake(auth.h.SrcID, fromAddr) c.sc.deleteHandshake(auth.h.SrcID, fromAddr)
return node, msg, nil return node, msg, nil
} }
@ -644,6 +656,10 @@ func (c *Codec) decryptMessage(input, nonce, headerData, readKey []byte) (Packet
return DecodeMessage(msgdata[0], msgdata[1:]) return DecodeMessage(msgdata[0], msgdata[1:])
} }
func (c *Codec) SessionNode(id enode.ID, addr string) *enode.Node {
return c.sc.readNode(id, addr)
}
// checkValid performs some basic validity checks on the header. // checkValid performs some basic validity checks on the header.
// The packetLen here is the length remaining after the static header. // The packetLen here is the length remaining after the static header.
func (h *StaticHeader) checkValid(packetLen int, protocolID [6]byte) error { func (h *StaticHeader) checkValid(packetLen int, protocolID [6]byte) error {

View file

@ -166,7 +166,7 @@ func TestHandshake_rekey(t *testing.T) {
readKey: []byte("BBBBBBBBBBBBBBBB"), readKey: []byte("BBBBBBBBBBBBBBBB"),
writeKey: []byte("AAAAAAAAAAAAAAAA"), writeKey: []byte("AAAAAAAAAAAAAAAA"),
} }
net.nodeA.c.sc.storeNewSession(net.nodeB.id(), net.nodeB.addr(), session) net.nodeA.c.sc.storeNewSession(net.nodeB.id(), net.nodeB.addr(), session, net.nodeB.n())
// A -> B FINDNODE (encrypted with zero keys) // A -> B FINDNODE (encrypted with zero keys)
findnode, authTag := net.nodeA.encode(t, net.nodeB, &Findnode{}) findnode, authTag := net.nodeA.encode(t, net.nodeB, &Findnode{})
@ -209,8 +209,8 @@ func TestHandshake_rekey2(t *testing.T) {
readKey: []byte("CCCCCCCCCCCCCCCC"), readKey: []byte("CCCCCCCCCCCCCCCC"),
writeKey: []byte("DDDDDDDDDDDDDDDD"), writeKey: []byte("DDDDDDDDDDDDDDDD"),
} }
net.nodeA.c.sc.storeNewSession(net.nodeB.id(), net.nodeB.addr(), initKeysA) net.nodeA.c.sc.storeNewSession(net.nodeB.id(), net.nodeB.addr(), initKeysA, net.nodeB.n())
net.nodeB.c.sc.storeNewSession(net.nodeA.id(), net.nodeA.addr(), initKeysB) net.nodeB.c.sc.storeNewSession(net.nodeA.id(), net.nodeA.addr(), initKeysB, net.nodeA.n())
// A -> B FINDNODE encrypted with initKeysA // A -> B FINDNODE encrypted with initKeysA
findnode, authTag := net.nodeA.encode(t, net.nodeB, &Findnode{Distances: []uint{3}}) findnode, authTag := net.nodeA.encode(t, net.nodeB, &Findnode{Distances: []uint{3}})
@ -362,8 +362,8 @@ func TestTestVectorsV5(t *testing.T) {
ENRSeq: 2, ENRSeq: 2,
}, },
prep: func(net *handshakeTest) { prep: func(net *handshakeTest) {
net.nodeA.c.sc.storeNewSession(idB, addr, session) net.nodeA.c.sc.storeNewSession(idB, addr, session, net.nodeB.n())
net.nodeB.c.sc.storeNewSession(idA, addr, session.keysFlipped()) net.nodeB.c.sc.storeNewSession(idA, addr, session.keysFlipped(), net.nodeA.n())
}, },
}, },
{ {
@ -499,8 +499,8 @@ func BenchmarkV5_DecodePing(b *testing.B) {
readKey: []byte{233, 203, 93, 195, 86, 47, 177, 186, 227, 43, 2, 141, 244, 230, 120, 17}, readKey: []byte{233, 203, 93, 195, 86, 47, 177, 186, 227, 43, 2, 141, 244, 230, 120, 17},
writeKey: []byte{79, 145, 252, 171, 167, 216, 252, 161, 208, 190, 176, 106, 214, 39, 178, 134}, writeKey: []byte{79, 145, 252, 171, 167, 216, 252, 161, 208, 190, 176, 106, 214, 39, 178, 134},
} }
net.nodeA.c.sc.storeNewSession(net.nodeB.id(), net.nodeB.addr(), session) net.nodeA.c.sc.storeNewSession(net.nodeB.id(), net.nodeB.addr(), session, net.nodeB.n())
net.nodeB.c.sc.storeNewSession(net.nodeA.id(), net.nodeA.addr(), session.keysFlipped()) net.nodeB.c.sc.storeNewSession(net.nodeA.id(), net.nodeA.addr(), session.keysFlipped(), net.nodeA.n())
addrB := net.nodeA.addr() addrB := net.nodeA.addr()
ping := &Ping{ReqID: []byte("reqid"), ENRSeq: 5} ping := &Ping{ReqID: []byte("reqid"), ENRSeq: 5}
enc, _, err := net.nodeA.c.Encode(net.nodeB.id(), addrB, ping, nil) enc, _, err := net.nodeA.c.Encode(net.nodeB.id(), addrB, ping, nil)

View file

@ -73,6 +73,9 @@ type (
Node *enode.Node Node *enode.Node
sent mclock.AbsTime // for handshake GC. sent mclock.AbsTime // for handshake GC.
// Encoded is packet raw data for sending out, but should not be include in the RLP encoding.
Encoded []byte `rlp:"-"`
} }
// PING is sent during liveness checks. // PING is sent during liveness checks.

View file

@ -54,11 +54,12 @@ type session struct {
writeKey []byte writeKey []byte
readKey []byte readKey []byte
nonceCounter uint32 nonceCounter uint32
node *enode.Node
} }
// keysFlipped returns a copy of s with the read and write keys flipped. // keysFlipped returns a copy of s with the read and write keys flipped.
func (s *session) keysFlipped() *session { func (s *session) keysFlipped() *session {
return &session{s.readKey, s.writeKey, s.nonceCounter} return &session{s.readKey, s.writeKey, s.nonceCounter, s.node}
} }
func NewSessionCache(maxItems int, clock mclock.Clock) *SessionCache { func NewSessionCache(maxItems int, clock mclock.Clock) *SessionCache {
@ -103,8 +104,19 @@ func (sc *SessionCache) readKey(id enode.ID, addr string) []byte {
return nil return nil
} }
func (sc *SessionCache) readNode(id enode.ID, addr string) *enode.Node {
if s := sc.session(id, addr); s != nil {
return s.node
}
return nil
}
// storeNewSession stores new encryption keys in the cache. // storeNewSession stores new encryption keys in the cache.
func (sc *SessionCache) storeNewSession(id enode.ID, addr string, s *session) { func (sc *SessionCache) storeNewSession(id enode.ID, addr string, s *session, n *enode.Node) {
if n == nil {
panic("nil node in storeNewSession")
}
s.node = n
sc.sessions.Add(sessionID{id, addr}, s) sc.sessions.Add(sessionID{id, addr}, s)
} }

View file

@ -140,7 +140,7 @@ type ExtIP net.IP
func (n ExtIP) ExternalIP() (net.IP, error) { return net.IP(n), nil } func (n ExtIP) ExternalIP() (net.IP, error) { return net.IP(n), nil }
func (n ExtIP) String() string { return fmt.Sprintf("ExtIP(%v)", net.IP(n)) } func (n ExtIP) String() string { return fmt.Sprintf("ExtIP(%v)", net.IP(n)) }
func (n ExtIP) MarshalText() ([]byte, error) { return []byte(fmt.Sprintf("extip:%v", net.IP(n))), nil } func (n ExtIP) MarshalText() ([]byte, error) { return fmt.Appendf(nil, "extip:%v", net.IP(n)), nil }
// These do nothing. // These do nothing.

View file

@ -71,7 +71,7 @@ func (n *pmp) DeleteMapping(protocol string, extport, intport int) (err error) {
} }
func (n *pmp) MarshalText() ([]byte, error) { func (n *pmp) MarshalText() ([]byte, error) {
return []byte(fmt.Sprintf("natpmp:%v", n.gw)), nil return fmt.Appendf(nil, "natpmp:%v", n.gw), nil
} }
func discoverPMP() Interface { func discoverPMP() Interface {

View file

@ -82,7 +82,7 @@ func (n *upnp) ExternalIP() (addr net.IP, err error) {
func (n *upnp) AddMapping(protocol string, extport, intport int, desc string, lifetime time.Duration) (uint16, error) { func (n *upnp) AddMapping(protocol string, extport, intport int, desc string, lifetime time.Duration) (uint16, error) {
ip, err := n.internalAddress() ip, err := n.internalAddress()
if err != nil { if err != nil {
return 0, nil // TODO: Shouldn't we return the error? return 0, err
} }
protocol = strings.ToUpper(protocol) protocol = strings.ToUpper(protocol)
lifetimeS := uint32(lifetime / time.Second) lifetimeS := uint32(lifetime / time.Second)
@ -94,14 +94,15 @@ func (n *upnp) AddMapping(protocol string, extport, intport int, desc string, li
if err == nil { if err == nil {
return uint16(extport), nil return uint16(extport), nil
} }
// Try addAnyPortMapping if mapping specified port didn't work.
return uint16(extport), n.withRateLimit(func() error { err = n.withRateLimit(func() error {
p, err := n.addAnyPortMapping(protocol, extport, intport, ip, desc, lifetimeS) p, err := n.addAnyPortMapping(protocol, extport, intport, ip, desc, lifetimeS)
if err == nil { if err == nil {
extport = int(p) extport = int(p)
} }
return err return err
}) })
return uint16(extport), err
} }
func (n *upnp) addAnyPortMapping(protocol string, extport, intport int, ip net.IP, desc string, lifetimeS uint32) (uint16, error) { func (n *upnp) addAnyPortMapping(protocol string, extport, intport int, ip net.IP, desc string, lifetimeS uint32) (uint16, error) {

View file

@ -63,11 +63,11 @@ type jsonWriter interface {
type BlockNumber int64 type BlockNumber int64
const ( const (
EarliestBlockNumber = BlockNumber(-5)
SafeBlockNumber = BlockNumber(-4) SafeBlockNumber = BlockNumber(-4)
FinalizedBlockNumber = BlockNumber(-3) FinalizedBlockNumber = BlockNumber(-3)
LatestBlockNumber = BlockNumber(-2) LatestBlockNumber = BlockNumber(-2)
PendingBlockNumber = BlockNumber(-1) PendingBlockNumber = BlockNumber(-1)
EarliestBlockNumber = BlockNumber(0)
) )
// UnmarshalJSON parses the given JSON fragment into a BlockNumber. It supports: // UnmarshalJSON parses the given JSON fragment into a BlockNumber. It supports:

View file

@ -282,7 +282,7 @@ func TestTypedDataArrayValidate(t *testing.T) {
messageHash, tErr := td.HashStruct(td.PrimaryType, td.Message) messageHash, tErr := td.HashStruct(td.PrimaryType, td.Message)
assert.NoError(t, tErr, "failed to hash message: %v", tErr) assert.NoError(t, tErr, "failed to hash message: %v", tErr)
digest := crypto.Keccak256Hash([]byte(fmt.Sprintf("%s%s%s", "\x19\x01", string(domainSeparator), string(messageHash)))) digest := crypto.Keccak256Hash(fmt.Appendf(nil, "%s%s%s", "\x19\x01", string(domainSeparator), string(messageHash)))
assert.Equal(t, tc.Digest, digest.String(), "digest doesn't not match") assert.Equal(t, tc.Digest, digest.String(), "digest doesn't not match")
assert.NoError(t, td.validate(), "validation failed", tErr) assert.NoError(t, td.validate(), "validation failed", tErr)

View file

@ -369,7 +369,7 @@ func sign(typedData apitypes.TypedData) ([]byte, []byte, error) {
if err != nil { if err != nil {
return nil, nil, err return nil, nil, err
} }
rawData := []byte(fmt.Sprintf("\x19\x01%s%s", string(domainSeparator), string(typedDataHash))) rawData := fmt.Appendf(nil, "\x19\x01%s%s", string(domainSeparator), string(typedDataHash))
sighash := crypto.Keccak256(rawData) sighash := crypto.Keccak256(rawData)
return typedDataHash, sighash, nil return typedDataHash, sighash, nil
} }

@ -1 +1 @@
Subproject commit faf33b471465d3c6cdc3d04fbd690895f78d33f2 Subproject commit 81862e4848585a438d64f911a19b3825f0f4cd95

View file

@ -57,32 +57,26 @@ func (c *committer) commit(path []byte, n node, parallel bool) node {
// Commit children, then parent, and remove the dirty flag. // Commit children, then parent, and remove the dirty flag.
switch cn := n.(type) { switch cn := n.(type) {
case *shortNode: case *shortNode:
// Commit child
collapsed := cn.copy()
// If the child is fullNode, recursively commit, // If the child is fullNode, recursively commit,
// otherwise it can only be hashNode or valueNode. // otherwise it can only be hashNode or valueNode.
if _, ok := cn.Val.(*fullNode); ok { if _, ok := cn.Val.(*fullNode); ok {
collapsed.Val = c.commit(append(path, cn.Key...), cn.Val, false) cn.Val = c.commit(append(path, cn.Key...), cn.Val, false)
} }
// The key needs to be copied, since we're adding it to the // The key needs to be copied, since we're adding it to the
// modified nodeset. // modified nodeset.
collapsed.Key = hexToCompact(cn.Key) cn.Key = hexToCompact(cn.Key)
hashedNode := c.store(path, collapsed) hashedNode := c.store(path, cn)
if hn, ok := hashedNode.(hashNode); ok { if hn, ok := hashedNode.(hashNode); ok {
return hn return hn
} }
return collapsed return cn
case *fullNode: case *fullNode:
hashedKids := c.commitChildren(path, cn, parallel) c.commitChildren(path, cn, parallel)
collapsed := cn.copy() hashedNode := c.store(path, cn)
collapsed.Children = hashedKids
hashedNode := c.store(path, collapsed)
if hn, ok := hashedNode.(hashNode); ok { if hn, ok := hashedNode.(hashNode); ok {
return hn return hn
} }
return collapsed return cn
case hashNode: case hashNode:
return cn return cn
default: default:
@ -92,11 +86,10 @@ func (c *committer) commit(path []byte, n node, parallel bool) node {
} }
// commitChildren commits the children of the given fullnode // commitChildren commits the children of the given fullnode
func (c *committer) commitChildren(path []byte, n *fullNode, parallel bool) [17]node { func (c *committer) commitChildren(path []byte, n *fullNode, parallel bool) {
var ( var (
wg sync.WaitGroup wg sync.WaitGroup
nodesMu sync.Mutex nodesMu sync.Mutex
children [17]node
) )
for i := 0; i < 16; i++ { for i := 0; i < 16; i++ {
child := n.Children[i] child := n.Children[i]
@ -106,22 +99,21 @@ func (c *committer) commitChildren(path []byte, n *fullNode, parallel bool) [17]
// If it's the hashed child, save the hash value directly. // If it's the hashed child, save the hash value directly.
// Note: it's impossible that the child in range [0, 15] // Note: it's impossible that the child in range [0, 15]
// is a valueNode. // is a valueNode.
if hn, ok := child.(hashNode); ok { if _, ok := child.(hashNode); ok {
children[i] = hn
continue continue
} }
// Commit the child recursively and store the "hashed" value. // Commit the child recursively and store the "hashed" value.
// Note the returned node can be some embedded nodes, so it's // Note the returned node can be some embedded nodes, so it's
// possible the type is not hashNode. // possible the type is not hashNode.
if !parallel { if !parallel {
children[i] = c.commit(append(path, byte(i)), child, false) n.Children[i] = c.commit(append(path, byte(i)), child, false)
} else { } else {
wg.Add(1) wg.Add(1)
go func(index int) { go func(index int) {
p := append(path, byte(index)) p := append(path, byte(index))
childSet := trienode.NewNodeSet(c.nodes.Owner) childSet := trienode.NewNodeSet(c.nodes.Owner)
childCommitter := newCommitter(childSet, c.tracer, c.collectLeaf) childCommitter := newCommitter(childSet, c.tracer, c.collectLeaf)
children[index] = childCommitter.commit(p, child, false) n.Children[index] = childCommitter.commit(p, child, false)
nodesMu.Lock() nodesMu.Lock()
c.nodes.MergeSet(childSet) c.nodes.MergeSet(childSet)
nodesMu.Unlock() nodesMu.Unlock()
@ -132,11 +124,6 @@ func (c *committer) commitChildren(path []byte, n *fullNode, parallel bool) [17]
if parallel { if parallel {
wg.Wait() wg.Wait()
} }
// For the 17th child, it's possible the type is valuenode.
if n.Children[16] != nil {
children[16] = n.Children[16]
}
return children
} }
// store hashes the node n and adds it to the modified nodeset. If leaf collection // store hashes the node n and adds it to the modified nodeset. If leaf collection

View file

@ -53,62 +53,56 @@ func returnHasherToPool(h *hasher) {
hasherPool.Put(h) hasherPool.Put(h)
} }
// hash collapses a node down into a hash node, also returning a copy of the // hash collapses a node down into a hash node.
// original node initialized with the computed hash to replace the original one. func (h *hasher) hash(n node, force bool) node {
func (h *hasher) hash(n node, force bool) (hashed node, cached node) {
// Return the cached hash if it's available // Return the cached hash if it's available
if hash, _ := n.cache(); hash != nil { if hash, _ := n.cache(); hash != nil {
return hash, n return hash
} }
// Trie not processed yet, walk the children // Trie not processed yet, walk the children
switch n := n.(type) { switch n := n.(type) {
case *shortNode: case *shortNode:
collapsed, cached := h.hashShortNodeChildren(n) collapsed := h.hashShortNodeChildren(n)
hashed := h.shortnodeToHash(collapsed, force) hashed := h.shortnodeToHash(collapsed, force)
// We need to retain the possibly _not_ hashed node, in case it was too
// small to be hashed
if hn, ok := hashed.(hashNode); ok { if hn, ok := hashed.(hashNode); ok {
cached.flags.hash = hn n.flags.hash = hn
} else { } else {
cached.flags.hash = nil n.flags.hash = nil
} }
return hashed, cached return hashed
case *fullNode: case *fullNode:
collapsed, cached := h.hashFullNodeChildren(n) collapsed := h.hashFullNodeChildren(n)
hashed = h.fullnodeToHash(collapsed, force) hashed := h.fullnodeToHash(collapsed, force)
if hn, ok := hashed.(hashNode); ok { if hn, ok := hashed.(hashNode); ok {
cached.flags.hash = hn n.flags.hash = hn
} else { } else {
cached.flags.hash = nil n.flags.hash = nil
} }
return hashed, cached return hashed
default: default:
// Value and hash nodes don't have children, so they're left as were // Value and hash nodes don't have children, so they're left as were
return n, n return n
} }
} }
// hashShortNodeChildren collapses the short node. The returned collapsed node // hashShortNodeChildren returns a copy of the supplied shortNode, with its child
// holds a live reference to the Key, and must not be modified. // being replaced by either the hash or an embedded node if the child is small.
func (h *hasher) hashShortNodeChildren(n *shortNode) (collapsed, cached *shortNode) { func (h *hasher) hashShortNodeChildren(n *shortNode) *shortNode {
// Hash the short node's child, caching the newly hashed subtree var collapsed shortNode
collapsed, cached = n.copy(), n.copy()
// Previously, we did copy this one. We don't seem to need to actually
// do that, since we don't overwrite/reuse keys
// cached.Key = common.CopyBytes(n.Key)
collapsed.Key = hexToCompact(n.Key) collapsed.Key = hexToCompact(n.Key)
// Unless the child is a valuenode or hashnode, hash it
switch n.Val.(type) { switch n.Val.(type) {
case *fullNode, *shortNode: case *fullNode, *shortNode:
collapsed.Val, cached.Val = h.hash(n.Val, false) collapsed.Val = h.hash(n.Val, false)
default:
collapsed.Val = n.Val
} }
return collapsed, cached return &collapsed
} }
func (h *hasher) hashFullNodeChildren(n *fullNode) (collapsed *fullNode, cached *fullNode) { // hashFullNodeChildren returns a copy of the supplied fullNode, with its child
// Hash the full node's children, caching the newly hashed subtrees // being replaced by either the hash or an embedded node if the child is small.
cached = n.copy() func (h *hasher) hashFullNodeChildren(n *fullNode) *fullNode {
collapsed = n.copy() var children [17]node
if h.parallel { if h.parallel {
var wg sync.WaitGroup var wg sync.WaitGroup
wg.Add(16) wg.Add(16)
@ -116,9 +110,9 @@ func (h *hasher) hashFullNodeChildren(n *fullNode) (collapsed *fullNode, cached
go func(i int) { go func(i int) {
hasher := newHasher(false) hasher := newHasher(false)
if child := n.Children[i]; child != nil { if child := n.Children[i]; child != nil {
collapsed.Children[i], cached.Children[i] = hasher.hash(child, false) children[i] = hasher.hash(child, false)
} else { } else {
collapsed.Children[i] = nilValueNode children[i] = nilValueNode
} }
returnHasherToPool(hasher) returnHasherToPool(hasher)
wg.Done() wg.Done()
@ -128,19 +122,21 @@ func (h *hasher) hashFullNodeChildren(n *fullNode) (collapsed *fullNode, cached
} else { } else {
for i := 0; i < 16; i++ { for i := 0; i < 16; i++ {
if child := n.Children[i]; child != nil { if child := n.Children[i]; child != nil {
collapsed.Children[i], cached.Children[i] = h.hash(child, false) children[i] = h.hash(child, false)
} else { } else {
collapsed.Children[i] = nilValueNode children[i] = nilValueNode
} }
} }
} }
return collapsed, cached if n.Children[16] != nil {
children[16] = n.Children[16]
}
return &fullNode{flags: nodeFlag{}, Children: children}
} }
// shortnodeToHash creates a hashNode from a shortNode. The supplied shortnode // shortNodeToHash computes the hash of the given shortNode. The shortNode must
// should have hex-type Key, which will be converted (without modification) // first be collapsed, with its key converted to compact form. If the RLP-encoded
// into compact form for RLP encoding. // node data is smaller than 32 bytes, the node itself is returned.
// If the rlp data is smaller than 32 bytes, `nil` is returned.
func (h *hasher) shortnodeToHash(n *shortNode, force bool) node { func (h *hasher) shortnodeToHash(n *shortNode, force bool) node {
n.encode(h.encbuf) n.encode(h.encbuf)
enc := h.encodedBytes() enc := h.encodedBytes()
@ -151,8 +147,8 @@ func (h *hasher) shortnodeToHash(n *shortNode, force bool) node {
return h.hashData(enc) return h.hashData(enc)
} }
// fullnodeToHash is used to create a hashNode from a fullNode, (which // fullnodeToHash computes the hash of the given fullNode. If the RLP-encoded
// may contain nil values) // node data is smaller than 32 bytes, the node itself is returned.
func (h *hasher) fullnodeToHash(n *fullNode, force bool) node { func (h *hasher) fullnodeToHash(n *fullNode, force bool) node {
n.encode(h.encbuf) n.encode(h.encbuf)
enc := h.encodedBytes() enc := h.encodedBytes()
@ -203,10 +199,10 @@ func (h *hasher) hashDataTo(dst, data []byte) {
func (h *hasher) proofHash(original node) (collapsed, hashed node) { func (h *hasher) proofHash(original node) (collapsed, hashed node) {
switch n := original.(type) { switch n := original.(type) {
case *shortNode: case *shortNode:
sn, _ := h.hashShortNodeChildren(n) sn := h.hashShortNodeChildren(n)
return sn, h.shortnodeToHash(sn, false) return sn, h.shortnodeToHash(sn, false)
case *fullNode: case *fullNode:
fn, _ := h.hashFullNodeChildren(n) fn := h.hashFullNodeChildren(n)
return fn, h.fullnodeToHash(fn, false) return fn, h.fullnodeToHash(fn, false)
default: default:
// Value and hash nodes don't have children, so they're left as were // Value and hash nodes don't have children, so they're left as were

View file

@ -79,15 +79,19 @@ func (n *fullNode) EncodeRLP(w io.Writer) error {
return eb.Flush() return eb.Flush()
} }
func (n *fullNode) copy() *fullNode { copy := *n; return &copy }
func (n *shortNode) copy() *shortNode { copy := *n; return &copy }
// nodeFlag contains caching-related metadata about a node. // nodeFlag contains caching-related metadata about a node.
type nodeFlag struct { type nodeFlag struct {
hash hashNode // cached hash of the node (may be nil) hash hashNode // cached hash of the node (may be nil)
dirty bool // whether the node has changes that must be written to the database dirty bool // whether the node has changes that must be written to the database
} }
func (n nodeFlag) copy() nodeFlag {
return nodeFlag{
hash: common.CopyBytes(n.hash),
dirty: n.dirty,
}
}
func (n *fullNode) cache() (hashNode, bool) { return n.flags.hash, n.flags.dirty } func (n *fullNode) cache() (hashNode, bool) { return n.flags.hash, n.flags.dirty }
func (n *shortNode) cache() (hashNode, bool) { return n.flags.hash, n.flags.dirty } func (n *shortNode) cache() (hashNode, bool) { return n.flags.hash, n.flags.dirty }
func (n hashNode) cache() (hashNode, bool) { return nil, true } func (n hashNode) cache() (hashNode, bool) { return nil, true }
@ -228,7 +232,9 @@ func decodeRef(buf []byte) (node, []byte, error) {
err := fmt.Errorf("oversized embedded node (size is %d bytes, want size < %d)", size, hashLen) err := fmt.Errorf("oversized embedded node (size is %d bytes, want size < %d)", size, hashLen)
return nil, buf, err return nil, buf, err
} }
n, err := decodeNode(nil, buf) // The buffer content has already been copied or is safe to use;
// no additional copy is required.
n, err := decodeNodeUnsafe(nil, buf)
return n, rest, err return n, rest, err
case kind == rlp.String && len(val) == 0: case kind == rlp.String && len(val) == 0:
// empty node // empty node

View file

@ -29,11 +29,11 @@ import (
"github.com/ethereum/go-ethereum/triedb/database" "github.com/ethereum/go-ethereum/triedb/database"
) )
// Trie is a Merkle Patricia Trie. Use New to create a trie that sits on // Trie represents a Merkle Patricia Trie. Use New to create a trie that operates
// top of a database. Whenever trie performs a commit operation, the generated // on top of a node database. During a commit operation, the trie collects all
// nodes will be gathered and returned in a set. Once the trie is committed, // modified nodes into a set for return. After committing, the trie becomes
// it's not usable anymore. Callers have to re-create the trie with new root // unusable, and callers must recreate it with the new root based on the updated
// based on the updated trie database. // trie database.
// //
// Trie is not safe for concurrent use. // Trie is not safe for concurrent use.
type Trie struct { type Trie struct {
@ -67,13 +67,13 @@ func (t *Trie) newFlag() nodeFlag {
// Copy returns a copy of Trie. // Copy returns a copy of Trie.
func (t *Trie) Copy() *Trie { func (t *Trie) Copy() *Trie {
return &Trie{ return &Trie{
root: t.root, root: copyNode(t.root),
owner: t.owner, owner: t.owner,
committed: t.committed, committed: t.committed,
unhashed: t.unhashed,
uncommitted: t.uncommitted,
reader: t.reader, reader: t.reader,
tracer: t.tracer.copy(), tracer: t.tracer.copy(),
uncommitted: t.uncommitted,
unhashed: t.unhashed,
} }
} }
@ -169,14 +169,12 @@ func (t *Trie) get(origNode node, key []byte, pos int) (value []byte, newnode no
} }
value, newnode, didResolve, err = t.get(n.Val, key, pos+len(n.Key)) value, newnode, didResolve, err = t.get(n.Val, key, pos+len(n.Key))
if err == nil && didResolve { if err == nil && didResolve {
n = n.copy()
n.Val = newnode n.Val = newnode
} }
return value, n, didResolve, err return value, n, didResolve, err
case *fullNode: case *fullNode:
value, newnode, didResolve, err = t.get(n.Children[key[pos]], key, pos+1) value, newnode, didResolve, err = t.get(n.Children[key[pos]], key, pos+1)
if err == nil && didResolve { if err == nil && didResolve {
n = n.copy()
n.Children[key[pos]] = newnode n.Children[key[pos]] = newnode
} }
return value, n, didResolve, err return value, n, didResolve, err
@ -257,7 +255,6 @@ func (t *Trie) getNode(origNode node, path []byte, pos int) (item []byte, newnod
} }
item, newnode, resolved, err = t.getNode(n.Val, path, pos+len(n.Key)) item, newnode, resolved, err = t.getNode(n.Val, path, pos+len(n.Key))
if err == nil && resolved > 0 { if err == nil && resolved > 0 {
n = n.copy()
n.Val = newnode n.Val = newnode
} }
return item, n, resolved, err return item, n, resolved, err
@ -265,7 +262,6 @@ func (t *Trie) getNode(origNode node, path []byte, pos int) (item []byte, newnod
case *fullNode: case *fullNode:
item, newnode, resolved, err = t.getNode(n.Children[path[pos]], path, pos+1) item, newnode, resolved, err = t.getNode(n.Children[path[pos]], path, pos+1)
if err == nil && resolved > 0 { if err == nil && resolved > 0 {
n = n.copy()
n.Children[path[pos]] = newnode n.Children[path[pos]] = newnode
} }
return item, n, resolved, err return item, n, resolved, err
@ -375,7 +371,6 @@ func (t *Trie) insert(n node, prefix, key []byte, value node) (bool, node, error
if !dirty || err != nil { if !dirty || err != nil {
return false, n, err return false, n, err
} }
n = n.copy()
n.flags = t.newFlag() n.flags = t.newFlag()
n.Children[key[0]] = nn n.Children[key[0]] = nn
return true, n, nil return true, n, nil
@ -483,7 +478,6 @@ func (t *Trie) delete(n node, prefix, key []byte) (bool, node, error) {
if !dirty || err != nil { if !dirty || err != nil {
return false, n, err return false, n, err
} }
n = n.copy()
n.flags = t.newFlag() n.flags = t.newFlag()
n.Children[key[0]] = nn n.Children[key[0]] = nn
@ -576,6 +570,36 @@ func concat(s1 []byte, s2 ...byte) []byte {
return r return r
} }
// copyNode deep-copies the supplied node along with its children recursively.
func copyNode(n node) node {
switch n := (n).(type) {
case nil:
return nil
case valueNode:
return valueNode(common.CopyBytes(n))
case *shortNode:
return &shortNode{
flags: n.flags.copy(),
Key: common.CopyBytes(n.Key),
Val: copyNode(n.Val),
}
case *fullNode:
var children [17]node
for i, cn := range n.Children {
children[i] = copyNode(cn)
}
return &fullNode{
flags: n.flags.copy(),
Children: children,
}
case hashNode:
return n
default:
panic(fmt.Sprintf("%T: unknown node type", n))
}
}
func (t *Trie) resolve(n node, prefix []byte) (node, error) { func (t *Trie) resolve(n node, prefix []byte) (node, error) {
if n, ok := n.(hashNode); ok { if n, ok := n.(hashNode); ok {
return t.resolveAndTrack(n, prefix) return t.resolveAndTrack(n, prefix)
@ -593,15 +617,16 @@ func (t *Trie) resolveAndTrack(n hashNode, prefix []byte) (node, error) {
return nil, err return nil, err
} }
t.tracer.onRead(prefix, blob) t.tracer.onRead(prefix, blob)
return mustDecodeNode(n, blob), nil
// The returned node blob won't be changed afterward. No need to
// deep-copy the slice.
return decodeNodeUnsafe(n, blob)
} }
// Hash returns the root hash of the trie. It does not write to the // Hash returns the root hash of the trie. It does not write to the
// database and can be used even if the trie doesn't have one. // database and can be used even if the trie doesn't have one.
func (t *Trie) Hash() common.Hash { func (t *Trie) Hash() common.Hash {
hash, cached := t.hashRoot() return common.BytesToHash(t.hashRoot().(hashNode))
t.root = cached
return common.BytesToHash(hash.(hashNode))
} }
// Commit collects all dirty nodes in the trie and replaces them with the // Commit collects all dirty nodes in the trie and replaces them with the
@ -652,9 +677,9 @@ func (t *Trie) Commit(collectLeaf bool) (common.Hash, *trienode.NodeSet) {
} }
// hashRoot calculates the root hash of the given trie // hashRoot calculates the root hash of the given trie
func (t *Trie) hashRoot() (node, node) { func (t *Trie) hashRoot() node {
if t.root == nil { if t.root == nil {
return hashNode(types.EmptyRootHash.Bytes()), nil return hashNode(types.EmptyRootHash.Bytes())
} }
// If the number of changes is below 100, we let one thread handle it // If the number of changes is below 100, we let one thread handle it
h := newHasher(t.unhashed >= 100) h := newHasher(t.unhashed >= 100)
@ -662,8 +687,7 @@ func (t *Trie) hashRoot() (node, node) {
returnHasherToPool(h) returnHasherToPool(h)
t.unhashed = 0 t.unhashed = 0
}() }()
hashed, cached := h.hash(t.root, true) return h.hash(t.root, true)
return hashed, cached
} }
// Witness returns a set containing all trie nodes that have been accessed. // Witness returns a set containing all trie nodes that have been accessed.

View file

@ -1330,3 +1330,171 @@ func printSet(set *trienode.NodeSet) string {
} }
return out.String() return out.String()
} }
func TestTrieCopy(t *testing.T) {
testTrieCopy(t, []kv{
{k: []byte("do"), v: []byte("verb")},
{k: []byte("ether"), v: []byte("wookiedoo")},
{k: []byte("horse"), v: []byte("stallion")},
{k: []byte("shaman"), v: []byte("horse")},
{k: []byte("doge"), v: []byte("coin")},
{k: []byte("dog"), v: []byte("puppy")},
})
var entries []kv
for i := 0; i < 256; i++ {
entries = append(entries, kv{k: testrand.Bytes(32), v: testrand.Bytes(32)})
}
testTrieCopy(t, entries)
}
func testTrieCopy(t *testing.T, entries []kv) {
tr := NewEmpty(nil)
for _, entry := range entries {
tr.Update(entry.k, entry.v)
}
trCpy := tr.Copy()
if tr.Hash() != trCpy.Hash() {
t.Errorf("Hash mismatch: old %v, copy %v", tr.Hash(), trCpy.Hash())
}
// Check iterator
it, _ := tr.NodeIterator(nil)
itCpy, _ := trCpy.NodeIterator(nil)
for it.Next(false) {
hasNext := itCpy.Next(false)
if !hasNext {
t.Fatal("Iterator is not matched")
}
if !bytes.Equal(it.Path(), itCpy.Path()) {
t.Fatal("Iterator is not matched")
}
if it.Leaf() != itCpy.Leaf() {
t.Fatal("Iterator is not matched")
}
if it.Leaf() && !bytes.Equal(it.LeafBlob(), itCpy.LeafBlob()) {
t.Fatal("Iterator is not matched")
}
}
// Check commit
root, nodes := tr.Commit(false)
rootCpy, nodesCpy := trCpy.Commit(false)
if root != rootCpy {
t.Fatal("root mismatch")
}
if len(nodes.Nodes) != len(nodesCpy.Nodes) {
t.Fatal("commit node mismatch")
}
for p, n := range nodes.Nodes {
nn, exists := nodesCpy.Nodes[p]
if !exists {
t.Fatalf("node not exists: %v", p)
}
if !reflect.DeepEqual(n, nn) {
t.Fatalf("node mismatch: %v", p)
}
}
}
func TestTrieCopyOldTrie(t *testing.T) {
testTrieCopyOldTrie(t, []kv{
{k: []byte("do"), v: []byte("verb")},
{k: []byte("ether"), v: []byte("wookiedoo")},
{k: []byte("horse"), v: []byte("stallion")},
{k: []byte("shaman"), v: []byte("horse")},
{k: []byte("doge"), v: []byte("coin")},
{k: []byte("dog"), v: []byte("puppy")},
})
var entries []kv
for i := 0; i < 256; i++ {
entries = append(entries, kv{k: testrand.Bytes(32), v: testrand.Bytes(32)})
}
testTrieCopyOldTrie(t, entries)
}
func testTrieCopyOldTrie(t *testing.T, entries []kv) {
tr := NewEmpty(nil)
for _, entry := range entries {
tr.Update(entry.k, entry.v)
}
hash := tr.Hash()
trCpy := tr.Copy()
for _, val := range entries {
if rand.Intn(2) == 0 {
trCpy.Delete(val.k)
} else {
trCpy.Update(val.k, testrand.Bytes(32))
}
}
for i := 0; i < 10; i++ {
trCpy.Update(testrand.Bytes(32), testrand.Bytes(32))
}
trCpy.Hash()
trCpy.Commit(false)
// Traverse the original tree, the changes made on the copy one shouldn't
// affect the old one
for _, entry := range entries {
d, _ := tr.Get(entry.k)
if !bytes.Equal(d, entry.v) {
t.Errorf("Unexpected data, key: %v, want: %v, got: %v", entry.k, entry.v, d)
}
}
if tr.Hash() != hash {
t.Errorf("Hash mismatch: old %v, new %v", hash, tr.Hash())
}
}
func TestTrieCopyNewTrie(t *testing.T) {
testTrieCopyNewTrie(t, []kv{
{k: []byte("do"), v: []byte("verb")},
{k: []byte("ether"), v: []byte("wookiedoo")},
{k: []byte("horse"), v: []byte("stallion")},
{k: []byte("shaman"), v: []byte("horse")},
{k: []byte("doge"), v: []byte("coin")},
{k: []byte("dog"), v: []byte("puppy")},
})
var entries []kv
for i := 0; i < 256; i++ {
entries = append(entries, kv{k: testrand.Bytes(32), v: testrand.Bytes(32)})
}
testTrieCopyNewTrie(t, entries)
}
func testTrieCopyNewTrie(t *testing.T, entries []kv) {
tr := NewEmpty(nil)
for _, entry := range entries {
tr.Update(entry.k, entry.v)
}
trCpy := tr.Copy()
hash := trCpy.Hash()
for _, val := range entries {
if rand.Intn(2) == 0 {
tr.Delete(val.k)
} else {
tr.Update(val.k, testrand.Bytes(32))
}
}
for i := 0; i < 10; i++ {
tr.Update(testrand.Bytes(32), testrand.Bytes(32))
}
// Traverse the original tree, the changes made on the copy one shouldn't
// affect the old one
for _, entry := range entries {
d, _ := trCpy.Get(entry.k)
if !bytes.Equal(d, entry.v) {
t.Errorf("Unexpected data, key: %v, want: %v, got: %v", entry.k, entry.v, d)
}
}
if trCpy.Hash() != hash {
t.Errorf("Hash mismatch: old %v, new %v", hash, tr.Hash())
}
}

View file

@ -27,6 +27,8 @@ type NodeReader interface {
// node path and the corresponding node hash. No error will be returned // node path and the corresponding node hash. No error will be returned
// if the node is not found. // if the node is not found.
// //
// The returned node content won't be changed after the call.
//
// Don't modify the returned byte slice since it's not deep-copied and // Don't modify the returned byte slice since it's not deep-copied and
// still be referenced by database. // still be referenced by database.
Node(owner common.Hash, path []byte, hash common.Hash) ([]byte, error) Node(owner common.Hash, path []byte, hash common.Hash) ([]byte, error)

View file

@ -371,27 +371,27 @@ func TestAccountIteratorTraversalValues(t *testing.T) {
h = make(map[common.Hash][]byte) h = make(map[common.Hash][]byte)
) )
for i := byte(2); i < 0xff; i++ { for i := byte(2); i < 0xff; i++ {
a[common.Hash{i}] = []byte(fmt.Sprintf("layer-%d, key %d", 0, i)) a[common.Hash{i}] = fmt.Appendf(nil, "layer-%d, key %d", 0, i)
if i > 20 && i%2 == 0 { if i > 20 && i%2 == 0 {
b[common.Hash{i}] = []byte(fmt.Sprintf("layer-%d, key %d", 1, i)) b[common.Hash{i}] = fmt.Appendf(nil, "layer-%d, key %d", 1, i)
} }
if i%4 == 0 { if i%4 == 0 {
c[common.Hash{i}] = []byte(fmt.Sprintf("layer-%d, key %d", 2, i)) c[common.Hash{i}] = fmt.Appendf(nil, "layer-%d, key %d", 2, i)
} }
if i%7 == 0 { if i%7 == 0 {
d[common.Hash{i}] = []byte(fmt.Sprintf("layer-%d, key %d", 3, i)) d[common.Hash{i}] = fmt.Appendf(nil, "layer-%d, key %d", 3, i)
} }
if i%8 == 0 { if i%8 == 0 {
e[common.Hash{i}] = []byte(fmt.Sprintf("layer-%d, key %d", 4, i)) e[common.Hash{i}] = fmt.Appendf(nil, "layer-%d, key %d", 4, i)
} }
if i > 50 || i < 85 { if i > 50 || i < 85 {
f[common.Hash{i}] = []byte(fmt.Sprintf("layer-%d, key %d", 5, i)) f[common.Hash{i}] = fmt.Appendf(nil, "layer-%d, key %d", 5, i)
} }
if i%64 == 0 { if i%64 == 0 {
g[common.Hash{i}] = []byte(fmt.Sprintf("layer-%d, key %d", 6, i)) g[common.Hash{i}] = fmt.Appendf(nil, "layer-%d, key %d", 6, i)
} }
if i%128 == 0 { if i%128 == 0 {
h[common.Hash{i}] = []byte(fmt.Sprintf("layer-%d, key %d", 7, i)) h[common.Hash{i}] = fmt.Appendf(nil, "layer-%d, key %d", 7, i)
} }
} }
// Assemble a stack of snapshots from the account layers // Assemble a stack of snapshots from the account layers
@ -480,27 +480,27 @@ func TestStorageIteratorTraversalValues(t *testing.T) {
h = make(map[common.Hash][]byte) h = make(map[common.Hash][]byte)
) )
for i := byte(2); i < 0xff; i++ { for i := byte(2); i < 0xff; i++ {
a[common.Hash{i}] = []byte(fmt.Sprintf("layer-%d, key %d", 0, i)) a[common.Hash{i}] = fmt.Appendf(nil, "layer-%d, key %d", 0, i)
if i > 20 && i%2 == 0 { if i > 20 && i%2 == 0 {
b[common.Hash{i}] = []byte(fmt.Sprintf("layer-%d, key %d", 1, i)) b[common.Hash{i}] = fmt.Appendf(nil, "layer-%d, key %d", 1, i)
} }
if i%4 == 0 { if i%4 == 0 {
c[common.Hash{i}] = []byte(fmt.Sprintf("layer-%d, key %d", 2, i)) c[common.Hash{i}] = fmt.Appendf(nil, "layer-%d, key %d", 2, i)
} }
if i%7 == 0 { if i%7 == 0 {
d[common.Hash{i}] = []byte(fmt.Sprintf("layer-%d, key %d", 3, i)) d[common.Hash{i}] = fmt.Appendf(nil, "layer-%d, key %d", 3, i)
} }
if i%8 == 0 { if i%8 == 0 {
e[common.Hash{i}] = []byte(fmt.Sprintf("layer-%d, key %d", 4, i)) e[common.Hash{i}] = fmt.Appendf(nil, "layer-%d, key %d", 4, i)
} }
if i > 50 || i < 85 { if i > 50 || i < 85 {
f[common.Hash{i}] = []byte(fmt.Sprintf("layer-%d, key %d", 5, i)) f[common.Hash{i}] = fmt.Appendf(nil, "layer-%d, key %d", 5, i)
} }
if i%64 == 0 { if i%64 == 0 {
g[common.Hash{i}] = []byte(fmt.Sprintf("layer-%d, key %d", 6, i)) g[common.Hash{i}] = fmt.Appendf(nil, "layer-%d, key %d", 6, i)
} }
if i%128 == 0 { if i%128 == 0 {
h[common.Hash{i}] = []byte(fmt.Sprintf("layer-%d, key %d", 7, i)) h[common.Hash{i}] = fmt.Appendf(nil, "layer-%d, key %d", 7, i)
} }
} }
// Assemble a stack of snapshots from the account layers // Assemble a stack of snapshots from the account layers

View file

@ -36,8 +36,9 @@ import (
// transition, typically corresponding to a block execution. It can also represent // transition, typically corresponding to a block execution. It can also represent
// the combined trie node set from several aggregated state transitions. // the combined trie node set from several aggregated state transitions.
type nodeSet struct { type nodeSet struct {
size uint64 // aggregated size of the trie node size uint64 // aggregated size of the trie node
nodes map[common.Hash]map[string]*trienode.Node // node set, mapped by owner and path accountNodes map[string]*trienode.Node // account trie nodes, mapped by path
storageNodes map[common.Hash]map[string]*trienode.Node // storage trie nodes, mapped by owner and path
} }
// newNodeSet constructs the set with the provided dirty trie nodes. // newNodeSet constructs the set with the provided dirty trie nodes.
@ -46,7 +47,17 @@ func newNodeSet(nodes map[common.Hash]map[string]*trienode.Node) *nodeSet {
if nodes == nil { if nodes == nil {
nodes = make(map[common.Hash]map[string]*trienode.Node) nodes = make(map[common.Hash]map[string]*trienode.Node)
} }
s := &nodeSet{nodes: nodes} s := &nodeSet{
accountNodes: make(map[string]*trienode.Node),
storageNodes: make(map[common.Hash]map[string]*trienode.Node),
}
for owner, subset := range nodes {
if owner == (common.Hash{}) {
s.accountNodes = subset
} else {
s.storageNodes[owner] = subset
}
}
s.computeSize() s.computeSize()
return s return s
} }
@ -54,13 +65,12 @@ func newNodeSet(nodes map[common.Hash]map[string]*trienode.Node) *nodeSet {
// computeSize calculates the database size of the held trie nodes. // computeSize calculates the database size of the held trie nodes.
func (s *nodeSet) computeSize() { func (s *nodeSet) computeSize() {
var size uint64 var size uint64
for owner, subset := range s.nodes { for path, n := range s.accountNodes {
var prefix int size += uint64(len(n.Blob) + len(path))
if owner != (common.Hash{}) { }
prefix = common.HashLength // owner (32 bytes) for storage trie nodes for _, subset := range s.storageNodes {
}
for path, n := range subset { for path, n := range subset {
size += uint64(prefix + len(n.Blob) + len(path)) size += uint64(common.HashLength + len(n.Blob) + len(path))
} }
} }
s.size = size s.size = size
@ -79,15 +89,18 @@ func (s *nodeSet) updateSize(delta int64) {
// node retrieves the trie node with node path and its trie identifier. // node retrieves the trie node with node path and its trie identifier.
func (s *nodeSet) node(owner common.Hash, path []byte) (*trienode.Node, bool) { func (s *nodeSet) node(owner common.Hash, path []byte) (*trienode.Node, bool) {
subset, ok := s.nodes[owner] // Account trie node
if owner == (common.Hash{}) {
n, ok := s.accountNodes[string(path)]
return n, ok
}
// Storage trie node
subset, ok := s.storageNodes[owner]
if !ok { if !ok {
return nil, false return nil, false
} }
n, ok := subset[string(path)] n, ok := subset[string(path)]
if !ok { return n, ok
return nil, false
}
return n, true
} }
// merge integrates the provided dirty nodes into the set. The provided nodeset // merge integrates the provided dirty nodes into the set. The provided nodeset
@ -97,15 +110,24 @@ func (s *nodeSet) merge(set *nodeSet) {
delta int64 // size difference resulting from node merging delta int64 // size difference resulting from node merging
overwrite counter // counter of nodes being overwritten overwrite counter // counter of nodes being overwritten
) )
for owner, subset := range set.nodes {
var prefix int // Merge account nodes
if owner != (common.Hash{}) { for path, n := range set.accountNodes {
prefix = common.HashLength if orig, exist := s.accountNodes[path]; !exist {
delta += int64(len(n.Blob) + len(path))
} else {
delta += int64(len(n.Blob) - len(orig.Blob))
overwrite.add(len(orig.Blob) + len(path))
} }
current, exist := s.nodes[owner] s.accountNodes[path] = n
}
// Merge storage nodes
for owner, subset := range set.storageNodes {
current, exist := s.storageNodes[owner]
if !exist { if !exist {
for path, n := range subset { for path, n := range subset {
delta += int64(prefix + len(n.Blob) + len(path)) delta += int64(common.HashLength + len(n.Blob) + len(path))
} }
// Perform a shallow copy of the map for the subset instead of claiming it // Perform a shallow copy of the map for the subset instead of claiming it
// directly from the provided nodeset to avoid potential concurrent map // directly from the provided nodeset to avoid potential concurrent map
@ -113,19 +135,19 @@ func (s *nodeSet) merge(set *nodeSet) {
// accessible even after merging. Therefore, ownership of the nodes map // accessible even after merging. Therefore, ownership of the nodes map
// should still belong to the original layer, and any modifications to it // should still belong to the original layer, and any modifications to it
// should be prevented. // should be prevented.
s.nodes[owner] = maps.Clone(subset) s.storageNodes[owner] = maps.Clone(subset)
continue continue
} }
for path, n := range subset { for path, n := range subset {
if orig, exist := current[path]; !exist { if orig, exist := current[path]; !exist {
delta += int64(prefix + len(n.Blob) + len(path)) delta += int64(common.HashLength + len(n.Blob) + len(path))
} else { } else {
delta += int64(len(n.Blob) - len(orig.Blob)) delta += int64(len(n.Blob) - len(orig.Blob))
overwrite.add(prefix + len(orig.Blob) + len(path)) overwrite.add(common.HashLength + len(orig.Blob) + len(path))
} }
current[path] = n current[path] = n
} }
s.nodes[owner] = current s.storageNodes[owner] = current
} }
overwrite.report(gcTrieNodeMeter, gcTrieNodeBytesMeter) overwrite.report(gcTrieNodeMeter, gcTrieNodeBytesMeter)
s.updateSize(delta) s.updateSize(delta)
@ -136,34 +158,38 @@ func (s *nodeSet) merge(set *nodeSet) {
func (s *nodeSet) revertTo(db ethdb.KeyValueReader, nodes map[common.Hash]map[string]*trienode.Node) { func (s *nodeSet) revertTo(db ethdb.KeyValueReader, nodes map[common.Hash]map[string]*trienode.Node) {
var delta int64 var delta int64
for owner, subset := range nodes { for owner, subset := range nodes {
current, ok := s.nodes[owner] if owner == (common.Hash{}) {
if !ok { // Account trie nodes
panic(fmt.Sprintf("non-existent subset (%x)", owner)) for path, n := range subset {
} orig, ok := s.accountNodes[path]
for path, n := range subset { if !ok {
orig, ok := current[path] blob := rawdb.ReadAccountTrieNode(db, []byte(path))
if !ok { if bytes.Equal(blob, n.Blob) {
// There is a special case in merkle tree that one child is removed continue
// from a fullNode which only has two children, and then a new child }
// with different position is immediately inserted into the fullNode. panic(fmt.Sprintf("non-existent account node (%v) blob: %v", path, crypto.Keccak256Hash(n.Blob).Hex()))
// In this case, the clean child of the fullNode will also be marked
// as dirty because of node collapse and expansion. In case of database
// rollback, don't panic if this "clean" node occurs which is not
// present in buffer.
var blob []byte
if owner == (common.Hash{}) {
blob = rawdb.ReadAccountTrieNode(db, []byte(path))
} else {
blob = rawdb.ReadStorageTrieNode(db, owner, []byte(path))
} }
// Ignore the clean node in the case described above. s.accountNodes[path] = n
if bytes.Equal(blob, n.Blob) { delta += int64(len(n.Blob)) - int64(len(orig.Blob))
continue }
} } else {
panic(fmt.Sprintf("non-existent node (%x %v) blob: %v", owner, path, crypto.Keccak256Hash(n.Blob).Hex())) // Storage trie nodes
current, ok := s.storageNodes[owner]
if !ok {
panic(fmt.Sprintf("non-existent subset (%x)", owner))
}
for path, n := range subset {
orig, ok := current[path]
if !ok {
blob := rawdb.ReadStorageTrieNode(db, owner, []byte(path))
if bytes.Equal(blob, n.Blob) {
continue
}
panic(fmt.Sprintf("non-existent storage node (%x %v) blob: %v", owner, path, crypto.Keccak256Hash(n.Blob).Hex()))
}
current[path] = n
delta += int64(len(n.Blob)) - int64(len(orig.Blob))
} }
current[path] = n
delta += int64(len(n.Blob)) - int64(len(orig.Blob))
} }
} }
s.updateSize(delta) s.updateSize(delta)
@ -184,8 +210,21 @@ type journalNodes struct {
// encode serializes the content of trie nodes into the provided writer. // encode serializes the content of trie nodes into the provided writer.
func (s *nodeSet) encode(w io.Writer) error { func (s *nodeSet) encode(w io.Writer) error {
nodes := make([]journalNodes, 0, len(s.nodes)) nodes := make([]journalNodes, 0, len(s.storageNodes)+1)
for owner, subset := range s.nodes {
// Encode account nodes
if len(s.accountNodes) > 0 {
entry := journalNodes{Owner: common.Hash{}}
for path, node := range s.accountNodes {
entry.Nodes = append(entry.Nodes, journalNode{
Path: []byte(path),
Blob: node.Blob,
})
}
nodes = append(nodes, entry)
}
// Encode storage nodes
for owner, subset := range s.storageNodes {
entry := journalNodes{Owner: owner} entry := journalNodes{Owner: owner}
for path, node := range subset { for path, node := range subset {
entry.Nodes = append(entry.Nodes, journalNode{ entry.Nodes = append(entry.Nodes, journalNode{
@ -204,43 +243,61 @@ func (s *nodeSet) decode(r *rlp.Stream) error {
if err := r.Decode(&encoded); err != nil { if err := r.Decode(&encoded); err != nil {
return fmt.Errorf("load nodes: %v", err) return fmt.Errorf("load nodes: %v", err)
} }
nodes := make(map[common.Hash]map[string]*trienode.Node) s.accountNodes = make(map[string]*trienode.Node)
s.storageNodes = make(map[common.Hash]map[string]*trienode.Node)
for _, entry := range encoded { for _, entry := range encoded {
subset := make(map[string]*trienode.Node) if entry.Owner == (common.Hash{}) {
for _, n := range entry.Nodes { // Account nodes
if len(n.Blob) > 0 { for _, n := range entry.Nodes {
subset[string(n.Path)] = trienode.New(crypto.Keccak256Hash(n.Blob), n.Blob) if len(n.Blob) > 0 {
} else { s.accountNodes[string(n.Path)] = trienode.New(crypto.Keccak256Hash(n.Blob), n.Blob)
subset[string(n.Path)] = trienode.NewDeleted() } else {
s.accountNodes[string(n.Path)] = trienode.NewDeleted()
}
} }
} else {
// Storage nodes
subset := make(map[string]*trienode.Node)
for _, n := range entry.Nodes {
if len(n.Blob) > 0 {
subset[string(n.Path)] = trienode.New(crypto.Keccak256Hash(n.Blob), n.Blob)
} else {
subset[string(n.Path)] = trienode.NewDeleted()
}
}
s.storageNodes[entry.Owner] = subset
} }
nodes[entry.Owner] = subset
} }
s.nodes = nodes
s.computeSize() s.computeSize()
return nil return nil
} }
// write flushes nodes into the provided database batch as a whole. // write flushes nodes into the provided database batch as a whole.
func (s *nodeSet) write(batch ethdb.Batch, clean *fastcache.Cache) int { func (s *nodeSet) write(batch ethdb.Batch, clean *fastcache.Cache) int {
return writeNodes(batch, s.nodes, clean) nodes := make(map[common.Hash]map[string]*trienode.Node)
if len(s.accountNodes) > 0 {
nodes[common.Hash{}] = s.accountNodes
}
for owner, subset := range s.storageNodes {
nodes[owner] = subset
}
return writeNodes(batch, nodes, clean)
} }
// reset clears all cached trie node data. // reset clears all cached trie node data.
func (s *nodeSet) reset() { func (s *nodeSet) reset() {
s.nodes = make(map[common.Hash]map[string]*trienode.Node) s.accountNodes = make(map[string]*trienode.Node)
s.storageNodes = make(map[common.Hash]map[string]*trienode.Node)
s.size = 0 s.size = 0
} }
// dbsize returns the approximate size of db write. // dbsize returns the approximate size of db write.
func (s *nodeSet) dbsize() int { func (s *nodeSet) dbsize() int {
var m int var m int
for owner, nodes := range s.nodes { m += len(s.accountNodes) * len(rawdb.TrieNodeAccountPrefix) // database key prefix
if owner == (common.Hash{}) { for _, nodes := range s.storageNodes {
m += len(nodes) * len(rawdb.TrieNodeAccountPrefix) // database key prefix m += len(nodes) * (len(rawdb.TrieNodeStoragePrefix)) // database key prefix
} else {
m += len(nodes) * (len(rawdb.TrieNodeStoragePrefix)) // database key prefix
}
} }
return m + int(s.size) return m + int(s.size)
} }

View file

@ -19,6 +19,6 @@ package version
const ( const (
Major = 1 // Major version component of the current release Major = 1 // Major version component of the current release
Minor = 15 // Minor version component of the current release Minor = 15 // Minor version component of the current release
Patch = 6 // Patch version component of the current release Patch = 8 // Patch version component of the current release
Meta = "unstable" // Version metadata to append to the version string Meta = "unstable" // Version metadata to append to the version string
) )