mirror of
https://github.com/ethereum/go-ethereum.git
synced 2026-07-26 22:56:43 +00:00
Merge branch 'ethereum:master' into master
This commit is contained in:
commit
0427856858
88 changed files with 1970 additions and 726 deletions
13
.gitignore
vendored
13
.gitignore
vendored
|
|
@ -43,3 +43,16 @@ profile.cov
|
|||
.vscode
|
||||
|
||||
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
|
||||
23
.travis.yml
23
.travis.yml
|
|
@ -2,12 +2,6 @@ language: go
|
|||
go_import_path: github.com/ethereum/go-ethereum
|
||||
sudo: false
|
||||
jobs:
|
||||
allow_failures:
|
||||
- stage: build
|
||||
os: osx
|
||||
env:
|
||||
- azure-osx
|
||||
|
||||
include:
|
||||
# This builder create and push the Docker images for all architectures
|
||||
- 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 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
|
||||
- stage: build
|
||||
if: type = push
|
||||
|
|
|
|||
|
|
@ -541,7 +541,7 @@ var bindTests = []struct {
|
|||
struct A {
|
||||
bytes32 B;
|
||||
}
|
||||
|
||||
|
||||
function F() public view returns (A[] memory a, uint256[] memory c, bool[] memory d) {
|
||||
A[] memory a = new A[](2);
|
||||
a[0].B = bytes32(uint256(1234) << 96);
|
||||
|
|
@ -549,7 +549,7 @@ var bindTests = []struct {
|
|||
bool[] memory d;
|
||||
return (a, c, d);
|
||||
}
|
||||
|
||||
|
||||
function G() public view returns (A[] memory a) {
|
||||
A[] memory a = new A[](2);
|
||||
a[0].B = bytes32(uint256(1234) << 96);
|
||||
|
|
@ -571,10 +571,10 @@ var bindTests = []struct {
|
|||
// Generate a new random account and a funded simulator
|
||||
key, _ := crypto.GenerateKey()
|
||||
auth, _ := bind.NewKeyedTransactorWithChainID(key, big.NewInt(1337))
|
||||
|
||||
|
||||
sim := backends.NewSimulatedBackend(types.GenesisAlloc{auth.From: {Balance: big.NewInt(10000000000000000)}}, 10000000)
|
||||
defer sim.Close()
|
||||
|
||||
|
||||
// Deploy a structs method invoker contract and execute its default method
|
||||
_, _, structs, err := DeployStructs(auth, sim)
|
||||
if err != nil {
|
||||
|
|
@ -1701,13 +1701,13 @@ var bindTests = []struct {
|
|||
`NewFallbacks`,
|
||||
`
|
||||
pragma solidity >=0.6.0 <0.7.0;
|
||||
|
||||
|
||||
contract NewFallbacks {
|
||||
event Fallback(bytes data);
|
||||
fallback() external {
|
||||
emit Fallback(msg.data);
|
||||
}
|
||||
|
||||
|
||||
event Received(address addr, uint value);
|
||||
receive() external payable {
|
||||
emit Received(msg.sender, msg.value);
|
||||
|
|
@ -1719,7 +1719,7 @@ var bindTests = []struct {
|
|||
`
|
||||
"bytes"
|
||||
"math/big"
|
||||
|
||||
|
||||
"github.com/ethereum/go-ethereum/accounts/abi/bind"
|
||||
"github.com/ethereum/go-ethereum/accounts/abi/bind/backends"
|
||||
"github.com/ethereum/go-ethereum/core/types"
|
||||
|
|
@ -1728,22 +1728,22 @@ var bindTests = []struct {
|
|||
`
|
||||
key, _ := crypto.GenerateKey()
|
||||
addr := crypto.PubkeyToAddress(key.PublicKey)
|
||||
|
||||
|
||||
sim := backends.NewSimulatedBackend(types.GenesisAlloc{addr: {Balance: big.NewInt(10000000000000000)}}, 1000000)
|
||||
defer sim.Close()
|
||||
|
||||
|
||||
opts, _ := bind.NewKeyedTransactorWithChainID(key, big.NewInt(1337))
|
||||
_, _, c, err := DeployNewFallbacks(opts, sim)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to deploy contract: %v", err)
|
||||
}
|
||||
sim.Commit()
|
||||
|
||||
|
||||
// Test receive function
|
||||
opts.Value = big.NewInt(100)
|
||||
c.Receive(opts)
|
||||
sim.Commit()
|
||||
|
||||
|
||||
var gotEvent bool
|
||||
iter, _ := c.FilterReceived(nil)
|
||||
defer iter.Close()
|
||||
|
|
@ -1760,14 +1760,14 @@ var bindTests = []struct {
|
|||
if !gotEvent {
|
||||
t.Fatal("Expect to receive event emitted by receive")
|
||||
}
|
||||
|
||||
|
||||
// Test fallback function
|
||||
gotEvent = false
|
||||
opts.Value = nil
|
||||
calldata := []byte{0x01, 0x02, 0x03}
|
||||
c.Fallback(opts, calldata)
|
||||
sim.Commit()
|
||||
|
||||
|
||||
iter2, _ := c.FilterFallback(nil)
|
||||
defer iter2.Close()
|
||||
for iter2.Next() {
|
||||
|
|
@ -1806,7 +1806,9 @@ var bindTests = []struct {
|
|||
[]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"}]`},
|
||||
`
|
||||
"context"
|
||||
"math/big"
|
||||
"time"
|
||||
|
||||
"github.com/ethereum/go-ethereum/accounts/abi/bind"
|
||||
"github.com/ethereum/go-ethereum/accounts/abi/bind/backends"
|
||||
|
|
@ -1828,12 +1830,23 @@ var bindTests = []struct {
|
|||
}
|
||||
sim.Commit()
|
||||
|
||||
_, err = d.TestEvent(user)
|
||||
tx, err := d.TestEvent(user)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to call contract %v", err)
|
||||
}
|
||||
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)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to filter contract event %v", err)
|
||||
|
|
@ -1862,7 +1875,7 @@ var bindTests = []struct {
|
|||
`NewErrors`,
|
||||
`
|
||||
pragma solidity >0.8.4;
|
||||
|
||||
|
||||
contract NewErrors {
|
||||
error MyError(uint256);
|
||||
error MyError1(uint256);
|
||||
|
|
@ -1878,7 +1891,7 @@ var bindTests = []struct {
|
|||
`
|
||||
"context"
|
||||
"math/big"
|
||||
|
||||
|
||||
"github.com/ethereum/go-ethereum/accounts/abi/bind"
|
||||
"github.com/ethereum/go-ethereum/accounts/abi/bind/backends"
|
||||
"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)
|
||||
)
|
||||
defer sim.Close()
|
||||
|
||||
|
||||
_, tx, contract, err := DeployNewErrors(user, sim)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
|
|
@ -1917,12 +1930,12 @@ var bindTests = []struct {
|
|||
name: `ConstructorWithStructParam`,
|
||||
contract: `
|
||||
pragma solidity >=0.8.0 <0.9.0;
|
||||
|
||||
|
||||
contract ConstructorWithStructParam {
|
||||
struct StructType {
|
||||
uint256 field;
|
||||
}
|
||||
|
||||
|
||||
constructor(StructType memory st) {}
|
||||
}
|
||||
`,
|
||||
|
|
@ -1951,7 +1964,7 @@ var bindTests = []struct {
|
|||
t.Fatalf("DeployConstructorWithStructParam() got err %v; want nil err", err)
|
||||
}
|
||||
sim.Commit()
|
||||
|
||||
|
||||
if _, err = bind.WaitDeployed(context.Background(), sim, tx); err != nil {
|
||||
t.Logf("Deployment tx: %+v", tx)
|
||||
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)
|
||||
}
|
||||
sim.Commit()
|
||||
|
||||
|
||||
if _, err = bind.WaitDeployed(context.Background(), sim, tx); err != nil {
|
||||
t.Logf("Deployment tx: %+v", tx)
|
||||
t.Errorf("bind.WaitDeployed(nil, %T, <deployment tx>) got err %v; want nil err", sim, err)
|
||||
|
|
|
|||
|
|
@ -383,13 +383,14 @@ func (c *BoundContract) estimateGasLimit(opts *TransactOpts, contract *common.Ad
|
|||
}
|
||||
}
|
||||
msg := ethereum.CallMsg{
|
||||
From: opts.From,
|
||||
To: contract,
|
||||
GasPrice: gasPrice,
|
||||
GasTipCap: gasTipCap,
|
||||
GasFeeCap: gasFeeCap,
|
||||
Value: value,
|
||||
Data: input,
|
||||
From: opts.From,
|
||||
To: contract,
|
||||
GasPrice: gasPrice,
|
||||
GasTipCap: gasTipCap,
|
||||
GasFeeCap: gasFeeCap,
|
||||
Value: value,
|
||||
Data: input,
|
||||
AccessList: opts.AccessList,
|
||||
}
|
||||
return c.transactor.EstimateGas(ensureContext(opts.Context), msg)
|
||||
}
|
||||
|
|
|
|||
1
beacon/params/checkpoint_holesky.hex
Normal file
1
beacon/params/checkpoint_holesky.hex
Normal file
|
|
@ -0,0 +1 @@
|
|||
0xf5606a019f0f1006e9ec2070695045f4334450362a48da4c5965314510e0b4c3
|
||||
1
beacon/params/checkpoint_mainnet.hex
Normal file
1
beacon/params/checkpoint_mainnet.hex
Normal file
|
|
@ -0,0 +1 @@
|
|||
0x3c0cb4aa83beded1803d262664ba4392b1023f334d9cca02dc3a6925987ebe91
|
||||
1
beacon/params/checkpoint_sepolia.hex
Normal file
1
beacon/params/checkpoint_sepolia.hex
Normal file
|
|
@ -0,0 +1 @@
|
|||
0xa8d56457aa414523d93659aef1f7409bbfb72ad75e94d917c8c0b1baa38153ef
|
||||
|
|
@ -17,14 +17,25 @@
|
|||
package params
|
||||
|
||||
import (
|
||||
_ "embed"
|
||||
|
||||
"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 (
|
||||
MainnetLightConfig = (&ChainConfig{
|
||||
GenesisValidatorsRoot: common.HexToHash("0x4b363db94e286120d76eb905340fdd4e54bfe9f06bf33ff6cf5ad27f511bfe95"),
|
||||
GenesisTime: 1606824023,
|
||||
Checkpoint: common.HexToHash("0x6509b691f4de4f7b083f2784938fd52f0e131675432b3fd85ea549af9aebd3d0"),
|
||||
Checkpoint: common.HexToHash(checkpointMainnet),
|
||||
}).
|
||||
AddFork("GENESIS", 0, []byte{0, 0, 0, 0}).
|
||||
AddFork("ALTAIR", 74240, []byte{1, 0, 0, 0}).
|
||||
|
|
@ -35,7 +46,7 @@ var (
|
|||
SepoliaLightConfig = (&ChainConfig{
|
||||
GenesisValidatorsRoot: common.HexToHash("0xd8ea171f3c94aea21ebc42a1ed61052acf3f9209c00e4efbaaddac09ed9b8078"),
|
||||
GenesisTime: 1655733600,
|
||||
Checkpoint: common.HexToHash("0x456e85f5608afab3465a0580bff8572255f6d97af0c5f939e3f7536b5edb2d3f"),
|
||||
Checkpoint: common.HexToHash(checkpointSepolia),
|
||||
}).
|
||||
AddFork("GENESIS", 0, []byte{144, 0, 0, 105}).
|
||||
AddFork("ALTAIR", 50, []byte{144, 0, 0, 112}).
|
||||
|
|
@ -47,7 +58,7 @@ var (
|
|||
HoleskyLightConfig = (&ChainConfig{
|
||||
GenesisValidatorsRoot: common.HexToHash("0x9143aa7c615a7f7115e2b6aac319c03529df8242ae705fba9df39b79c59fa8b1"),
|
||||
GenesisTime: 1695902400,
|
||||
Checkpoint: common.HexToHash("0x6456a1317f54d4b4f2cb5bc9d153b5af0988fe767ef0609f0236cf29030bcff7"),
|
||||
Checkpoint: common.HexToHash(checkpointHolesky),
|
||||
}).
|
||||
AddFork("GENESIS", 0, []byte{1, 1, 112, 0}).
|
||||
AddFork("ALTAIR", 0, []byte{2, 1, 112, 0}).
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
# 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.
|
||||
|
||||
|
|
@ -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.
|
||||
|
||||
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:
|
||||
|
||||
|
|
@ -335,7 +335,7 @@ Bash example:
|
|||
|
||||
#### Arguments
|
||||
- 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
|
||||
- `text/plain`: simple hex data validated by `account_ecRecover`
|
||||
- account [address]: account to sign with
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
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 transaction to contract `EthAlarmClock` with `data`=`0xdeadbeef`, if `value=0`, `gas < 44k` and `gasPrice < 40Gwei`
|
||||
|
|
|
|||
|
|
@ -16,7 +16,7 @@ which can
|
|||
1. Take a prestate, including
|
||||
- Accounts,
|
||||
- Block context information,
|
||||
- Previous blockshashes (*optional)
|
||||
- Previous block hashes (*optional)
|
||||
2. Apply a set of transactions,
|
||||
3. Apply a mining-reward (*optional),
|
||||
4. And generate a post-state, including
|
||||
|
|
|
|||
|
|
@ -22,7 +22,7 @@ import "regexp"
|
|||
// within its filename by the execution spec test (EEST).
|
||||
type testMetadata struct {
|
||||
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
|
||||
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
|
||||
|
|
|
|||
|
|
@ -37,7 +37,9 @@ import (
|
|||
"github.com/ethereum/go-ethereum/crypto"
|
||||
"github.com/ethereum/go-ethereum/eth/ethconfig"
|
||||
"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/flags"
|
||||
"github.com/ethereum/go-ethereum/log"
|
||||
"github.com/ethereum/go-ethereum/params"
|
||||
"github.com/urfave/cli/v2"
|
||||
|
|
@ -66,7 +68,7 @@ It expects the genesis file as argument.`,
|
|||
Name: "dumpgenesis",
|
||||
Usage: "Dumps genesis block JSON configuration to stdout",
|
||||
ArgsUsage: "",
|
||||
Flags: append([]cli.Flag{utils.DataDirFlag}, utils.NetworkFlags...),
|
||||
Flags: slices.Concat([]cli.Flag{utils.DataDirFlag}, utils.NetworkFlags),
|
||||
Description: `
|
||||
The dumpgenesis command prints the genesis configuration of the network preset
|
||||
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>) ",
|
||||
Flags: slices.Concat([]cli.Flag{
|
||||
utils.CacheFlag,
|
||||
utils.SyncModeFlag,
|
||||
utils.GCModeFlag,
|
||||
utils.SnapshotFlag,
|
||||
utils.CacheDatabaseFlag,
|
||||
utils.CacheGCFlag,
|
||||
utils.NoCompactionFlag,
|
||||
utils.MetricsEnabledFlag,
|
||||
utils.MetricsEnabledExpensiveFlag,
|
||||
utils.MetricsHTTPFlag,
|
||||
|
|
@ -105,7 +107,11 @@ if one is set. Otherwise it prints the genesis from the datadir.`,
|
|||
utils.LogNoHistoryFlag,
|
||||
utils.LogExportCheckpointsFlag,
|
||||
utils.StateHistoryFlag,
|
||||
}, utils.DatabaseFlags),
|
||||
}, utils.DatabaseFlags, debug.Flags),
|
||||
Before: func(ctx *cli.Context) error {
|
||||
flags.MigrateGlobalFlags(ctx)
|
||||
return debug.Setup(ctx)
|
||||
},
|
||||
Description: `
|
||||
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.
|
||||
|
|
@ -119,10 +125,7 @@ to import successfully.`,
|
|||
Name: "export",
|
||||
Usage: "Export blockchain into file",
|
||||
ArgsUsage: "<filename> [<blockNumFirst> <blockNumLast>]",
|
||||
Flags: slices.Concat([]cli.Flag{
|
||||
utils.CacheFlag,
|
||||
utils.SyncModeFlag,
|
||||
}, utils.DatabaseFlags),
|
||||
Flags: slices.Concat([]cli.Flag{utils.CacheFlag}, utils.DatabaseFlags),
|
||||
Description: `
|
||||
Requires a first argument of the file to write to.
|
||||
Optional second and third arguments control the first and
|
||||
|
|
@ -135,12 +138,7 @@ be gzipped.`,
|
|||
Name: "import-history",
|
||||
Usage: "Import an Era archive",
|
||||
ArgsUsage: "<dir>",
|
||||
Flags: slices.Concat([]cli.Flag{
|
||||
utils.TxLookupLimitFlag,
|
||||
},
|
||||
utils.DatabaseFlags,
|
||||
utils.NetworkFlags,
|
||||
),
|
||||
Flags: slices.Concat([]cli.Flag{utils.TxLookupLimitFlag, utils.TransactionHistoryFlag}, utils.DatabaseFlags, utils.NetworkFlags),
|
||||
Description: `
|
||||
The import-history command will import blocks and their corresponding receipts
|
||||
from Era archives.
|
||||
|
|
@ -151,7 +149,7 @@ from Era archives.
|
|||
Name: "export-history",
|
||||
Usage: "Export blockchain history to Era archives",
|
||||
ArgsUsage: "<dir> <first> <last>",
|
||||
Flags: slices.Concat(utils.DatabaseFlags),
|
||||
Flags: utils.DatabaseFlags,
|
||||
Description: `
|
||||
The export-history command will export blocks and their corresponding receipts
|
||||
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",
|
||||
Usage: "Import the preimage database from an RLP stream",
|
||||
ArgsUsage: "<datafile>",
|
||||
Flags: slices.Concat([]cli.Flag{
|
||||
utils.CacheFlag,
|
||||
utils.SyncModeFlag,
|
||||
}, utils.DatabaseFlags),
|
||||
Flags: slices.Concat([]cli.Flag{utils.CacheFlag}, utils.DatabaseFlags),
|
||||
Description: `
|
||||
The import-preimages command imports hash preimages from an RLP encoded stream.
|
||||
It's deprecated, please use "geth db import" instead.
|
||||
|
|
@ -435,6 +430,10 @@ func importHistory(ctx *cli.Context) error {
|
|||
network = "mainnet"
|
||||
case ctx.Bool(utils.SepoliaFlag.Name):
|
||||
network = "sepolia"
|
||||
case ctx.Bool(utils.HoleskyFlag.Name):
|
||||
network = "holesky"
|
||||
case ctx.Bool(utils.HoodiFlag.Name):
|
||||
network = "hoodi"
|
||||
}
|
||||
} else {
|
||||
// No network flag set, try to determine network based on files
|
||||
|
|
|
|||
|
|
@ -86,12 +86,10 @@ Remove blockchain and state databases`,
|
|||
},
|
||||
}
|
||||
dbInspectCmd = &cli.Command{
|
||||
Action: inspect,
|
||||
Name: "inspect",
|
||||
ArgsUsage: "<prefix> <start>",
|
||||
Flags: slices.Concat([]cli.Flag{
|
||||
utils.SyncModeFlag,
|
||||
}, utils.NetworkFlags, utils.DatabaseFlags),
|
||||
Action: inspect,
|
||||
Name: "inspect",
|
||||
ArgsUsage: "<prefix> <start>",
|
||||
Flags: slices.Concat(utils.NetworkFlags, utils.DatabaseFlags),
|
||||
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.`,
|
||||
}
|
||||
|
|
@ -109,16 +107,13 @@ a data corruption.`,
|
|||
Action: dbStats,
|
||||
Name: "stats",
|
||||
Usage: "Print leveldb statistics",
|
||||
Flags: slices.Concat([]cli.Flag{
|
||||
utils.SyncModeFlag,
|
||||
}, utils.NetworkFlags, utils.DatabaseFlags),
|
||||
Flags: slices.Concat(utils.NetworkFlags, utils.DatabaseFlags),
|
||||
}
|
||||
dbCompactCmd = &cli.Command{
|
||||
Action: dbCompact,
|
||||
Name: "compact",
|
||||
Usage: "Compact leveldb database. WARNING: May take a very long time",
|
||||
Flags: slices.Concat([]cli.Flag{
|
||||
utils.SyncModeFlag,
|
||||
utils.CacheFlag,
|
||||
utils.CacheDatabaseFlag,
|
||||
}, 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'!`,
|
||||
}
|
||||
dbGetCmd = &cli.Command{
|
||||
Action: dbGet,
|
||||
Name: "get",
|
||||
Usage: "Show the value of a database key",
|
||||
ArgsUsage: "<hex-encoded key>",
|
||||
Flags: slices.Concat([]cli.Flag{
|
||||
utils.SyncModeFlag,
|
||||
}, utils.NetworkFlags, utils.DatabaseFlags),
|
||||
Action: dbGet,
|
||||
Name: "get",
|
||||
Usage: "Show the value of a database key",
|
||||
ArgsUsage: "<hex-encoded key>",
|
||||
Flags: slices.Concat(utils.NetworkFlags, utils.DatabaseFlags),
|
||||
Description: "This command looks up the specified database key from the database.",
|
||||
}
|
||||
dbDeleteCmd = &cli.Command{
|
||||
|
|
@ -141,9 +134,7 @@ corruption if it is aborted during execution'!`,
|
|||
Name: "delete",
|
||||
Usage: "Delete a database key (WARNING: may corrupt your database)",
|
||||
ArgsUsage: "<hex-encoded key>",
|
||||
Flags: slices.Concat([]cli.Flag{
|
||||
utils.SyncModeFlag,
|
||||
}, utils.NetworkFlags, utils.DatabaseFlags),
|
||||
Flags: slices.Concat(utils.NetworkFlags, utils.DatabaseFlags),
|
||||
Description: `This command deletes the specified database key from the database.
|
||||
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",
|
||||
Usage: "Set the value of a database key (WARNING: may corrupt your database)",
|
||||
ArgsUsage: "<hex-encoded key> <hex-encoded value>",
|
||||
Flags: slices.Concat([]cli.Flag{
|
||||
utils.SyncModeFlag,
|
||||
}, utils.NetworkFlags, utils.DatabaseFlags),
|
||||
Flags: slices.Concat(utils.NetworkFlags, utils.DatabaseFlags),
|
||||
Description: `This command sets a given database key to the given value.
|
||||
WARNING: This is a low-level operation which may cause database corruption!`,
|
||||
}
|
||||
dbGetSlotsCmd = &cli.Command{
|
||||
Action: dbDumpTrie,
|
||||
Name: "dumptrie",
|
||||
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)>",
|
||||
Flags: slices.Concat([]cli.Flag{
|
||||
utils.SyncModeFlag,
|
||||
}, utils.NetworkFlags, utils.DatabaseFlags),
|
||||
Action: dbDumpTrie,
|
||||
Name: "dumptrie",
|
||||
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)>",
|
||||
Flags: slices.Concat(utils.NetworkFlags, utils.DatabaseFlags),
|
||||
Description: "This command looks up the specified database key from the database.",
|
||||
}
|
||||
dbDumpFreezerIndex = &cli.Command{
|
||||
Action: freezerInspect,
|
||||
Name: "freezer-index",
|
||||
Usage: "Dump out the index of a specific freezer table",
|
||||
ArgsUsage: "<freezer-type> <table-type> <start (int)> <end (int)>",
|
||||
Flags: slices.Concat([]cli.Flag{
|
||||
utils.SyncModeFlag,
|
||||
}, utils.NetworkFlags, utils.DatabaseFlags),
|
||||
Action: freezerInspect,
|
||||
Name: "freezer-index",
|
||||
Usage: "Dump out the index of a specific freezer table",
|
||||
ArgsUsage: "<freezer-type> <table-type> <start (int)> <end (int)>",
|
||||
Flags: slices.Concat(utils.NetworkFlags, utils.DatabaseFlags),
|
||||
Description: "This command displays information about the freezer index.",
|
||||
}
|
||||
dbImportCmd = &cli.Command{
|
||||
Action: importLDBdata,
|
||||
Name: "import",
|
||||
Usage: "Imports leveldb-data from an exported RLP dump.",
|
||||
ArgsUsage: "<dumpfile> <start (optional)",
|
||||
Flags: slices.Concat([]cli.Flag{
|
||||
utils.SyncModeFlag,
|
||||
}, utils.NetworkFlags, utils.DatabaseFlags),
|
||||
Action: importLDBdata,
|
||||
Name: "import",
|
||||
Usage: "Imports leveldb-data from an exported RLP dump.",
|
||||
ArgsUsage: "<dumpfile> <start (optional)",
|
||||
Flags: slices.Concat(utils.NetworkFlags, utils.DatabaseFlags),
|
||||
Description: "The import command imports the specific chain data from an RLP encoded stream.",
|
||||
}
|
||||
dbExportCmd = &cli.Command{
|
||||
Action: exportChaindata,
|
||||
Name: "export",
|
||||
Usage: "Exports the chain data into an RLP dump. If the <dumpfile> has .gz suffix, gzip compression will be used.",
|
||||
ArgsUsage: "<type> <dumpfile>",
|
||||
Flags: slices.Concat([]cli.Flag{
|
||||
utils.SyncModeFlag,
|
||||
}, utils.NetworkFlags, utils.DatabaseFlags),
|
||||
Action: exportChaindata,
|
||||
Name: "export",
|
||||
Usage: "Exports the chain data into an RLP dump. If the <dumpfile> has .gz suffix, gzip compression will be used.",
|
||||
ArgsUsage: "<type> <dumpfile>",
|
||||
Flags: slices.Concat(utils.NetworkFlags, utils.DatabaseFlags),
|
||||
Description: "Exports the specified chain data to an RLP encoded stream, optionally gzip-compressed.",
|
||||
}
|
||||
dbMetadataCmd = &cli.Command{
|
||||
Action: showMetaData,
|
||||
Name: "metadata",
|
||||
Usage: "Shows metadata about the chain status.",
|
||||
Flags: slices.Concat([]cli.Flag{
|
||||
utils.SyncModeFlag,
|
||||
}, utils.NetworkFlags, utils.DatabaseFlags),
|
||||
Action: showMetaData,
|
||||
Name: "metadata",
|
||||
Usage: "Shows metadata about the chain status.",
|
||||
Flags: slices.Concat(utils.NetworkFlags, utils.DatabaseFlags),
|
||||
Description: "Shows metadata about the chain status.",
|
||||
}
|
||||
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",
|
||||
ArgsUsage: "<address> [OPTIONAL <storage-slot>]",
|
||||
Flags: slices.Concat([]cli.Flag{
|
||||
utils.SyncModeFlag,
|
||||
&cli.Uint64Flag{
|
||||
Name: "start",
|
||||
Usage: "block number of the range start, zero means earliest history",
|
||||
|
|
|
|||
|
|
@ -142,7 +142,6 @@ var (
|
|||
utils.VMTraceJsonConfigFlag,
|
||||
utils.NetworkIdFlag,
|
||||
utils.EthStatsURLFlag,
|
||||
utils.NoCompactionFlag,
|
||||
utils.GpoBlocksFlag,
|
||||
utils.GpoPercentileFlag,
|
||||
utils.GpoMaxGasPriceFlag,
|
||||
|
|
|
|||
|
|
@ -54,8 +54,8 @@ func (iter *testIterator) Next() (byte, []byte, []byte, bool) {
|
|||
if iter.index == 42 {
|
||||
iter.index += 1
|
||||
}
|
||||
return OpBatchAdd, []byte(fmt.Sprintf("key-%04d", iter.index)),
|
||||
[]byte(fmt.Sprintf("value %d", iter.index)), true
|
||||
return OpBatchAdd, fmt.Appendf(nil, "key-%04d", iter.index),
|
||||
fmt.Appendf(nil, "value %d", iter.index), true
|
||||
}
|
||||
|
||||
func (iter *testIterator) Release() {}
|
||||
|
|
@ -72,7 +72,7 @@ func testExport(t *testing.T, f string) {
|
|||
}
|
||||
// verify
|
||||
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 {
|
||||
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 {
|
||||
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 {
|
||||
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() {}
|
||||
|
|
@ -132,14 +132,14 @@ func testDeletion(t *testing.T, f string) {
|
|||
}
|
||||
db := rawdb.NewMemoryDatabase()
|
||||
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{}))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
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 err != nil {
|
||||
t.Fatalf("expected element at idx %d, got '%v'", i, err)
|
||||
|
|
|
|||
|
|
@ -2189,6 +2189,8 @@ func MakeChain(ctx *cli.Context, stack *node.Node, readonly bool) (*core.BlockCh
|
|||
}
|
||||
if !ctx.Bool(SnapshotFlag.Name) {
|
||||
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 readonly {
|
||||
|
|
|
|||
|
|
@ -72,7 +72,7 @@ func (i *HexOrDecimal256) MarshalText() ([]byte, error) {
|
|||
if i == 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,
|
||||
|
|
|
|||
|
|
@ -48,7 +48,7 @@ func (i *HexOrDecimal64) UnmarshalText(input []byte) error {
|
|||
|
||||
// MarshalText implements encoding.TextMarshaler.
|
||||
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.
|
||||
|
|
|
|||
|
|
@ -86,6 +86,11 @@ func (bc *BlockChain) GetHeaderByNumber(number uint64) *types.Header {
|
|||
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
|
||||
// backwards from the given number.
|
||||
func (bc *BlockChain) GetHeadersFrom(number, count uint64) []rlp.RawValue {
|
||||
|
|
|
|||
|
|
@ -504,6 +504,13 @@ func GenerateVerkleChain(config *params.ChainConfig, parent *types.Block, engine
|
|||
if gen != nil {
|
||||
gen(i, b)
|
||||
}
|
||||
|
||||
requests := b.collectRequests(false)
|
||||
if requests != nil {
|
||||
reqHash := types.CalcRequestsHash(requests)
|
||||
b.header.RequestsHash = &reqHash
|
||||
}
|
||||
|
||||
body := &types.Body{
|
||||
Transactions: b.txs,
|
||||
Uncles: b.uncles,
|
||||
|
|
|
|||
|
|
@ -83,7 +83,11 @@ func (cv *ChainView) getReceipts(number uint64) types.Receipts {
|
|||
if number > cv.headNumber {
|
||||
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.
|
||||
|
|
|
|||
|
|
@ -29,8 +29,24 @@ import (
|
|||
"github.com/ethereum/go-ethereum/core/rawdb"
|
||||
"github.com/ethereum/go-ethereum/core/types"
|
||||
"github.com/ethereum/go-ethereum/ethdb"
|
||||
"github.com/ethereum/go-ethereum/ethdb/leveldb"
|
||||
"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 (
|
||||
|
|
@ -53,11 +69,13 @@ type FilterMaps struct {
|
|||
// This is configured by the --history.logs.disable Geth flag.
|
||||
// We chose to implement disabling this way because it requires less special
|
||||
// case logic in eth/filters.
|
||||
disabled bool
|
||||
disabled bool
|
||||
disabledCh chan struct{} // closed by indexer if disabled
|
||||
|
||||
closeCh chan struct{}
|
||||
closeWg sync.WaitGroup
|
||||
history uint64
|
||||
hashScheme bool // use hashdb-safe delete range method
|
||||
exportFileName string
|
||||
Params
|
||||
|
||||
|
|
@ -66,10 +84,11 @@ type FilterMaps struct {
|
|||
// fields written by the indexer and read by matcher backend. Indexer can
|
||||
// read them without a lock and write them under indexLock write lock.
|
||||
// Matcher backend can read them under indexLock read lock.
|
||||
indexLock sync.RWMutex
|
||||
indexedRange filterMapsRange
|
||||
indexedView *ChainView // always consistent with the log index
|
||||
hasTempRange bool
|
||||
indexLock sync.RWMutex
|
||||
indexedRange filterMapsRange
|
||||
cleanedEpochsBefore uint32 // all unindexed data cleaned before this point
|
||||
indexedView *ChainView // always consistent with the log index
|
||||
hasTempRange bool
|
||||
|
||||
// also accessed by indexer and matcher backend but no locking needed.
|
||||
filterMapCache *lru.Cache[uint32, filterMap]
|
||||
|
|
@ -179,6 +198,10 @@ type Config struct {
|
|||
// This option enables the checkpoint JSON file generator.
|
||||
// If set, the given file will be updated with checkpoint information.
|
||||
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.
|
||||
|
|
@ -196,6 +219,8 @@ func NewFilterMaps(db ethdb.KeyValueStore, initView *ChainView, historyCutoff, f
|
|||
blockProcessingCh: make(chan bool, 1),
|
||||
history: config.History,
|
||||
disabled: config.Disabled,
|
||||
hashScheme: config.HashScheme,
|
||||
disabledCh: make(chan struct{}),
|
||||
exportFileName: config.ExportFileName,
|
||||
Params: params,
|
||||
indexedRange: filterMapsRange{
|
||||
|
|
@ -206,13 +231,17 @@ func NewFilterMaps(db ethdb.KeyValueStore, initView *ChainView, historyCutoff, f
|
|||
maps: common.NewRange(rs.MapsFirst, rs.MapsAfterLast-rs.MapsFirst),
|
||||
tailPartialEpoch: rs.TailPartialEpoch,
|
||||
},
|
||||
matcherSyncCh: make(chan *FilterMapsMatcherBackend),
|
||||
matchers: make(map[*FilterMapsMatcherBackend]struct{}),
|
||||
filterMapCache: lru.NewCache[uint32, filterMap](cachedFilterMaps),
|
||||
lastBlockCache: lru.NewCache[uint32, lastBlockOfMap](cachedLastBlocks),
|
||||
lvPointerCache: lru.NewCache[uint64, uint64](cachedLvPointers),
|
||||
baseRowsCache: lru.NewCache[uint64, [][]uint32](cachedBaseRows),
|
||||
renderSnapshots: lru.NewCache[uint64, *renderedMap](cachedRenderSnapshots),
|
||||
// deleting last unindexed epoch might have been interrupted by shutdown
|
||||
cleanedEpochsBefore: max(rs.MapsFirst>>params.logMapsPerEpoch, 1) - 1,
|
||||
historyCutoff: historyCutoff,
|
||||
finalBlock: finalBlock,
|
||||
matcherSyncCh: make(chan *FilterMapsMatcherBackend),
|
||||
matchers: make(map[*FilterMapsMatcherBackend]struct{}),
|
||||
filterMapCache: lru.NewCache[uint32, filterMap](cachedFilterMaps),
|
||||
lastBlockCache: lru.NewCache[uint32, lastBlockOfMap](cachedLastBlocks),
|
||||
lvPointerCache: lru.NewCache[uint64, uint64](cachedLvPointers),
|
||||
baseRowsCache: lru.NewCache[uint64, [][]uint32](cachedBaseRows),
|
||||
renderSnapshots: lru.NewCache[uint64, *renderedMap](cachedRenderSnapshots),
|
||||
}
|
||||
|
||||
// Set initial indexer target.
|
||||
|
|
@ -278,8 +307,13 @@ func (f *FilterMaps) initChainView(chainView *ChainView) *ChainView {
|
|||
}
|
||||
|
||||
// reset un-initializes the FilterMaps structure and removes all related data from
|
||||
// the database. The function returns true if everything was successfully removed.
|
||||
func (f *FilterMaps) reset() bool {
|
||||
// the database.
|
||||
// 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.indexedRange = filterMapsRange{}
|
||||
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
|
||||
// startup and any leftover data will be removed even if it cannot finish now.
|
||||
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.
|
||||
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()
|
||||
defer f.indexLock.Unlock()
|
||||
|
||||
|
|
@ -317,6 +366,13 @@ func (f *FilterMaps) init() error {
|
|||
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()
|
||||
for epoch := range bestLen {
|
||||
cp := checkpoints[bestIdx][epoch]
|
||||
|
|
@ -337,38 +393,37 @@ func (f *FilterMaps) init() error {
|
|||
|
||||
// removeBloomBits removes old bloom bits data from the database.
|
||||
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()
|
||||
}
|
||||
|
||||
// safeDeleteRange calls the specified database range deleter function
|
||||
// repeatedly as long as it returns leveldb.ErrTooManyKeys.
|
||||
// This wrapper is necessary because of the leveldb fallback implementation
|
||||
// of DeleteRange.
|
||||
func (f *FilterMaps) safeDeleteRange(removeFn func(ethdb.KeyValueRangeDeleter) error, action string) bool {
|
||||
start := time.Now()
|
||||
var retry bool
|
||||
for {
|
||||
err := removeFn(f.db)
|
||||
if err == nil {
|
||||
if retry {
|
||||
log.Info(action+" finished", "elapsed", time.Since(start))
|
||||
}
|
||||
return true
|
||||
// safeDeleteWithLogs is a wrapper for a function that performs a safe range
|
||||
// delete operation using rawdb.SafeDeleteRange. It emits log messages if the
|
||||
// process takes long enough to call the stop callback.
|
||||
func (f *FilterMaps) safeDeleteWithLogs(deleteFn func(db ethdb.KeyValueStore, hashScheme bool, stopCb func(bool) bool) error, action string, stopCb func() bool) error {
|
||||
var (
|
||||
start = time.Now()
|
||||
logPrinted bool
|
||||
lastLogPrinted = start
|
||||
)
|
||||
switch err := deleteFn(f.db, f.hashScheme, func(deleted bool) bool {
|
||||
if deleted && !logPrinted || time.Since(lastLogPrinted) > time.Second*10 {
|
||||
log.Info(action+" in progress...", "elapsed", common.PrettyDuration(time.Since(start)))
|
||||
logPrinted, lastLogPrinted = true, time.Now()
|
||||
}
|
||||
if err != leveldb.ErrTooManyKeys {
|
||||
log.Error(action+" failed", "error", err)
|
||||
return false
|
||||
}
|
||||
select {
|
||||
case <-f.closeCh:
|
||||
return false
|
||||
default:
|
||||
}
|
||||
if !retry {
|
||||
log.Info(action+" in progress...", "elapsed", time.Since(start))
|
||||
retry = true
|
||||
return stopCb()
|
||||
}); {
|
||||
case err == nil:
|
||||
if logPrinted {
|
||||
log.Info(action+" finished", "elapsed", common.PrettyDuration(time.Since(start)))
|
||||
}
|
||||
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,
|
||||
}
|
||||
rawdb.WriteFilterMapsRange(batch, rs)
|
||||
if !isTempRange {
|
||||
mapCountGauge.Update(int64(newRange.maps.Count() + newRange.tailPartialEpoch))
|
||||
}
|
||||
} else {
|
||||
rawdb.DeleteFilterMapsRange(batch)
|
||||
mapCountGauge.Update(0)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -637,54 +696,97 @@ func (f *FilterMaps) deleteLastBlockOfMap(batch ethdb.Batch, mapIndex uint32) {
|
|||
rawdb.DeleteFilterMapLastBlock(batch, mapIndex)
|
||||
}
|
||||
|
||||
// deleteTailEpoch deletes index data from the earliest, either fully or partially
|
||||
// indexed epoch. The last block pointer for the last map of the epoch and the
|
||||
// corresponding block log value pointer are retained as these are always assumed
|
||||
// to be available for each epoch.
|
||||
func (f *FilterMaps) deleteTailEpoch(epoch uint32) error {
|
||||
// deleteTailEpoch deletes index data from the specified epoch. The last block
|
||||
// pointer for the last map of the epoch and the corresponding block log value
|
||||
// pointer are retained as these are always assumed to be available for each
|
||||
// epoch as boundary markers.
|
||||
// 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()
|
||||
defer f.indexLock.Unlock()
|
||||
|
||||
// determine epoch boundaries
|
||||
firstMap := epoch << f.logMapsPerEpoch
|
||||
lastBlock, _, err := f.getLastBlockOfMap(firstMap + f.mapsPerEpoch - 1)
|
||||
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
|
||||
if epoch > 0 {
|
||||
firstBlock, _, err = f.getLastBlockOfMap(firstMap - 1)
|
||||
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++
|
||||
}
|
||||
fmr := f.indexedRange
|
||||
if f.indexedRange.maps.First() == firstMap &&
|
||||
f.indexedRange.maps.AfterLast() > firstMap+f.mapsPerEpoch &&
|
||||
f.indexedRange.tailPartialEpoch == 0 {
|
||||
fmr.maps.SetFirst(firstMap + f.mapsPerEpoch)
|
||||
fmr.blocks.SetFirst(lastBlock + 1)
|
||||
} else if f.indexedRange.maps.First() == firstMap+f.mapsPerEpoch {
|
||||
// update rendered range if necessary
|
||||
var (
|
||||
fmr = f.indexedRange
|
||||
firstEpoch = f.indexedRange.maps.First() >> f.logMapsPerEpoch
|
||||
afterLastEpoch = (f.indexedRange.maps.AfterLast() + f.mapsPerEpoch - 1) >> f.logMapsPerEpoch
|
||||
)
|
||||
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.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 {
|
||||
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.
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@
|
|||
package filtermaps
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"math"
|
||||
"time"
|
||||
|
||||
|
|
@ -36,26 +37,28 @@ func (f *FilterMaps) indexerLoop() {
|
|||
|
||||
if f.disabled {
|
||||
f.reset()
|
||||
close(f.disabledCh)
|
||||
return
|
||||
}
|
||||
log.Info("Started log indexer")
|
||||
|
||||
for !f.stop {
|
||||
if !f.indexedRange.initialized {
|
||||
if err := f.init(); err != nil {
|
||||
log.Error("Error initializing log index", "error", err)
|
||||
// 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.
|
||||
if f.targetView.headNumber == 0 {
|
||||
// initialize when chain head is available
|
||||
f.processSingleEvent(true)
|
||||
continue
|
||||
}
|
||||
if err := f.init(); err != nil {
|
||||
f.disableForError("initialization", err)
|
||||
f.reset() // remove broken index from DB
|
||||
return
|
||||
}
|
||||
}
|
||||
if !f.targetHeadIndexed() {
|
||||
if !f.tryIndexHead() {
|
||||
// either shutdown or unexpected error; in the latter case ensure
|
||||
// that proper shutdown is still possible.
|
||||
f.processSingleEvent(true)
|
||||
if err := f.tryIndexHead(); err != nil && err != errChainUpdate {
|
||||
f.disableForError("head rendering", err)
|
||||
return
|
||||
}
|
||||
} else {
|
||||
if f.finalBlock != f.lastFinal {
|
||||
|
|
@ -64,13 +67,39 @@ func (f *FilterMaps) indexerLoop() {
|
|||
}
|
||||
f.lastFinal = f.finalBlock
|
||||
}
|
||||
if f.tryIndexTail() && f.tryUnindexTail() {
|
||||
f.waitForNewHead()
|
||||
// always attempt unindexing before indexing the tail in order to
|
||||
// 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 {
|
||||
targetView *ChainView
|
||||
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
|
||||
// latest targetView.
|
||||
func (f *FilterMaps) WaitIdle() {
|
||||
if f.disabled {
|
||||
f.closeWg.Wait()
|
||||
return
|
||||
}
|
||||
for {
|
||||
ch := make(chan bool)
|
||||
f.waitIdleCh <- ch
|
||||
if <-ch {
|
||||
select {
|
||||
case f.waitIdleCh <- ch:
|
||||
if <-ch {
|
||||
return
|
||||
}
|
||||
case <-f.disabledCh:
|
||||
f.closeWg.Wait()
|
||||
return
|
||||
}
|
||||
}
|
||||
|
|
@ -191,16 +221,16 @@ func (f *FilterMaps) setTarget(target targetUpdate) {
|
|||
f.finalBlock = target.finalBlock
|
||||
}
|
||||
|
||||
// tryIndexHead tries to render head maps according to the current targetView
|
||||
// and returns true if successful.
|
||||
func (f *FilterMaps) tryIndexHead() bool {
|
||||
// tryIndexHead tries to render head maps according to the current targetView.
|
||||
// Should be called when targetHeadIndexed returns false. If this function
|
||||
// returns no error then either stop is true or head indexing is finished.
|
||||
func (f *FilterMaps) tryIndexHead() error {
|
||||
headRenderer, err := f.renderMapsBefore(math.MaxUint32)
|
||||
if err != nil {
|
||||
log.Error("Error creating log index head renderer", "error", err)
|
||||
return false
|
||||
return err
|
||||
}
|
||||
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 {
|
||||
f.lastLogHeadIndex = time.Now()
|
||||
|
|
@ -225,8 +255,7 @@ func (f *FilterMaps) tryIndexHead() bool {
|
|||
f.lastLogHeadIndex = time.Now()
|
||||
}
|
||||
}); err != nil {
|
||||
log.Error("Log index head rendering failed", "error", err)
|
||||
return false
|
||||
return err
|
||||
}
|
||||
if f.loggedHeadIndex && f.indexedRange.hasIndexedBlocks() {
|
||||
log.Info("Log index head rendering finished",
|
||||
|
|
@ -235,7 +264,7 @@ func (f *FilterMaps) tryIndexHead() bool {
|
|||
"elapsed", common.PrettyDuration(time.Since(f.startedHeadIndexAt)))
|
||||
}
|
||||
f.loggedHeadIndex, f.startedHeadIndex = false, false
|
||||
return true
|
||||
return nil
|
||||
}
|
||||
|
||||
// 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
|
||||
// rendered according to targetView and is suspended as soon as the targetView
|
||||
// is changed.
|
||||
func (f *FilterMaps) tryIndexTail() bool {
|
||||
func (f *FilterMaps) tryIndexTail() (bool, error) {
|
||||
for {
|
||||
firstEpoch := f.indexedRange.maps.First() >> f.logMapsPerEpoch
|
||||
if firstEpoch == 0 || !f.needTailEpoch(firstEpoch-1) {
|
||||
|
|
@ -251,7 +280,7 @@ func (f *FilterMaps) tryIndexTail() bool {
|
|||
}
|
||||
f.processEvents()
|
||||
if f.stop || !f.targetHeadIndexed() {
|
||||
return false
|
||||
return false, nil
|
||||
}
|
||||
// resume process if tail rendering was interrupted because of head rendering
|
||||
tailRenderer := f.tailRenderer
|
||||
|
|
@ -263,8 +292,7 @@ func (f *FilterMaps) tryIndexTail() bool {
|
|||
var err error
|
||||
tailRenderer, err = f.renderMapsBefore(f.indexedRange.maps.First())
|
||||
if err != nil {
|
||||
log.Error("Error creating log index tail renderer", "error", err)
|
||||
return false
|
||||
return false, err
|
||||
}
|
||||
}
|
||||
if tailRenderer == nil {
|
||||
|
|
@ -297,13 +325,16 @@ func (f *FilterMaps) tryIndexTail() bool {
|
|||
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
|
||||
log.Error("Log index tail rendering failed", "error", err)
|
||||
return true, nil
|
||||
}
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
if !done {
|
||||
f.tailRenderer = tailRenderer // only keep tail renderer if interrupted by stopCb
|
||||
return false
|
||||
return false, nil
|
||||
}
|
||||
}
|
||||
if f.loggedTailIndex && f.indexedRange.hasIndexedBlocks() {
|
||||
|
|
@ -313,32 +344,31 @@ func (f *FilterMaps) tryIndexTail() bool {
|
|||
"elapsed", common.PrettyDuration(time.Since(f.startedTailIndexAt)))
|
||||
f.loggedTailIndex = false
|
||||
}
|
||||
return true
|
||||
return true, nil
|
||||
}
|
||||
|
||||
// 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.
|
||||
// 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.
|
||||
func (f *FilterMaps) tryUnindexTail() bool {
|
||||
for {
|
||||
firstEpoch := (f.indexedRange.maps.First() - f.indexedRange.tailPartialEpoch) >> f.logMapsPerEpoch
|
||||
if f.needTailEpoch(firstEpoch) {
|
||||
break
|
||||
}
|
||||
f.processEvents()
|
||||
if f.stop {
|
||||
return false
|
||||
}
|
||||
func (f *FilterMaps) tryUnindexTail() (bool, error) {
|
||||
firstEpoch := f.indexedRange.maps.First() >> f.logMapsPerEpoch
|
||||
if f.indexedRange.tailPartialEpoch > 0 && firstEpoch > 0 {
|
||||
firstEpoch--
|
||||
}
|
||||
for epoch := min(firstEpoch, f.cleanedEpochsBefore); !f.needTailEpoch(epoch); epoch++ {
|
||||
if !f.startedTailUnindex {
|
||||
f.startedTailUnindexAt = time.Now()
|
||||
f.startedTailUnindex = true
|
||||
f.ptrTailUnindexMap = f.indexedRange.maps.First() - f.indexedRange.tailPartialEpoch
|
||||
f.ptrTailUnindexBlock = f.indexedRange.blocks.First() - f.tailPartialBlocks()
|
||||
}
|
||||
if err := f.deleteTailEpoch(firstEpoch); err != nil {
|
||||
log.Error("Log index tail epoch unindexing failed", "error", err)
|
||||
return false
|
||||
if done, err := f.deleteTailEpoch(epoch); !done {
|
||||
return false, err
|
||||
}
|
||||
f.processEvents()
|
||||
if f.stop || !f.targetHeadIndexed() {
|
||||
return false, nil
|
||||
}
|
||||
}
|
||||
if f.startedTailUnindex && f.indexedRange.hasIndexedBlocks() {
|
||||
|
|
@ -349,7 +379,7 @@ func (f *FilterMaps) tryUnindexTail() bool {
|
|||
"elapsed", common.PrettyDuration(time.Since(f.startedTailUnindexAt)))
|
||||
f.startedTailUnindex = false
|
||||
}
|
||||
return true
|
||||
return true, nil
|
||||
}
|
||||
|
||||
// needTailEpoch returns true if the given tail epoch needs to be kept
|
||||
|
|
|
|||
|
|
@ -21,6 +21,7 @@ import (
|
|||
"fmt"
|
||||
"math"
|
||||
"sort"
|
||||
"time"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"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.
|
||||
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) {
|
||||
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 {
|
||||
waitCnt++
|
||||
if waitCnt >= valuesPerCallback {
|
||||
totalTime += time.Since(start)
|
||||
if stopCb() {
|
||||
return false, nil
|
||||
}
|
||||
start = time.Now()
|
||||
if !r.iterator.updateChainView(r.f.targetView) {
|
||||
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)
|
||||
}
|
||||
if !r.iterator.skipToBoundary {
|
||||
logValuesProcessed++
|
||||
r.currentMap.lastBlock = r.iterator.blockNumber
|
||||
if r.iterator.blockStart {
|
||||
blocksProcessed++
|
||||
r.currentMap.blockLvPtrs = append(r.currentMap.blockLvPtrs, r.iterator.lvIndex)
|
||||
}
|
||||
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.lastBlockId = r.f.targetView.getBlockId(r.currentMap.lastBlock)
|
||||
totalTime += time.Since(start)
|
||||
mapRenderTimer.Update(totalTime)
|
||||
mapLogValueMeter.Mark(logValuesProcessed)
|
||||
mapBlockMeter.Mark(blocksProcessed)
|
||||
return true, nil
|
||||
}
|
||||
|
||||
// writeFinishedMaps writes rendered maps to the database and updates
|
||||
// filterMapsRange and indexedView accordingly.
|
||||
func (r *mapRenderer) writeFinishedMaps(pauseCb func() bool) error {
|
||||
var totalTime time.Duration
|
||||
start := time.Now()
|
||||
if len(r.finishedMaps) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
|
@ -379,7 +395,7 @@ func (r *mapRenderer) writeFinishedMaps(pauseCb func() bool) error {
|
|||
if err != nil {
|
||||
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()
|
||||
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
|
||||
// events and pausing while block processing is in progress
|
||||
r.f.indexLock.Unlock()
|
||||
totalTime += time.Since(start)
|
||||
pauseCb()
|
||||
start = time.Now()
|
||||
r.f.indexLock.Lock()
|
||||
batch = r.f.db.NewBatch()
|
||||
}
|
||||
|
|
@ -477,6 +495,8 @@ func (r *mapRenderer) writeFinishedMaps(pauseCb func() bool) error {
|
|||
if err := batch.Write(); err != nil {
|
||||
log.Crit("Error writing log index update batch", "error", err)
|
||||
}
|
||||
totalTime += time.Since(start)
|
||||
mapWriteTimer.Update(totalTime)
|
||||
return nil
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -125,6 +125,7 @@ func GetPotentialMatches(ctx context.Context, backend MatcherBackend, firstBlock
|
|||
|
||||
start := time.Now()
|
||||
res, err := m.process()
|
||||
matchRequestTimer.Update(time.Since(start))
|
||||
|
||||
if doRuntimeStats {
|
||||
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...)
|
||||
if err := tasks[waitEpoch].err; err != nil {
|
||||
if err == ErrMatchAll {
|
||||
matchAllMeter.Mark(1)
|
||||
return logs, 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.
|
||||
func (m *matcherEnv) processEpoch(epochIndex uint32) ([]*types.Log, error) {
|
||||
start := time.Now()
|
||||
var logs []*types.Log
|
||||
// create a list of map indices to process
|
||||
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...)
|
||||
}
|
||||
m.getLogStats.addAmount(st, int64(len(logs)))
|
||||
matchEpochTimer.Update(time.Since(start))
|
||||
return logs, nil
|
||||
}
|
||||
|
||||
|
|
@ -273,6 +277,7 @@ func (m *matcherEnv) getLogsFromMatches(matches potentialMatches) ([]*types.Log,
|
|||
if log != nil {
|
||||
logs = append(logs, log)
|
||||
}
|
||||
matchLogLookup.Mark(1)
|
||||
}
|
||||
return logs, nil
|
||||
}
|
||||
|
|
@ -381,6 +386,13 @@ func (m *singleMatcherInstance) getMatchesForLayer(ctx context.Context, layerInd
|
|||
m.stats.setState(&st, stNone)
|
||||
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.setState(&st, stOther)
|
||||
filterRows = append(filterRows, filterRow)
|
||||
|
|
|
|||
|
|
@ -141,10 +141,6 @@ func (fm *FilterMapsMatcherBackend) synced() {
|
|||
// 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.
|
||||
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)
|
||||
fm.f.matchersLock.Lock()
|
||||
fm.syncCh = syncCh
|
||||
|
|
@ -154,12 +150,16 @@ func (fm *FilterMapsMatcherBackend) SyncLogIndex(ctx context.Context) (SyncRange
|
|||
case fm.f.matcherSyncCh <- fm:
|
||||
case <-ctx.Done():
|
||||
return SyncRange{}, ctx.Err()
|
||||
case <-fm.f.disabledCh:
|
||||
return SyncRange{HeadNumber: fm.f.targetView.headNumber}, nil
|
||||
}
|
||||
select {
|
||||
case vr := <-syncCh:
|
||||
return vr, nil
|
||||
case <-ctx.Done():
|
||||
return SyncRange{}, ctx.Err()
|
||||
case <-fm.f.disabledCh:
|
||||
return SyncRange{HeadNumber: fm.f.targetView.headNumber}, nil
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -354,10 +354,8 @@ func WriteFilterMapBaseRows(db ethdb.KeyValueWriter, mapRowIndex uint64, rows []
|
|||
}
|
||||
}
|
||||
|
||||
func DeleteFilterMapRows(db ethdb.KeyValueRangeDeleter, mapRows common.Range[uint64]) {
|
||||
if err := db.DeleteRange(filterMapRowKey(mapRows.First(), false), filterMapRowKey(mapRows.AfterLast(), false)); err != nil {
|
||||
log.Crit("Failed to delete range of filter map rows", "err", err)
|
||||
}
|
||||
func DeleteFilterMapRows(db ethdb.KeyValueStore, mapRows common.Range[uint64], hashScheme bool, stopCallback func(bool) bool) error {
|
||||
return SafeDeleteRange(db, filterMapRowKey(mapRows.First(), false), filterMapRowKey(mapRows.AfterLast(), false), hashScheme, stopCallback)
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
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
|
||||
copy(id[:], enc[8:])
|
||||
|
|
@ -394,10 +392,8 @@ func DeleteFilterMapLastBlock(db ethdb.KeyValueWriter, mapIndex uint32) {
|
|||
}
|
||||
}
|
||||
|
||||
func DeleteFilterMapLastBlocks(db ethdb.KeyValueRangeDeleter, maps common.Range[uint32]) {
|
||||
if err := db.DeleteRange(filterMapLastBlockKey(maps.First()), filterMapLastBlockKey(maps.AfterLast())); err != nil {
|
||||
log.Crit("Failed to delete range of filter map last block pointers", "err", err)
|
||||
}
|
||||
func DeleteFilterMapLastBlocks(db ethdb.KeyValueStore, maps common.Range[uint32], hashScheme bool, stopCallback func(bool) bool) error {
|
||||
return SafeDeleteRange(db, filterMapLastBlockKey(maps.First()), filterMapLastBlockKey(maps.AfterLast()), hashScheme, stopCallback)
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
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
|
||||
}
|
||||
|
|
@ -431,10 +427,8 @@ func DeleteBlockLvPointer(db ethdb.KeyValueWriter, blockNumber uint64) {
|
|||
}
|
||||
}
|
||||
|
||||
func DeleteBlockLvPointers(db ethdb.KeyValueRangeDeleter, blocks common.Range[uint64]) {
|
||||
if err := db.DeleteRange(filterMapBlockLVKey(blocks.First()), filterMapBlockLVKey(blocks.AfterLast())); err != nil {
|
||||
log.Crit("Failed to delete range of block log value pointers", "err", err)
|
||||
}
|
||||
func DeleteBlockLvPointers(db ethdb.KeyValueStore, blocks common.Range[uint64], hashScheme bool, stopCallback func(bool) bool) error {
|
||||
return SafeDeleteRange(db, filterMapBlockLVKey(blocks.First()), filterMapBlockLVKey(blocks.AfterLast()), hashScheme, stopCallback)
|
||||
}
|
||||
|
||||
// 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.
|
||||
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[len(end)-1]++
|
||||
return db.DeleteRange(prefix, end)
|
||||
return SafeDeleteRange(db, prefix, end, hashScheme, stopCallback)
|
||||
}
|
||||
|
||||
// DeleteFilterMapsDb removes the entire filter maps database
|
||||
func DeleteFilterMapsDb(db ethdb.KeyValueRangeDeleter) error {
|
||||
return deletePrefixRange(db, []byte(filterMapsPrefix))
|
||||
func DeleteFilterMapsDb(db ethdb.KeyValueStore, hashScheme bool, stopCallback func(bool) bool) error {
|
||||
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.
|
||||
func DeleteBloomBitsDb(db ethdb.KeyValueRangeDeleter) error {
|
||||
if err := deletePrefixRange(db, bloomBitsPrefix); err != nil {
|
||||
func DeleteBloomBitsDb(db ethdb.KeyValueStore, hashScheme bool, stopCallback func(bool) bool) error {
|
||||
if err := deletePrefixRange(db, bloomBitsPrefix, hashScheme, stopCallback); err != nil {
|
||||
return err
|
||||
}
|
||||
return deletePrefixRange(db, bloomBitsIndexPrefix)
|
||||
return deletePrefixRange(db, bloomBitsMetaPrefix, hashScheme, stopCallback)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -20,18 +20,23 @@ import (
|
|||
"bytes"
|
||||
"errors"
|
||||
"fmt"
|
||||
"maps"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"slices"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/crypto"
|
||||
"github.com/ethereum/go-ethereum/ethdb"
|
||||
"github.com/ethereum/go-ethereum/ethdb/memorydb"
|
||||
"github.com/ethereum/go-ethereum/log"
|
||||
"github.com/olekukonko/tablewriter"
|
||||
)
|
||||
|
||||
var ErrDeleteRangeInterrupted = errors.New("safe delete range operation interrupted")
|
||||
|
||||
// freezerdb is a database wrapper that enables ancient chain segment freezing.
|
||||
type freezerdb struct {
|
||||
ethdb.KeyValueStore
|
||||
|
|
@ -360,39 +365,43 @@ func InspectDatabase(db ethdb.Database, keyPrefix, keyStart []byte) error {
|
|||
logged = time.Now()
|
||||
|
||||
// Key-value store statistics
|
||||
headers stat
|
||||
bodies stat
|
||||
receipts stat
|
||||
tds stat
|
||||
numHashPairings stat
|
||||
hashNumPairings stat
|
||||
legacyTries stat
|
||||
stateLookups stat
|
||||
accountTries stat
|
||||
storageTries stat
|
||||
codes stat
|
||||
txLookups stat
|
||||
accountSnaps stat
|
||||
storageSnaps stat
|
||||
preimages stat
|
||||
filterMaps stat
|
||||
beaconHeaders stat
|
||||
cliqueSnaps stat
|
||||
headers stat
|
||||
bodies stat
|
||||
receipts stat
|
||||
tds stat
|
||||
numHashPairings stat
|
||||
hashNumPairings stat
|
||||
legacyTries stat
|
||||
stateLookups stat
|
||||
accountTries stat
|
||||
storageTries stat
|
||||
codes stat
|
||||
txLookups stat
|
||||
accountSnaps stat
|
||||
storageSnaps stat
|
||||
preimages stat
|
||||
beaconHeaders stat
|
||||
cliqueSnaps stat
|
||||
bloomBits stat
|
||||
filterMapRows stat
|
||||
filterMapLastBlock stat
|
||||
filterMapBlockLV stat
|
||||
|
||||
// Verkle statistics
|
||||
verkleTries stat
|
||||
verkleStateLookups stat
|
||||
|
||||
// Les statistic
|
||||
chtTrieNodes stat
|
||||
bloomTrieNodes stat
|
||||
|
||||
// Meta- and unaccounted data
|
||||
metadata stat
|
||||
unaccounted stat
|
||||
|
||||
// Totals
|
||||
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.
|
||||
for it.Next() {
|
||||
|
|
@ -436,20 +445,24 @@ func InspectDatabase(db ethdb.Database, keyPrefix, keyStart []byte) error {
|
|||
metadata.Add(size)
|
||||
case bytes.HasPrefix(key, genesisPrefix) && len(key) == (len(genesisPrefix)+common.HashLength):
|
||||
metadata.Add(size)
|
||||
case bytes.HasPrefix(key, []byte(filterMapsPrefix)):
|
||||
filterMaps.Add(size)
|
||||
case bytes.HasPrefix(key, skeletonHeaderPrefix) && len(key) == (len(skeletonHeaderPrefix)+8):
|
||||
beaconHeaders.Add(size)
|
||||
case bytes.HasPrefix(key, CliqueSnapshotPrefix) && len(key) == 7+common.HashLength:
|
||||
cliqueSnaps.Add(size)
|
||||
case bytes.HasPrefix(key, ChtTablePrefix) ||
|
||||
bytes.HasPrefix(key, ChtIndexTablePrefix) ||
|
||||
bytes.HasPrefix(key, ChtPrefix): // Canonical hash trie
|
||||
chtTrieNodes.Add(size)
|
||||
case bytes.HasPrefix(key, BloomTrieTablePrefix) ||
|
||||
bytes.HasPrefix(key, BloomTrieIndexPrefix) ||
|
||||
bytes.HasPrefix(key, BloomTriePrefix): // Bloomtrie sub
|
||||
bloomTrieNodes.Add(size)
|
||||
|
||||
// new log index
|
||||
case bytes.HasPrefix(key, filterMapRowPrefix) && len(key) <= len(filterMapRowPrefix)+9:
|
||||
filterMapRows.Add(size)
|
||||
case bytes.HasPrefix(key, filterMapLastBlockPrefix) && len(key) == len(filterMapLastBlockPrefix)+4:
|
||||
filterMapLastBlock.Add(size)
|
||||
case bytes.HasPrefix(key, filterMapBlockLVPrefix) && len(key) == len(filterMapBlockLVPrefix)+8:
|
||||
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
|
||||
case bytes.HasPrefix(key, VerklePrefix):
|
||||
|
|
@ -468,24 +481,19 @@ func InspectDatabase(db ethdb.Database, keyPrefix, keyStart []byte) error {
|
|||
default:
|
||||
unaccounted.Add(size)
|
||||
}
|
||||
|
||||
// Metadata keys
|
||||
case slices.ContainsFunc(knownMetadataKeys, func(x []byte) bool { return bytes.Equal(x, key) }):
|
||||
metadata.Add(size)
|
||||
|
||||
default:
|
||||
var accounted bool
|
||||
for _, meta := range [][]byte{
|
||||
databaseVersionKey, headHeaderKey, headBlockKey, headFastBlockKey, headFinalizedBlockKey,
|
||||
lastPivotKey, fastTrieProgressKey, snapshotDisabledKey, SnapshotRootKey, snapshotJournalKey,
|
||||
snapshotGeneratorKey, snapshotRecoveryKey, txIndexTailKey, fastTxLookupLimitKey,
|
||||
uncleanShutdownKey, badBlockKey, transitionStatusKey, skeletonSyncStatusKey,
|
||||
persistentStateIDKey, trieJournalKey, snapshotSyncStatusKey, snapSyncStatusFlagKey,
|
||||
} {
|
||||
if bytes.Equal(key, meta) {
|
||||
metadata.Add(size)
|
||||
accounted = true
|
||||
break
|
||||
unaccounted.Add(size)
|
||||
if len(key) >= 2 {
|
||||
prefix := [2]byte(key[:2])
|
||||
if _, ok := unaccountedKeys[prefix]; !ok {
|
||||
unaccountedKeys[prefix] = bytes.Clone(key)
|
||||
}
|
||||
}
|
||||
if !accounted {
|
||||
unaccounted.Add(size)
|
||||
}
|
||||
}
|
||||
count++
|
||||
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 hash->number", hashNumPairings.Size(), hashNumPairings.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", "Hash trie nodes", legacyTries.Size(), legacyTries.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", "Clique snapshots", cliqueSnaps.Size(), cliqueSnaps.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.
|
||||
ancients, err := inspectFreezers(db)
|
||||
|
|
@ -543,10 +552,23 @@ func InspectDatabase(db ethdb.Database, keyPrefix, keyStart []byte) error {
|
|||
|
||||
if unaccounted.size > 0 {
|
||||
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
|
||||
}
|
||||
|
||||
// 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.
|
||||
func printChainMetadata(db ethdb.KeyValueStore) {
|
||||
fmt.Fprintf(os.Stderr, "Chain metadata\n")
|
||||
|
|
@ -566,6 +588,7 @@ func ReadChainMetadata(db ethdb.KeyValueStore) [][]string {
|
|||
}
|
||||
return fmt.Sprintf("%d (%#x)", *val, *val)
|
||||
}
|
||||
|
||||
data := [][]string{
|
||||
{"databaseVersion", pp(ReadDatabaseVersion(db))},
|
||||
{"headBlockHash", fmt.Sprintf("%v", ReadHeadBlockHash(db))},
|
||||
|
|
@ -582,5 +605,78 @@ func ReadChainMetadata(db ethdb.KeyValueStore) [][]string {
|
|||
if b := ReadSkeletonSyncStatus(db); b != nil {
|
||||
data = append(data, []string{"SkeletonSyncStatus", string(b)})
|
||||
}
|
||||
if fmr, ok, _ := ReadFilterMapsRange(db); ok {
|
||||
data = append(data, []string{"filterMapsRange", fmt.Sprintf("%+v", fmr)})
|
||||
}
|
||||
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()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -128,29 +128,22 @@ var (
|
|||
configPrefix = []byte("ethereum-config-") // config 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-")
|
||||
|
||||
BestUpdateKey = []byte("update-") // bigEndian64(syncPeriod) -> RLP(types.LightClientUpdate) (nextCommittee only referenced by root hash)
|
||||
FixedCommitteeRootKey = []byte("fixedRoot-") // bigEndian64(syncPeriod) -> committee root hash
|
||||
SyncCommitteeKey = []byte("committee-") // bigEndian64(syncPeriod) -> serialized committee
|
||||
|
||||
// new log index
|
||||
filterMapsPrefix = "fm-"
|
||||
filterMapsRangeKey = []byte(filterMapsPrefix + "R")
|
||||
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)
|
||||
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)
|
||||
preimageHitsCounter = metrics.NewRegisteredCounter("db/preimage/hits", nil)
|
||||
preimageMissCounter = metrics.NewRegisteredCounter("db/preimage/miss", nil)
|
||||
|
|
|
|||
|
|
@ -655,7 +655,7 @@ func testGenerateWithManyExtraAccounts(t *testing.T, scheme string) {
|
|||
for i := 0; i < 1000; i++ {
|
||||
acc := &types.StateAccount{Balance: uint256.NewInt(uint64(i)), Root: types.EmptyRootHash, CodeHash: types.EmptyCodeHash.Bytes()}
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -329,27 +329,27 @@ func TestAccountIteratorTraversalValues(t *testing.T) {
|
|||
h = make(map[common.Hash][]byte)
|
||||
)
|
||||
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 {
|
||||
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 {
|
||||
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 {
|
||||
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 {
|
||||
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 {
|
||||
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 {
|
||||
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 {
|
||||
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
|
||||
|
|
@ -428,27 +428,27 @@ func TestStorageIteratorTraversalValues(t *testing.T) {
|
|||
h = make(map[common.Hash][]byte)
|
||||
)
|
||||
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 {
|
||||
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 {
|
||||
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 {
|
||||
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 {
|
||||
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 {
|
||||
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 {
|
||||
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 {
|
||||
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
|
||||
|
|
|
|||
|
|
@ -600,7 +600,6 @@ func (s *StateDB) getStateObject(addr common.Address) *stateObject {
|
|||
// Insert into the live set
|
||||
obj := newObject(s, addr, acct)
|
||||
s.setStateObject(obj)
|
||||
s.AccountLoaded++
|
||||
return obj
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -87,8 +87,9 @@ type blobTxMeta struct {
|
|||
hash common.Hash // Transaction hash 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
|
||||
size uint32 // Byte size in the pool's persistent store
|
||||
id uint64 // Storage ID 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
|
||||
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
|
||||
// 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{
|
||||
hash: tx.Hash(),
|
||||
vhashes: tx.BlobHashes(),
|
||||
id: id,
|
||||
size: size,
|
||||
nonce: tx.Nonce(),
|
||||
costCap: uint256.MustFromBig(tx.Cost()),
|
||||
execTipCap: uint256.MustFromBig(tx.GasTipCap()),
|
||||
execFeeCap: uint256.MustFromBig(tx.GasFeeCap()),
|
||||
blobFeeCap: uint256.MustFromBig(tx.BlobGasFeeCap()),
|
||||
execGas: tx.Gas(),
|
||||
blobGas: tx.BlobGas(),
|
||||
hash: tx.Hash(),
|
||||
vhashes: tx.BlobHashes(),
|
||||
id: id,
|
||||
storageSize: storageSize,
|
||||
size: size,
|
||||
nonce: tx.Nonce(),
|
||||
costCap: uint256.MustFromBig(tx.Cost()),
|
||||
execTipCap: uint256.MustFromBig(tx.GasTipCap()),
|
||||
execFeeCap: uint256.MustFromBig(tx.GasFeeCap()),
|
||||
blobFeeCap: uint256.MustFromBig(tx.BlobGasFeeCap()),
|
||||
execGas: tx.Gas(),
|
||||
blobGas: tx.BlobGas(),
|
||||
}
|
||||
meta.basefeeJumps = dynamicFeeJumps(meta.execFeeCap)
|
||||
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")
|
||||
}
|
||||
|
||||
meta := newBlobTxMeta(id, size, tx)
|
||||
meta := newBlobTxMeta(id, tx.Size(), size, tx)
|
||||
if p.lookup.exists(meta.hash) {
|
||||
// This path is only possible after a crash, where deleted items are not
|
||||
// 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.lookup.track(meta)
|
||||
p.stored += uint64(meta.size)
|
||||
p.stored += uint64(meta.storageSize)
|
||||
return nil
|
||||
}
|
||||
|
||||
|
|
@ -539,7 +541,7 @@ func (p *BlobPool) recheck(addr common.Address, inclusions map[common.Hash]uint6
|
|||
ids = append(ids, txs[i].id)
|
||||
nonces = append(nonces, txs[i].nonce)
|
||||
|
||||
p.stored -= uint64(txs[i].size)
|
||||
p.stored -= uint64(txs[i].storageSize)
|
||||
p.lookup.untrack(txs[i])
|
||||
|
||||
// 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)
|
||||
|
||||
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])
|
||||
|
||||
// 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)
|
||||
|
||||
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])
|
||||
|
||||
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)
|
||||
|
||||
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])
|
||||
}
|
||||
txs = txs[:i]
|
||||
|
|
@ -696,7 +698,7 @@ func (p *BlobPool) recheck(addr common.Address, inclusions map[common.Hash]uint6
|
|||
nonces = append(nonces, last.nonce)
|
||||
|
||||
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)
|
||||
}
|
||||
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)
|
||||
|
||||
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.index[addr] = txs
|
||||
|
|
@ -1002,7 +1004,7 @@ func (p *BlobPool) reinject(addr common.Address, txhash common.Hash) error {
|
|||
}
|
||||
|
||||
// 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 err := p.reserve(addr, true); err != nil {
|
||||
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.lookup.track(meta)
|
||||
p.stored += uint64(meta.size)
|
||||
p.stored += uint64(meta.storageSize)
|
||||
return nil
|
||||
}
|
||||
|
||||
|
|
@ -1041,7 +1043,7 @@ func (p *BlobPool) SetGasTip(tip *big.Int) {
|
|||
nonces = []uint64{tx.nonce}
|
||||
)
|
||||
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)
|
||||
txs[i] = nil
|
||||
|
||||
|
|
@ -1051,7 +1053,7 @@ func (p *BlobPool) SetGasTip(tip *big.Int) {
|
|||
nonces = append(nonces, tx.nonce)
|
||||
|
||||
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)
|
||||
txs[i+1+j] = nil
|
||||
}
|
||||
|
|
@ -1236,6 +1238,25 @@ func (p *BlobPool) GetRLP(hash common.Hash) []byte {
|
|||
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.
|
||||
// This is a utility method for the engine API, enabling consensus clients to
|
||||
// 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 {
|
||||
return err
|
||||
}
|
||||
meta := newBlobTxMeta(id, p.store.Size(id), tx)
|
||||
meta := newBlobTxMeta(id, tx.Size(), p.store.Size(id), tx)
|
||||
|
||||
var (
|
||||
next = p.state.GetNonce(from)
|
||||
|
|
@ -1403,7 +1424,7 @@ func (p *BlobPool) add(tx *types.Transaction) (err error) {
|
|||
|
||||
p.lookup.untrack(prev)
|
||||
p.lookup.track(meta)
|
||||
p.stored += uint64(meta.size) - uint64(prev.size)
|
||||
p.stored += uint64(meta.storageSize) - uint64(prev.storageSize)
|
||||
} else {
|
||||
// Transaction extends previously scheduled ones
|
||||
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.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 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.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)
|
||||
|
||||
// Remove the transaction from the pool's eviction heap:
|
||||
|
|
|
|||
|
|
@ -376,7 +376,7 @@ func verifyPoolInternals(t *testing.T, pool *BlobPool) {
|
|||
var stored uint64
|
||||
for _, txs := range pool.index {
|
||||
for _, tx := range txs {
|
||||
stored += uint64(tx.size)
|
||||
stored += uint64(tx.storageSize)
|
||||
}
|
||||
}
|
||||
if pool.stored != stored {
|
||||
|
|
@ -1553,6 +1553,16 @@ func TestAdd(t *testing.T) {
|
|||
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)
|
||||
}
|
||||
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)
|
||||
|
|
|
|||
|
|
@ -146,7 +146,7 @@ func TestPriceHeapSorting(t *testing.T) {
|
|||
)
|
||||
index[addr] = []*blobTxMeta{{
|
||||
id: uint64(j),
|
||||
size: 128 * 1024,
|
||||
storageSize: 128 * 1024,
|
||||
nonce: 0,
|
||||
execTipCap: execTip,
|
||||
execFeeCap: execFee,
|
||||
|
|
@ -205,7 +205,7 @@ func benchmarkPriceHeapReinit(b *testing.B, datacap uint64) {
|
|||
)
|
||||
index[addr] = []*blobTxMeta{{
|
||||
id: uint64(i),
|
||||
size: 128 * 1024,
|
||||
storageSize: 128 * 1024,
|
||||
nonce: 0,
|
||||
execTipCap: execTip,
|
||||
execFeeCap: execFee,
|
||||
|
|
@ -281,7 +281,7 @@ func benchmarkPriceHeapOverflow(b *testing.B, datacap uint64) {
|
|||
)
|
||||
index[addr] = []*blobTxMeta{{
|
||||
id: uint64(i),
|
||||
size: 128 * 1024,
|
||||
storageSize: 128 * 1024,
|
||||
nonce: 0,
|
||||
execTipCap: execTip,
|
||||
execFeeCap: execFee,
|
||||
|
|
@ -312,7 +312,7 @@ func benchmarkPriceHeapOverflow(b *testing.B, datacap uint64) {
|
|||
)
|
||||
metas[i] = &blobTxMeta{
|
||||
id: uint64(int(blobs) + i),
|
||||
size: 128 * 1024,
|
||||
storageSize: 128 * 1024,
|
||||
nonce: 0,
|
||||
execTipCap: execTip,
|
||||
execFeeCap: execFee,
|
||||
|
|
|
|||
|
|
@ -20,18 +20,24 @@ import (
|
|||
"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,
|
||||
// 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 {
|
||||
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.
|
||||
func newLookup() *lookup {
|
||||
return &lookup{
|
||||
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.
|
||||
func (l *lookup) storeidOfTx(txhash common.Hash) (uint64, bool) {
|
||||
id, ok := l.txIndex[txhash]
|
||||
return id, ok
|
||||
meta, ok := l.txIndex[txhash]
|
||||
if !ok {
|
||||
return 0, false
|
||||
}
|
||||
return meta.id, true
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
|
||||
// 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
|
||||
// hashes; and from transaction hashes to datastore storage item ids.
|
||||
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
|
||||
}
|
||||
// Map the transaction hash to the datastore id
|
||||
l.txIndex[tx.hash] = tx.id
|
||||
// Map the transaction hash to the datastore id and RLP-encoded transaction size
|
||||
l.txIndex[tx.hash] = &txMetadata{
|
||||
id: tx.id,
|
||||
size: tx.size,
|
||||
}
|
||||
}
|
||||
|
||||
// untrack removes a set of mappings from blob versioned hashes to transaction
|
||||
|
|
|
|||
|
|
@ -618,7 +618,7 @@ func (pool *LegacyPool) checkDelegationLimit(tx *types.Transaction) error {
|
|||
from, _ := types.Sender(pool.signer, tx) // validated
|
||||
|
||||
// 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
|
||||
}
|
||||
pending := pool.pending[from]
|
||||
|
|
@ -1035,6 +1035,19 @@ func (pool *LegacyPool) GetRLP(hash common.Hash) []byte {
|
|||
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
|
||||
// implement the txpool.SubPool interface.
|
||||
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.
|
||||
func numSlots(tx *types.Transaction) int {
|
||||
return int((tx.Size() + txSlotSize - 1) / txSlotSize)
|
||||
|
|
|
|||
218
core/txpool/locals/tx_tracker_test.go
Normal file
218
core/txpool/locals/tx_tracker_test.go
Normal 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))
|
||||
}
|
||||
}
|
||||
|
|
@ -86,6 +86,12 @@ type PendingFilter struct {
|
|||
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.
|
||||
// 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
|
||||
|
|
@ -127,6 +133,10 @@ type SubPool interface {
|
|||
// GetRLP returns a RLP-encoded transaction if it is contained in the pool.
|
||||
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.
|
||||
// This is a utility method for the engine API, enabling consensus clients to
|
||||
// retrieve blobs from the pools directly instead of the network.
|
||||
|
|
|
|||
|
|
@ -24,11 +24,13 @@ import (
|
|||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"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/crypto/kzg4844"
|
||||
"github.com/ethereum/go-ethereum/event"
|
||||
"github.com/ethereum/go-ethereum/log"
|
||||
"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.
|
||||
|
|
@ -53,11 +55,17 @@ var (
|
|||
// 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.
|
||||
type BlockChain interface {
|
||||
// Config retrieves the chain's fork configuration.
|
||||
Config() *params.ChainConfig
|
||||
|
||||
// CurrentBlock returns the current head of the chain.
|
||||
CurrentBlock() *types.Header
|
||||
|
||||
// SubscribeChainHeadEvent subscribes to new blocks being added to the chain.
|
||||
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
|
||||
|
|
@ -67,6 +75,11 @@ type BlockChain interface {
|
|||
// resource constraints.
|
||||
type TxPool struct {
|
||||
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
|
||||
reserveLock sync.Mutex // Lock protecting the account reservations
|
||||
|
|
@ -86,8 +99,21 @@ func New(gasTip uint64, chain BlockChain, subpools []SubPool) (*TxPool, error) {
|
|||
// during initialization.
|
||||
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{
|
||||
subpools: subpools,
|
||||
chain: chain,
|
||||
signer: types.LatestSigner(chain.Config()),
|
||||
state: statedb,
|
||||
reservations: make(map[common.Address]SubPool),
|
||||
quit: make(chan chan error),
|
||||
term: make(chan struct{}),
|
||||
|
|
@ -101,7 +127,7 @@ func New(gasTip uint64, chain BlockChain, subpools []SubPool) (*TxPool, error) {
|
|||
return nil, err
|
||||
}
|
||||
}
|
||||
go pool.loop(head, chain)
|
||||
go pool.loop(head)
|
||||
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
|
||||
// outside blockchain events as well as for various reporting and transaction
|
||||
// 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
|
||||
defer close(p.term)
|
||||
|
||||
// Subscribe to chain head events to trigger subpool resets
|
||||
var (
|
||||
newHeadCh = make(chan core.ChainHeadEvent)
|
||||
newHeadSub = chain.SubscribeChainHeadEvent(newHeadCh)
|
||||
newHeadSub = p.chain.SubscribeChainHeadEvent(newHeadCh)
|
||||
)
|
||||
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
|
||||
select {
|
||||
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
|
||||
go func(oldHead, newHead *types.Header) {
|
||||
for _, subpool := range p.subpools {
|
||||
|
|
@ -320,6 +354,17 @@ func (p *TxPool) GetRLP(hash common.Hash) []byte {
|
|||
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.
|
||||
// This is a utility method for the engine API, enabling consensus clients to
|
||||
// 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
|
||||
// rules, but does not check state-dependent validation such as sufficient balance.
|
||||
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 {
|
||||
if subpool.Filter(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...))
|
||||
}
|
||||
|
||||
// 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.
|
||||
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
|
||||
// (at max) a non-state nonce. To avoid stateful lookups, just return the
|
||||
// highest nonce for now.
|
||||
|
|
@ -433,6 +492,15 @@ func (p *TxPool) Nonce(addr common.Address) uint64 {
|
|||
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
|
||||
// number of queued (non-executable) transactions.
|
||||
func (p *TxPool) Stats() (int, int) {
|
||||
|
|
@ -508,6 +576,9 @@ func (p *TxPool) Sync() error {
|
|||
|
||||
// Clear removes all tracked txs from the subpools.
|
||||
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 {
|
||||
subpool.Clear()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -34,6 +34,7 @@ import (
|
|||
"github.com/ethereum/go-ethereum/core/txpool"
|
||||
"github.com/ethereum/go-ethereum/core/types"
|
||||
"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/tracers"
|
||||
"github.com/ethereum/go-ethereum/ethdb"
|
||||
|
|
@ -91,7 +92,13 @@ func (b *EthAPIBackend) HeaderByNumber(ctx context.Context, number rpc.BlockNumb
|
|||
}
|
||||
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) {
|
||||
|
|
@ -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.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, ðconfig.PrunedHistoryError{}
|
||||
}
|
||||
return block, nil
|
||||
}
|
||||
|
||||
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, ðconfig.PrunedHistoryError{}
|
||||
}
|
||||
return block, nil
|
||||
}
|
||||
|
||||
// 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{}) {
|
||||
return nil, errors.New("invalid arguments; expect hash and no special block numbers")
|
||||
}
|
||||
if body := b.eth.blockchain.GetBody(hash); body != nil {
|
||||
return body, nil
|
||||
body := b.eth.blockchain.GetBody(hash)
|
||||
if body == nil {
|
||||
if uint64(number) < b.eth.blockchain.HistoryPruningCutoff() {
|
||||
return nil, ðconfig.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) {
|
||||
|
|
@ -175,6 +202,9 @@ func (b *EthAPIBackend) BlockByNumberOrHash(ctx context.Context, blockNrOrHash r
|
|||
}
|
||||
block := b.eth.blockchain.GetBlock(hash, header.Number.Uint64())
|
||||
if block == nil {
|
||||
if header.Number.Uint64() < b.eth.blockchain.HistoryPruningCutoff() {
|
||||
return nil, ðconfig.PrunedHistoryError{}
|
||||
}
|
||||
return nil, errors.New("header found, but block body is missing")
|
||||
}
|
||||
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")
|
||||
}
|
||||
|
||||
func (b *EthAPIBackend) HistoryPruningCutoff() uint64 {
|
||||
return b.eth.blockchain.HistoryPruningCutoff()
|
||||
}
|
||||
|
||||
func (b *EthAPIBackend) GetReceipts(ctx context.Context, hash common.Hash) (types.Receipts, error) {
|
||||
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) {
|
||||
return b.eth.txPool.Nonce(addr), nil
|
||||
return b.eth.txPool.PoolNonce(addr), nil
|
||||
}
|
||||
|
||||
func (b *EthAPIBackend) Stats() (runnable int, blocked int) {
|
||||
|
|
|
|||
|
|
@ -239,9 +239,19 @@ func New(stack *node.Node, config *ethconfig.Config) (*Ethereum, error) {
|
|||
if err != nil {
|
||||
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())
|
||||
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{})
|
||||
|
||||
if config.BlobPool.Datadir != "" {
|
||||
|
|
|
|||
|
|
@ -275,7 +275,7 @@ func (s *skeleton) startup() {
|
|||
for {
|
||||
// If the sync cycle terminated or was terminated, propagate up when
|
||||
// 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)
|
||||
switch {
|
||||
case err == errSyncLinked:
|
||||
|
|
|
|||
|
|
@ -90,3 +90,9 @@ var HistoryPrunePoints = map[common.Hash]*HistoryPrunePoint{
|
|||
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 }
|
||||
|
|
|
|||
|
|
@ -29,6 +29,7 @@ import (
|
|||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/common/hexutil"
|
||||
"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/rpc"
|
||||
)
|
||||
|
|
@ -354,9 +355,13 @@ func (api *FilterAPI) GetLogs(ctx context.Context, crit FilterCriteria) ([]*type
|
|||
if crit.ToBlock != nil {
|
||||
end = crit.ToBlock.Int64()
|
||||
}
|
||||
// Block numbers below 0 are special cases.
|
||||
if begin > 0 && end > 0 && begin > end {
|
||||
return nil, errInvalidBlockRange
|
||||
}
|
||||
if begin > 0 && begin < int64(api.events.backend.HistoryPruningCutoff()) {
|
||||
return nil, ðconfig.PrunedHistoryError{}
|
||||
}
|
||||
// Construct the range filter
|
||||
filter = api.sys.NewRangeFilter(begin, end, crit.Addresses, crit.Topics)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -27,6 +27,7 @@ import (
|
|||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/core/filtermaps"
|
||||
"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/rpc"
|
||||
)
|
||||
|
|
@ -86,6 +87,9 @@ func (f *Filter) Logs(ctx context.Context) ([]*types.Log, error) {
|
|||
if header == nil {
|
||||
return nil, errors.New("unknown block")
|
||||
}
|
||||
if header.Number.Uint64() < f.sys.backend.HistoryPruningCutoff() {
|
||||
return nil, ðconfig.PrunedHistoryError{}
|
||||
}
|
||||
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 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
|
||||
|
|
|
|||
|
|
@ -31,6 +31,7 @@ import (
|
|||
"github.com/ethereum/go-ethereum/core"
|
||||
"github.com/ethereum/go-ethereum/core/filtermaps"
|
||||
"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/event"
|
||||
"github.com/ethereum/go-ethereum/log"
|
||||
|
|
@ -64,6 +65,7 @@ type Backend interface {
|
|||
|
||||
CurrentHeader() *types.Header
|
||||
ChainConfig() *params.ChainConfig
|
||||
HistoryPruningCutoff() uint64
|
||||
SubscribeNewTxsEvent(chan<- core.NewTxsEvent) event.Subscription
|
||||
SubscribeChainEvent(ch chan<- core.ChainEvent) 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
|
||||
}
|
||||
|
||||
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, ðconfig.PrunedHistoryError{}
|
||||
}
|
||||
|
||||
// only interested in new mined logs
|
||||
if from == rpc.LatestBlockNumber && to == rpc.LatestBlockNumber {
|
||||
return es.subscribeLogs(crit, logs), nil
|
||||
|
|
|
|||
|
|
@ -181,6 +181,10 @@ func (b *testBackend) setPending(block *types.Block, receipts types.Receipts) {
|
|||
b.pendingReceipts = receipts
|
||||
}
|
||||
|
||||
func (b *testBackend) HistoryPruningCutoff() uint64 {
|
||||
return 0
|
||||
}
|
||||
|
||||
func newTestFilterSystem(db ethdb.Database, cfg Config) (*testBackend, *FilterSystem) {
|
||||
backend := &testBackend{db: db}
|
||||
sys := NewFilterSystem(backend, cfg)
|
||||
|
|
|
|||
|
|
@ -223,7 +223,9 @@ func run(ctx context.Context, call *core.Message, opts *Options) (*core.Executio
|
|||
dirtyState = opts.State.Copy()
|
||||
)
|
||||
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
|
||||
// invariants (basefee < feecap).
|
||||
|
|
|
|||
|
|
@ -71,6 +71,10 @@ type txPool interface {
|
|||
// with given tx hash.
|
||||
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(txs []*types.Transaction, sync bool) []error
|
||||
|
||||
|
|
@ -403,7 +407,7 @@ func (h *handler) unregisterPeer(id string) {
|
|||
// Abort if the peer does not exist
|
||||
peer := h.peers.peer(id)
|
||||
if peer == nil {
|
||||
logger.Error("Ethereum peer removal failed", "err", errPeerNotRegistered)
|
||||
logger.Warn("Ethereum peer removal failed", "err", errPeerNotRegistered)
|
||||
return
|
||||
}
|
||||
// Remove the `eth` peer if it exists
|
||||
|
|
|
|||
|
|
@ -93,6 +93,22 @@ func (p *testTxPool) GetRLP(hash common.Hash) []byte {
|
|||
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
|
||||
// listeners if the addition channel is non nil
|
||||
func (p *testTxPool) Add(txs []*types.Transaction, sync bool) []error {
|
||||
|
|
|
|||
|
|
@ -116,10 +116,10 @@ func (p *Peer) announceTransactions() {
|
|||
size common.StorageSize
|
||||
)
|
||||
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])
|
||||
pendingTypes = append(pendingTypes, tx.Type())
|
||||
pendingSizes = append(pendingSizes, uint32(tx.Size()))
|
||||
pendingTypes = append(pendingTypes, meta.Type)
|
||||
pendingSizes = append(pendingSizes, uint32(meta.Size))
|
||||
size += common.HashLength
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -22,6 +22,7 @@ import (
|
|||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"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/metrics"
|
||||
"github.com/ethereum/go-ethereum/p2p"
|
||||
|
|
@ -90,6 +91,10 @@ type TxPool interface {
|
|||
// GetRLP retrieves the RLP-encoded transaction from the local txpool with
|
||||
// the given hash.
|
||||
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`.
|
||||
|
|
|
|||
|
|
@ -950,7 +950,9 @@ func (api *API) TraceCall(ctx context.Context, args ethapi.TransactionArgs, bloc
|
|||
vmctx := core.NewEVMBlockContext(block.Header(), api.chainContext(ctx), nil)
|
||||
// Apply the customization rules if required.
|
||||
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)
|
||||
|
||||
precompiles = vm.ActivePrecompiledContracts(rules)
|
||||
|
|
|
|||
|
|
@ -103,16 +103,10 @@ type AccessListTracer struct {
|
|||
// NewAccessListTracer creates a new tracer that can generate AccessLists.
|
||||
// An optional AccessList can be specified to occupy slots and addresses in
|
||||
// the resulting accesslist.
|
||||
func NewAccessListTracer(acl types.AccessList, from, to common.Address, precompiles []common.Address) *AccessListTracer {
|
||||
excl := map[common.Address]struct{}{
|
||||
from: {}, to: {},
|
||||
}
|
||||
for _, addr := range precompiles {
|
||||
excl[addr] = struct{}{}
|
||||
}
|
||||
func NewAccessListTracer(acl types.AccessList, addressesToExclude map[common.Address]struct{}) *AccessListTracer {
|
||||
list := newAccessList()
|
||||
for _, al := range acl {
|
||||
if _, ok := excl[al.Address]; !ok {
|
||||
if _, ok := addressesToExclude[al.Address]; !ok {
|
||||
list.addAddress(al.Address)
|
||||
}
|
||||
for _, slot := range al.StorageKeys {
|
||||
|
|
@ -120,7 +114,7 @@ func NewAccessListTracer(acl types.AccessList, from, to common.Address, precompi
|
|||
}
|
||||
}
|
||||
return &AccessListTracer{
|
||||
excl: excl,
|
||||
excl: addressesToExclude,
|
||||
list: list,
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -17,7 +17,10 @@
|
|||
// Package ethdb defines the interfaces for an Ethereum data store.
|
||||
package ethdb
|
||||
|
||||
import "io"
|
||||
import (
|
||||
"errors"
|
||||
"io"
|
||||
)
|
||||
|
||||
// KeyValueReader wraps the Has and Get method of a backing data store.
|
||||
type KeyValueReader interface {
|
||||
|
|
@ -37,10 +40,14 @@ type KeyValueWriter interface {
|
|||
Delete(key []byte) error
|
||||
}
|
||||
|
||||
var ErrTooManyKeys = errors.New("too many keys in deleted range")
|
||||
|
||||
// KeyValueRangeDeleter wraps the DeleteRange method of a backing data store.
|
||||
type KeyValueRangeDeleter interface {
|
||||
// DeleteRange deletes all of the keys (and values) in the range [start,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
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -207,8 +207,6 @@ func (db *Database) Delete(key []byte) error {
|
|||
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)
|
||||
// (inclusive on start, exclusive on end).
|
||||
// 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 {
|
||||
return err
|
||||
}
|
||||
return ErrTooManyKeys
|
||||
return ethdb.ErrTooManyKeys
|
||||
}
|
||||
if err := batch.Delete(it.Key()); err != nil {
|
||||
return err
|
||||
|
|
|
|||
|
|
@ -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
|
||||
func (api *BlockChainAPI) GetUncleCountByBlockNumber(ctx context.Context, blockNr rpc.BlockNumber) *hexutil.Uint {
|
||||
if block, _ := api.b.BlockByNumber(ctx, blockNr); block != nil {
|
||||
func (api *BlockChainAPI) GetUncleCountByBlockNumber(ctx context.Context, blockNr rpc.BlockNumber) (*hexutil.Uint, error) {
|
||||
block, err := api.b.BlockByNumber(ctx, blockNr)
|
||||
if block != nil {
|
||||
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
|
||||
func (api *BlockChainAPI) GetUncleCountByBlockHash(ctx context.Context, blockHash common.Hash) *hexutil.Uint {
|
||||
if block, _ := api.b.BlockByHash(ctx, blockHash); block != nil {
|
||||
func (api *BlockChainAPI) GetUncleCountByBlockHash(ctx context.Context, blockHash common.Hash) (*hexutil.Uint, error) {
|
||||
block, err := api.b.BlockByHash(ctx, blockHash)
|
||||
if block != nil {
|
||||
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.
|
||||
|
|
@ -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) {
|
||||
block, err := api.b.BlockByNumberOrHash(ctx, blockNrOrHash)
|
||||
if block == nil || err != nil {
|
||||
// When the block doesn't exist, the RPC method should return JSON null
|
||||
// as per specification.
|
||||
return nil, nil
|
||||
return nil, err
|
||||
}
|
||||
receipts, err := api.b.GetReceipts(ctx, block.Hash())
|
||||
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) {
|
||||
blockCtx := core.NewEVMBlockContext(header, NewChainContext(ctx, b), 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)
|
||||
precompiles := vm.ActivePrecompiledContracts(rules)
|
||||
|
|
@ -1115,12 +1117,13 @@ type accessListResult struct {
|
|||
|
||||
// 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.
|
||||
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)
|
||||
if blockNrOrHash != nil {
|
||||
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 {
|
||||
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.
|
||||
// If the accesslist creation fails an error 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
|
||||
db, header, err := b.StateAndHeaderByNumberOrHash(ctx, blockNrOrHash)
|
||||
if db == nil || err != nil {
|
||||
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
|
||||
if err = args.setFeeDefaults(ctx, b, header); err != nil {
|
||||
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
|
||||
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
|
||||
prevTracer := logger.NewAccessListTracer(nil, args.from(), to, precompiles)
|
||||
prevTracer := logger.NewAccessListTracer(nil, addressesToExclude)
|
||||
if args.AccessList != nil {
|
||||
prevTracer = logger.NewAccessListTracer(*args.AccessList, args.from(), to, precompiles)
|
||||
prevTracer = logger.NewAccessListTracer(*args.AccessList, addressesToExclude)
|
||||
}
|
||||
for {
|
||||
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)
|
||||
|
||||
// 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}
|
||||
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.
|
||||
func (api *TransactionAPI) GetBlockTransactionCountByNumber(ctx context.Context, blockNr rpc.BlockNumber) *hexutil.Uint {
|
||||
if block, _ := api.b.BlockByNumber(ctx, blockNr); block != nil {
|
||||
func (api *TransactionAPI) GetBlockTransactionCountByNumber(ctx context.Context, blockNr rpc.BlockNumber) (*hexutil.Uint, error) {
|
||||
block, err := api.b.BlockByNumber(ctx, blockNr)
|
||||
if block != nil {
|
||||
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.
|
||||
func (api *TransactionAPI) GetBlockTransactionCountByHash(ctx context.Context, blockHash common.Hash) *hexutil.Uint {
|
||||
if block, _ := api.b.BlockByHash(ctx, blockHash); block != nil {
|
||||
func (api *TransactionAPI) GetBlockTransactionCountByHash(ctx context.Context, blockHash common.Hash) (*hexutil.Uint, error) {
|
||||
block, err := api.b.BlockByHash(ctx, blockHash)
|
||||
if block != nil {
|
||||
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.
|
||||
func (api *TransactionAPI) GetTransactionByBlockNumberAndIndex(ctx context.Context, blockNr rpc.BlockNumber, index hexutil.Uint) *RPCTransaction {
|
||||
if block, _ := api.b.BlockByNumber(ctx, blockNr); block != nil {
|
||||
return newRPCTransactionFromBlockIndex(block, uint64(index), api.b.ChainConfig())
|
||||
func (api *TransactionAPI) GetTransactionByBlockNumberAndIndex(ctx context.Context, blockNr rpc.BlockNumber, index hexutil.Uint) (*RPCTransaction, error) {
|
||||
block, err := api.b.BlockByNumber(ctx, blockNr)
|
||||
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.
|
||||
func (api *TransactionAPI) GetTransactionByBlockHashAndIndex(ctx context.Context, blockHash common.Hash, index hexutil.Uint) *RPCTransaction {
|
||||
if block, _ := api.b.BlockByHash(ctx, blockHash); block != nil {
|
||||
return newRPCTransactionFromBlockIndex(block, uint64(index), api.b.ChainConfig())
|
||||
func (api *TransactionAPI) GetTransactionByBlockHashAndIndex(ctx context.Context, blockHash common.Hash, index hexutil.Uint) (*RPCTransaction, error) {
|
||||
block, err := api.b.BlockByHash(ctx, blockHash)
|
||||
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.
|
||||
|
|
|
|||
|
|
@ -520,8 +520,12 @@ func (b testBackend) BlockByNumber(ctx context.Context, number rpc.BlockNumber)
|
|||
if number == rpc.PendingBlockNumber {
|
||||
return b.pending, nil
|
||||
}
|
||||
if number == rpc.EarliestBlockNumber {
|
||||
number = 0
|
||||
}
|
||||
return b.chain.GetBlockByNumber(uint64(number)), nil
|
||||
}
|
||||
|
||||
func (b testBackend) BlockByHash(ctx context.Context, hash common.Hash) (*types.Block, error) {
|
||||
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 {
|
||||
panic("implement me")
|
||||
}
|
||||
|
||||
func (b testBackend) HistoryPruningCutoff() uint64 { return b.chain.HistoryPruningCutoff() }
|
||||
|
||||
func TestEstimateGas(t *testing.T) {
|
||||
t.Parallel()
|
||||
// Initialize test accounts
|
||||
|
|
@ -1134,6 +1141,24 @@ func TestCall(t *testing.T) {
|
|||
},
|
||||
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 {
|
||||
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 {
|
||||
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)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -85,6 +85,7 @@ type Backend interface {
|
|||
|
||||
ChainConfig() *params.ChainConfig
|
||||
Engine() consensus.Engine
|
||||
HistoryPruningCutoff() uint64
|
||||
|
||||
// This is copied from filters.Backend
|
||||
// eth/filters needs to be initialized from this backend type, so methods needed by
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@
|
|||
package override
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"math/big"
|
||||
|
||||
|
|
@ -128,12 +129,20 @@ type BlockOverrides struct {
|
|||
PrevRandao *common.Hash
|
||||
BaseFeePerGas *hexutil.Big
|
||||
BlobBaseFee *hexutil.Big
|
||||
BeaconRoot *common.Hash
|
||||
Withdrawals *types.Withdrawals
|
||||
}
|
||||
|
||||
// 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 {
|
||||
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 {
|
||||
blockCtx.BlockNumber = o.Number.ToInt()
|
||||
|
|
@ -159,6 +168,7 @@ func (o *BlockOverrides) Apply(blockCtx *vm.BlockContext) {
|
|||
if o.BlobBaseFee != nil {
|
||||
blockCtx.BlobBaseFee = o.BlobBaseFee.ToInt()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// MakeHeader returns a new header object with the overridden
|
||||
|
|
|
|||
|
|
@ -36,7 +36,6 @@ import (
|
|||
"github.com/ethereum/go-ethereum/internal/ethapi/override"
|
||||
"github.com/ethereum/go-ethereum/params"
|
||||
"github.com/ethereum/go-ethereum/rpc"
|
||||
"github.com/ethereum/go-ethereum/trie"
|
||||
)
|
||||
|
||||
const (
|
||||
|
|
@ -95,6 +94,47 @@ type simOpts struct {
|
|||
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.
|
||||
// it is not safe for concurrent use.
|
||||
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) {
|
||||
core.ProcessParentBlockHash(header.ParentHash, evm)
|
||||
}
|
||||
if header.ParentBeaconRoot != nil {
|
||||
core.ProcessBeaconBlockRoot(*header.ParentBeaconRoot, evm)
|
||||
}
|
||||
var allLogs []*types.Log
|
||||
for i, call := range block.Calls {
|
||||
if err := ctx.Err(); err != nil {
|
||||
|
|
@ -258,6 +301,10 @@ func (sim *simulator) processBlock(ctx context.Context, block *simBlock, header,
|
|||
}
|
||||
callResults[i] = callRes
|
||||
}
|
||||
header.GasUsed = gasUsed
|
||||
if sim.chainConfig.IsCancun(header.Number, header.Time) {
|
||||
header.BlobGasUsed = &blobGasUsed
|
||||
}
|
||||
var requests [][]byte
|
||||
// Process EIP-7685 requests
|
||||
if sim.chainConfig.IsPrague(header.Number, header.Time) {
|
||||
|
|
@ -271,20 +318,16 @@ func (sim *simulator) processBlock(ctx context.Context, block *simBlock, header,
|
|||
// EIP-7251
|
||||
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 {
|
||||
reqHash := types.CalcRequestsHash(requests)
|
||||
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())
|
||||
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))
|
||||
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)
|
||||
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)}
|
||||
|
|
@ -360,7 +406,13 @@ func (sim *simulator) sanitizeChain(blocks []simBlock) ([]simBlock, error) {
|
|||
for i := uint64(0); i < gap.Uint64(); i++ {
|
||||
n := new(big.Int).Add(prevNumber, big.NewInt(int64(i+1)))
|
||||
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
|
||||
res = append(res, b)
|
||||
}
|
||||
|
|
@ -405,6 +457,9 @@ func (sim *simulator) makeHeaders(blocks []simBlock) ([]*types.Header, error) {
|
|||
var parentBeaconRoot *common.Hash
|
||||
if sim.chainConfig.IsCancun(overrides.Number.ToInt(), (uint64)(*overrides.Time)) {
|
||||
parentBeaconRoot = &common.Hash{}
|
||||
if overrides.BeaconRoot != nil {
|
||||
parentBeaconRoot = overrides.BeaconRoot
|
||||
}
|
||||
}
|
||||
header = overrides.MakeHeader(&types.Header{
|
||||
UncleHash: types.EmptyUncleHash,
|
||||
|
|
|
|||
|
|
@ -402,3 +402,5 @@ func (b *backendMock) SubscribeRemovedLogsEvent(ch chan<- core.RemovedLogsEvent)
|
|||
func (b *backendMock) Engine() consensus.Engine { return nil }
|
||||
|
||||
func (b *backendMock) NewMatcherBackend() filtermaps.MatcherBackend { return nil }
|
||||
|
||||
func (b *backendMock) HistoryPruningCutoff() uint64 { return 0 }
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@
|
|||
// 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
|
||||
// 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
|
||||
// modifying this file, please port any modification over into the developer's
|
||||
// guide wiki pages too!
|
||||
|
|
|
|||
|
|
@ -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
|
||||
// most 200ms or so. If the handler takes longer than that, the remote end may time out
|
||||
// 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 {
|
||||
transport *UDPv5
|
||||
|
|
@ -72,13 +72,19 @@ func (t *talkSystem) register(protocol string, handler TalkRequestHandler) {
|
|||
|
||||
// handleRequest handles a talk request.
|
||||
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()
|
||||
handler, ok := t.handlers[req.Protocol]
|
||||
t.mutex.Unlock()
|
||||
|
||||
if !ok {
|
||||
resp := &v5wire.TalkResponse{ReqID: req.ReqID}
|
||||
t.transport.sendResponse(id, addr, resp)
|
||||
t.transport.sendResponse(n.ID(), addr, resp)
|
||||
return
|
||||
}
|
||||
|
||||
|
|
@ -90,9 +96,9 @@ func (t *talkSystem) handleRequest(id enode.ID, addr netip.AddrPort, req *v5wire
|
|||
go func() {
|
||||
defer func() { t.slots <- struct{}{} }()
|
||||
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}
|
||||
t.transport.sendFromAnotherThread(id, addr, resp)
|
||||
t.transport.sendFromAnotherThread(n.ID(), addr, resp)
|
||||
}()
|
||||
case <-timeout.C:
|
||||
// Couldn't get it in time, drop the request.
|
||||
|
|
|
|||
|
|
@ -64,6 +64,9 @@ type codecV5 interface {
|
|||
// 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.
|
||||
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.
|
||||
|
|
|
|||
|
|
@ -181,29 +181,35 @@ func TestUDPv5_handshakeRepeatChallenge(t *testing.T) {
|
|||
nonce1 := v5wire.Nonce{1}
|
||||
nonce2 := v5wire.Nonce{2}
|
||||
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()
|
||||
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.
|
||||
test.packetIn(&v5wire.Unknown{Nonce: nonce1})
|
||||
test.waitPacketOut(func(p *v5wire.Whoareyou, addr netip.AddrPort, _ v5wire.Nonce) {
|
||||
check(p, nonce1)
|
||||
test.waitPacketOut(func(p *v5wire.Whoareyou, addr netip.AddrPort, authTag v5wire.Nonce) {
|
||||
check(p, authTag, nonce1)
|
||||
})
|
||||
|
||||
// Second unknown packet. Here we expect the response to reference the
|
||||
// first unknown packet.
|
||||
test.packetIn(&v5wire.Unknown{Nonce: nonce2})
|
||||
test.waitPacketOut(func(p *v5wire.Whoareyou, addr netip.AddrPort, _ v5wire.Nonce) {
|
||||
check(p, nonce1)
|
||||
test.waitPacketOut(func(p *v5wire.Whoareyou, addr netip.AddrPort, authTag v5wire.Nonce) {
|
||||
check(p, authTag, nonce1)
|
||||
})
|
||||
// Third unknown packet. This should still return the first nonce.
|
||||
test.packetIn(&v5wire.Unknown{Nonce: nonce3})
|
||||
test.waitPacketOut(func(p *v5wire.Whoareyou, addr netip.AddrPort, _ v5wire.Nonce) {
|
||||
check(p, nonce1)
|
||||
test.waitPacketOut(func(p *v5wire.Whoareyou, addr netip.AddrPort, authTag v5wire.Nonce) {
|
||||
check(p, authTag, nonce1)
|
||||
})
|
||||
}
|
||||
|
||||
|
|
@ -486,7 +492,7 @@ func TestUDPv5_talkHandling(t *testing.T) {
|
|||
defer test.close()
|
||||
|
||||
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
|
||||
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) {
|
||||
// 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++
|
||||
var authTag v5wire.Nonce
|
||||
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 {
|
||||
// Store recently sent Whoareyou challenges.
|
||||
w.Nonce = authTag
|
||||
w.Encoded = frame
|
||||
if c.sentChallenges == nil {
|
||||
c.sentChallenges = make(map[enode.ID]*v5wire.Whoareyou)
|
||||
}
|
||||
c.sentChallenges[toID] = w
|
||||
}
|
||||
|
||||
penc, _ := rlp.EncodeToBytes(p)
|
||||
frame, err := rlp.EncodeToBytes(testCodecFrame{c.id, authTag, p.Kind(), penc})
|
||||
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
|
||||
}
|
||||
|
||||
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) {
|
||||
if err = rlp.DecodeBytes(input, &frame); err != nil {
|
||||
return frame, nil, fmt.Errorf("invalid frame: %v", err)
|
||||
|
|
|
|||
|
|
@ -189,6 +189,11 @@ func (c *Codec) Encode(id enode.ID, addr string, packet Packet, challenge *Whoar
|
|||
)
|
||||
switch {
|
||||
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))
|
||||
case challenge != nil:
|
||||
// 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.
|
||||
if challenge, ok := packet.(*Whoareyou); ok {
|
||||
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)
|
||||
} else if msgData == nil {
|
||||
return enc, head.Nonce, err
|
||||
}
|
||||
|
||||
if msgData == nil {
|
||||
headerData := c.buf.Bytes()
|
||||
msgData, err = c.encryptMessage(session, packet, &head, headerData)
|
||||
if err != nil {
|
||||
return nil, Nonce{}, err
|
||||
}
|
||||
}
|
||||
|
||||
enc, err := c.EncodeRaw(id, head, msgData)
|
||||
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
|
||||
c.sc.storeNewSession(toID, addr, session)
|
||||
c.sc.storeNewSession(toID, addr, session, challenge.Node)
|
||||
|
||||
// Encode the auth header.
|
||||
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.
|
||||
c.sc.storeNewSession(auth.h.SrcID, fromAddr, session)
|
||||
c.sc.storeNewSession(auth.h.SrcID, fromAddr, session, node)
|
||||
c.sc.deleteHandshake(auth.h.SrcID, fromAddr)
|
||||
return node, msg, nil
|
||||
}
|
||||
|
|
@ -644,6 +656,10 @@ func (c *Codec) decryptMessage(input, nonce, headerData, readKey []byte) (Packet
|
|||
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.
|
||||
// The packetLen here is the length remaining after the static header.
|
||||
func (h *StaticHeader) checkValid(packetLen int, protocolID [6]byte) error {
|
||||
|
|
|
|||
|
|
@ -166,7 +166,7 @@ func TestHandshake_rekey(t *testing.T) {
|
|||
readKey: []byte("BBBBBBBBBBBBBBBB"),
|
||||
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)
|
||||
findnode, authTag := net.nodeA.encode(t, net.nodeB, &Findnode{})
|
||||
|
|
@ -209,8 +209,8 @@ func TestHandshake_rekey2(t *testing.T) {
|
|||
readKey: []byte("CCCCCCCCCCCCCCCC"),
|
||||
writeKey: []byte("DDDDDDDDDDDDDDDD"),
|
||||
}
|
||||
net.nodeA.c.sc.storeNewSession(net.nodeB.id(), net.nodeB.addr(), initKeysA)
|
||||
net.nodeB.c.sc.storeNewSession(net.nodeA.id(), net.nodeA.addr(), initKeysB)
|
||||
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.nodeA.n())
|
||||
|
||||
// A -> B FINDNODE encrypted with initKeysA
|
||||
findnode, authTag := net.nodeA.encode(t, net.nodeB, &Findnode{Distances: []uint{3}})
|
||||
|
|
@ -362,8 +362,8 @@ func TestTestVectorsV5(t *testing.T) {
|
|||
ENRSeq: 2,
|
||||
},
|
||||
prep: func(net *handshakeTest) {
|
||||
net.nodeA.c.sc.storeNewSession(idB, addr, session)
|
||||
net.nodeB.c.sc.storeNewSession(idA, addr, session.keysFlipped())
|
||||
net.nodeA.c.sc.storeNewSession(idB, addr, session, net.nodeB.n())
|
||||
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},
|
||||
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.nodeB.c.sc.storeNewSession(net.nodeA.id(), net.nodeA.addr(), session.keysFlipped())
|
||||
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.nodeA.n())
|
||||
addrB := net.nodeA.addr()
|
||||
ping := &Ping{ReqID: []byte("reqid"), ENRSeq: 5}
|
||||
enc, _, err := net.nodeA.c.Encode(net.nodeB.id(), addrB, ping, nil)
|
||||
|
|
|
|||
|
|
@ -73,6 +73,9 @@ type (
|
|||
Node *enode.Node
|
||||
|
||||
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.
|
||||
|
|
|
|||
|
|
@ -54,11 +54,12 @@ type session struct {
|
|||
writeKey []byte
|
||||
readKey []byte
|
||||
nonceCounter uint32
|
||||
node *enode.Node
|
||||
}
|
||||
|
||||
// keysFlipped returns a copy of s with the read and write keys flipped.
|
||||
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 {
|
||||
|
|
@ -103,8 +104,19 @@ func (sc *SessionCache) readKey(id enode.ID, addr string) []byte {
|
|||
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.
|
||||
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)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -140,7 +140,7 @@ type ExtIP net.IP
|
|||
|
||||
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) 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.
|
||||
|
||||
|
|
|
|||
|
|
@ -71,7 +71,7 @@ func (n *pmp) DeleteMapping(protocol string, extport, intport int) (err 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 {
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
ip, err := n.internalAddress()
|
||||
if err != nil {
|
||||
return 0, nil // TODO: Shouldn't we return the error?
|
||||
return 0, err
|
||||
}
|
||||
protocol = strings.ToUpper(protocol)
|
||||
lifetimeS := uint32(lifetime / time.Second)
|
||||
|
|
@ -94,14 +94,15 @@ func (n *upnp) AddMapping(protocol string, extport, intport int, desc string, li
|
|||
if err == nil {
|
||||
return uint16(extport), nil
|
||||
}
|
||||
|
||||
return uint16(extport), n.withRateLimit(func() error {
|
||||
// Try addAnyPortMapping if mapping specified port didn't work.
|
||||
err = n.withRateLimit(func() error {
|
||||
p, err := n.addAnyPortMapping(protocol, extport, intport, ip, desc, lifetimeS)
|
||||
if err == nil {
|
||||
extport = int(p)
|
||||
}
|
||||
return err
|
||||
})
|
||||
return uint16(extport), err
|
||||
}
|
||||
|
||||
func (n *upnp) addAnyPortMapping(protocol string, extport, intport int, ip net.IP, desc string, lifetimeS uint32) (uint16, error) {
|
||||
|
|
|
|||
|
|
@ -63,11 +63,11 @@ type jsonWriter interface {
|
|||
type BlockNumber int64
|
||||
|
||||
const (
|
||||
EarliestBlockNumber = BlockNumber(-5)
|
||||
SafeBlockNumber = BlockNumber(-4)
|
||||
FinalizedBlockNumber = BlockNumber(-3)
|
||||
LatestBlockNumber = BlockNumber(-2)
|
||||
PendingBlockNumber = BlockNumber(-1)
|
||||
EarliestBlockNumber = BlockNumber(0)
|
||||
)
|
||||
|
||||
// UnmarshalJSON parses the given JSON fragment into a BlockNumber. It supports:
|
||||
|
|
|
|||
|
|
@ -282,7 +282,7 @@ func TestTypedDataArrayValidate(t *testing.T) {
|
|||
messageHash, tErr := td.HashStruct(td.PrimaryType, td.Message)
|
||||
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.NoError(t, td.validate(), "validation failed", tErr)
|
||||
|
|
|
|||
|
|
@ -369,7 +369,7 @@ func sign(typedData apitypes.TypedData) ([]byte, []byte, error) {
|
|||
if err != nil {
|
||||
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)
|
||||
return typedDataHash, sighash, nil
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1 +1 @@
|
|||
Subproject commit faf33b471465d3c6cdc3d04fbd690895f78d33f2
|
||||
Subproject commit 81862e4848585a438d64f911a19b3825f0f4cd95
|
||||
|
|
@ -57,32 +57,26 @@ func (c *committer) commit(path []byte, n node, parallel bool) node {
|
|||
// Commit children, then parent, and remove the dirty flag.
|
||||
switch cn := n.(type) {
|
||||
case *shortNode:
|
||||
// Commit child
|
||||
collapsed := cn.copy()
|
||||
|
||||
// If the child is fullNode, recursively commit,
|
||||
// otherwise it can only be hashNode or valueNode.
|
||||
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
|
||||
// modified nodeset.
|
||||
collapsed.Key = hexToCompact(cn.Key)
|
||||
hashedNode := c.store(path, collapsed)
|
||||
cn.Key = hexToCompact(cn.Key)
|
||||
hashedNode := c.store(path, cn)
|
||||
if hn, ok := hashedNode.(hashNode); ok {
|
||||
return hn
|
||||
}
|
||||
return collapsed
|
||||
return cn
|
||||
case *fullNode:
|
||||
hashedKids := c.commitChildren(path, cn, parallel)
|
||||
collapsed := cn.copy()
|
||||
collapsed.Children = hashedKids
|
||||
|
||||
hashedNode := c.store(path, collapsed)
|
||||
c.commitChildren(path, cn, parallel)
|
||||
hashedNode := c.store(path, cn)
|
||||
if hn, ok := hashedNode.(hashNode); ok {
|
||||
return hn
|
||||
}
|
||||
return collapsed
|
||||
return cn
|
||||
case hashNode:
|
||||
return cn
|
||||
default:
|
||||
|
|
@ -92,11 +86,10 @@ func (c *committer) commit(path []byte, n node, parallel bool) node {
|
|||
}
|
||||
|
||||
// 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 (
|
||||
wg sync.WaitGroup
|
||||
nodesMu sync.Mutex
|
||||
children [17]node
|
||||
wg sync.WaitGroup
|
||||
nodesMu sync.Mutex
|
||||
)
|
||||
for i := 0; i < 16; 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.
|
||||
// Note: it's impossible that the child in range [0, 15]
|
||||
// is a valueNode.
|
||||
if hn, ok := child.(hashNode); ok {
|
||||
children[i] = hn
|
||||
if _, ok := child.(hashNode); ok {
|
||||
continue
|
||||
}
|
||||
// Commit the child recursively and store the "hashed" value.
|
||||
// Note the returned node can be some embedded nodes, so it's
|
||||
// possible the type is not hashNode.
|
||||
if !parallel {
|
||||
children[i] = c.commit(append(path, byte(i)), child, false)
|
||||
n.Children[i] = c.commit(append(path, byte(i)), child, false)
|
||||
} else {
|
||||
wg.Add(1)
|
||||
go func(index int) {
|
||||
p := append(path, byte(index))
|
||||
childSet := trienode.NewNodeSet(c.nodes.Owner)
|
||||
childCommitter := newCommitter(childSet, c.tracer, c.collectLeaf)
|
||||
children[index] = childCommitter.commit(p, child, false)
|
||||
n.Children[index] = childCommitter.commit(p, child, false)
|
||||
nodesMu.Lock()
|
||||
c.nodes.MergeSet(childSet)
|
||||
nodesMu.Unlock()
|
||||
|
|
@ -132,11 +124,6 @@ func (c *committer) commitChildren(path []byte, n *fullNode, parallel bool) [17]
|
|||
if parallel {
|
||||
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
|
||||
|
|
|
|||
|
|
@ -53,62 +53,56 @@ func returnHasherToPool(h *hasher) {
|
|||
hasherPool.Put(h)
|
||||
}
|
||||
|
||||
// hash collapses a node down into a hash node, also returning a copy of the
|
||||
// original node initialized with the computed hash to replace the original one.
|
||||
func (h *hasher) hash(n node, force bool) (hashed node, cached node) {
|
||||
// hash collapses a node down into a hash node.
|
||||
func (h *hasher) hash(n node, force bool) node {
|
||||
// Return the cached hash if it's available
|
||||
if hash, _ := n.cache(); hash != nil {
|
||||
return hash, n
|
||||
return hash
|
||||
}
|
||||
// Trie not processed yet, walk the children
|
||||
switch n := n.(type) {
|
||||
case *shortNode:
|
||||
collapsed, cached := h.hashShortNodeChildren(n)
|
||||
collapsed := h.hashShortNodeChildren(n)
|
||||
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 {
|
||||
cached.flags.hash = hn
|
||||
n.flags.hash = hn
|
||||
} else {
|
||||
cached.flags.hash = nil
|
||||
n.flags.hash = nil
|
||||
}
|
||||
return hashed, cached
|
||||
return hashed
|
||||
case *fullNode:
|
||||
collapsed, cached := h.hashFullNodeChildren(n)
|
||||
hashed = h.fullnodeToHash(collapsed, force)
|
||||
collapsed := h.hashFullNodeChildren(n)
|
||||
hashed := h.fullnodeToHash(collapsed, force)
|
||||
if hn, ok := hashed.(hashNode); ok {
|
||||
cached.flags.hash = hn
|
||||
n.flags.hash = hn
|
||||
} else {
|
||||
cached.flags.hash = nil
|
||||
n.flags.hash = nil
|
||||
}
|
||||
return hashed, cached
|
||||
return hashed
|
||||
default:
|
||||
// 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
|
||||
// holds a live reference to the Key, and must not be modified.
|
||||
func (h *hasher) hashShortNodeChildren(n *shortNode) (collapsed, cached *shortNode) {
|
||||
// Hash the short node's child, caching the newly hashed subtree
|
||||
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)
|
||||
// hashShortNodeChildren returns a copy of the supplied shortNode, with its child
|
||||
// being replaced by either the hash or an embedded node if the child is small.
|
||||
func (h *hasher) hashShortNodeChildren(n *shortNode) *shortNode {
|
||||
var collapsed shortNode
|
||||
collapsed.Key = hexToCompact(n.Key)
|
||||
// Unless the child is a valuenode or hashnode, hash it
|
||||
switch n.Val.(type) {
|
||||
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) {
|
||||
// Hash the full node's children, caching the newly hashed subtrees
|
||||
cached = n.copy()
|
||||
collapsed = n.copy()
|
||||
// hashFullNodeChildren returns a copy of the supplied fullNode, with its child
|
||||
// being replaced by either the hash or an embedded node if the child is small.
|
||||
func (h *hasher) hashFullNodeChildren(n *fullNode) *fullNode {
|
||||
var children [17]node
|
||||
if h.parallel {
|
||||
var wg sync.WaitGroup
|
||||
wg.Add(16)
|
||||
|
|
@ -116,9 +110,9 @@ func (h *hasher) hashFullNodeChildren(n *fullNode) (collapsed *fullNode, cached
|
|||
go func(i int) {
|
||||
hasher := newHasher(false)
|
||||
if child := n.Children[i]; child != nil {
|
||||
collapsed.Children[i], cached.Children[i] = hasher.hash(child, false)
|
||||
children[i] = hasher.hash(child, false)
|
||||
} else {
|
||||
collapsed.Children[i] = nilValueNode
|
||||
children[i] = nilValueNode
|
||||
}
|
||||
returnHasherToPool(hasher)
|
||||
wg.Done()
|
||||
|
|
@ -128,19 +122,21 @@ func (h *hasher) hashFullNodeChildren(n *fullNode) (collapsed *fullNode, cached
|
|||
} else {
|
||||
for i := 0; i < 16; i++ {
|
||||
if child := n.Children[i]; child != nil {
|
||||
collapsed.Children[i], cached.Children[i] = h.hash(child, false)
|
||||
children[i] = h.hash(child, false)
|
||||
} 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
|
||||
// should have hex-type Key, which will be converted (without modification)
|
||||
// into compact form for RLP encoding.
|
||||
// If the rlp data is smaller than 32 bytes, `nil` is returned.
|
||||
// shortNodeToHash computes the hash of the given shortNode. The shortNode must
|
||||
// first be collapsed, with its key converted to compact form. If the RLP-encoded
|
||||
// node data is smaller than 32 bytes, the node itself is returned.
|
||||
func (h *hasher) shortnodeToHash(n *shortNode, force bool) node {
|
||||
n.encode(h.encbuf)
|
||||
enc := h.encodedBytes()
|
||||
|
|
@ -151,8 +147,8 @@ func (h *hasher) shortnodeToHash(n *shortNode, force bool) node {
|
|||
return h.hashData(enc)
|
||||
}
|
||||
|
||||
// fullnodeToHash is used to create a hashNode from a fullNode, (which
|
||||
// may contain nil values)
|
||||
// fullnodeToHash computes the hash of the given fullNode. If the RLP-encoded
|
||||
// node data is smaller than 32 bytes, the node itself is returned.
|
||||
func (h *hasher) fullnodeToHash(n *fullNode, force bool) node {
|
||||
n.encode(h.encbuf)
|
||||
enc := h.encodedBytes()
|
||||
|
|
@ -203,10 +199,10 @@ func (h *hasher) hashDataTo(dst, data []byte) {
|
|||
func (h *hasher) proofHash(original node) (collapsed, hashed node) {
|
||||
switch n := original.(type) {
|
||||
case *shortNode:
|
||||
sn, _ := h.hashShortNodeChildren(n)
|
||||
sn := h.hashShortNodeChildren(n)
|
||||
return sn, h.shortnodeToHash(sn, false)
|
||||
case *fullNode:
|
||||
fn, _ := h.hashFullNodeChildren(n)
|
||||
fn := h.hashFullNodeChildren(n)
|
||||
return fn, h.fullnodeToHash(fn, false)
|
||||
default:
|
||||
// Value and hash nodes don't have children, so they're left as were
|
||||
|
|
|
|||
14
trie/node.go
14
trie/node.go
|
|
@ -79,15 +79,19 @@ func (n *fullNode) EncodeRLP(w io.Writer) error {
|
|||
return eb.Flush()
|
||||
}
|
||||
|
||||
func (n *fullNode) copy() *fullNode { copy := *n; return © }
|
||||
func (n *shortNode) copy() *shortNode { copy := *n; return © }
|
||||
|
||||
// nodeFlag contains caching-related metadata about a node.
|
||||
type nodeFlag struct {
|
||||
hash hashNode // cached hash of the node (may be nil)
|
||||
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 *shortNode) cache() (hashNode, bool) { return n.flags.hash, n.flags.dirty }
|
||||
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)
|
||||
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
|
||||
case kind == rlp.String && len(val) == 0:
|
||||
// empty node
|
||||
|
|
|
|||
68
trie/trie.go
68
trie/trie.go
|
|
@ -29,11 +29,11 @@ import (
|
|||
"github.com/ethereum/go-ethereum/triedb/database"
|
||||
)
|
||||
|
||||
// Trie is a Merkle Patricia Trie. Use New to create a trie that sits on
|
||||
// top of a database. Whenever trie performs a commit operation, the generated
|
||||
// nodes will be gathered and returned in a set. Once the trie is committed,
|
||||
// it's not usable anymore. Callers have to re-create the trie with new root
|
||||
// based on the updated trie database.
|
||||
// Trie represents a Merkle Patricia Trie. Use New to create a trie that operates
|
||||
// on top of a node database. During a commit operation, the trie collects all
|
||||
// modified nodes into a set for return. After committing, the trie becomes
|
||||
// unusable, and callers must recreate it with the new root based on the updated
|
||||
// trie database.
|
||||
//
|
||||
// Trie is not safe for concurrent use.
|
||||
type Trie struct {
|
||||
|
|
@ -67,13 +67,13 @@ func (t *Trie) newFlag() nodeFlag {
|
|||
// Copy returns a copy of Trie.
|
||||
func (t *Trie) Copy() *Trie {
|
||||
return &Trie{
|
||||
root: t.root,
|
||||
root: copyNode(t.root),
|
||||
owner: t.owner,
|
||||
committed: t.committed,
|
||||
unhashed: t.unhashed,
|
||||
uncommitted: t.uncommitted,
|
||||
reader: t.reader,
|
||||
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))
|
||||
if err == nil && didResolve {
|
||||
n = n.copy()
|
||||
n.Val = newnode
|
||||
}
|
||||
return value, n, didResolve, err
|
||||
case *fullNode:
|
||||
value, newnode, didResolve, err = t.get(n.Children[key[pos]], key, pos+1)
|
||||
if err == nil && didResolve {
|
||||
n = n.copy()
|
||||
n.Children[key[pos]] = newnode
|
||||
}
|
||||
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))
|
||||
if err == nil && resolved > 0 {
|
||||
n = n.copy()
|
||||
n.Val = newnode
|
||||
}
|
||||
return item, n, resolved, err
|
||||
|
|
@ -265,7 +262,6 @@ func (t *Trie) getNode(origNode node, path []byte, pos int) (item []byte, newnod
|
|||
case *fullNode:
|
||||
item, newnode, resolved, err = t.getNode(n.Children[path[pos]], path, pos+1)
|
||||
if err == nil && resolved > 0 {
|
||||
n = n.copy()
|
||||
n.Children[path[pos]] = newnode
|
||||
}
|
||||
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 {
|
||||
return false, n, err
|
||||
}
|
||||
n = n.copy()
|
||||
n.flags = t.newFlag()
|
||||
n.Children[key[0]] = nn
|
||||
return true, n, nil
|
||||
|
|
@ -483,7 +478,6 @@ func (t *Trie) delete(n node, prefix, key []byte) (bool, node, error) {
|
|||
if !dirty || err != nil {
|
||||
return false, n, err
|
||||
}
|
||||
n = n.copy()
|
||||
n.flags = t.newFlag()
|
||||
n.Children[key[0]] = nn
|
||||
|
||||
|
|
@ -576,6 +570,36 @@ func concat(s1 []byte, s2 ...byte) []byte {
|
|||
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) {
|
||||
if n, ok := n.(hashNode); ok {
|
||||
return t.resolveAndTrack(n, prefix)
|
||||
|
|
@ -593,15 +617,16 @@ func (t *Trie) resolveAndTrack(n hashNode, prefix []byte) (node, error) {
|
|||
return nil, err
|
||||
}
|
||||
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
|
||||
// database and can be used even if the trie doesn't have one.
|
||||
func (t *Trie) Hash() common.Hash {
|
||||
hash, cached := t.hashRoot()
|
||||
t.root = cached
|
||||
return common.BytesToHash(hash.(hashNode))
|
||||
return common.BytesToHash(t.hashRoot().(hashNode))
|
||||
}
|
||||
|
||||
// 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
|
||||
func (t *Trie) hashRoot() (node, node) {
|
||||
func (t *Trie) hashRoot() node {
|
||||
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
|
||||
h := newHasher(t.unhashed >= 100)
|
||||
|
|
@ -662,8 +687,7 @@ func (t *Trie) hashRoot() (node, node) {
|
|||
returnHasherToPool(h)
|
||||
t.unhashed = 0
|
||||
}()
|
||||
hashed, cached := h.hash(t.root, true)
|
||||
return hashed, cached
|
||||
return h.hash(t.root, true)
|
||||
}
|
||||
|
||||
// Witness returns a set containing all trie nodes that have been accessed.
|
||||
|
|
|
|||
|
|
@ -1330,3 +1330,171 @@ func printSet(set *trienode.NodeSet) 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())
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -27,6 +27,8 @@ type NodeReader interface {
|
|||
// node path and the corresponding node hash. No error will be returned
|
||||
// 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
|
||||
// still be referenced by database.
|
||||
Node(owner common.Hash, path []byte, hash common.Hash) ([]byte, error)
|
||||
|
|
|
|||
|
|
@ -371,27 +371,27 @@ func TestAccountIteratorTraversalValues(t *testing.T) {
|
|||
h = make(map[common.Hash][]byte)
|
||||
)
|
||||
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 {
|
||||
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 {
|
||||
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 {
|
||||
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 {
|
||||
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 {
|
||||
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 {
|
||||
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 {
|
||||
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
|
||||
|
|
@ -480,27 +480,27 @@ func TestStorageIteratorTraversalValues(t *testing.T) {
|
|||
h = make(map[common.Hash][]byte)
|
||||
)
|
||||
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 {
|
||||
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 {
|
||||
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 {
|
||||
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 {
|
||||
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 {
|
||||
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 {
|
||||
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 {
|
||||
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
|
||||
|
|
|
|||
|
|
@ -36,8 +36,9 @@ import (
|
|||
// transition, typically corresponding to a block execution. It can also represent
|
||||
// the combined trie node set from several aggregated state transitions.
|
||||
type nodeSet struct {
|
||||
size uint64 // aggregated size of the trie node
|
||||
nodes map[common.Hash]map[string]*trienode.Node // node set, mapped by owner and path
|
||||
size uint64 // aggregated size of the trie node
|
||||
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.
|
||||
|
|
@ -46,7 +47,17 @@ func newNodeSet(nodes map[common.Hash]map[string]*trienode.Node) *nodeSet {
|
|||
if nodes == nil {
|
||||
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()
|
||||
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.
|
||||
func (s *nodeSet) computeSize() {
|
||||
var size uint64
|
||||
for owner, subset := range s.nodes {
|
||||
var prefix int
|
||||
if owner != (common.Hash{}) {
|
||||
prefix = common.HashLength // owner (32 bytes) for storage trie nodes
|
||||
}
|
||||
for path, n := range s.accountNodes {
|
||||
size += uint64(len(n.Blob) + len(path))
|
||||
}
|
||||
for _, subset := range s.storageNodes {
|
||||
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
|
||||
|
|
@ -79,15 +89,18 @@ func (s *nodeSet) updateSize(delta int64) {
|
|||
|
||||
// node retrieves the trie node with node path and its trie identifier.
|
||||
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 {
|
||||
return nil, false
|
||||
}
|
||||
n, ok := subset[string(path)]
|
||||
if !ok {
|
||||
return nil, false
|
||||
}
|
||||
return n, true
|
||||
return n, ok
|
||||
}
|
||||
|
||||
// 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
|
||||
overwrite counter // counter of nodes being overwritten
|
||||
)
|
||||
for owner, subset := range set.nodes {
|
||||
var prefix int
|
||||
if owner != (common.Hash{}) {
|
||||
prefix = common.HashLength
|
||||
|
||||
// Merge account nodes
|
||||
for path, n := range set.accountNodes {
|
||||
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 {
|
||||
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
|
||||
// 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
|
||||
// should still belong to the original layer, and any modifications to it
|
||||
// should be prevented.
|
||||
s.nodes[owner] = maps.Clone(subset)
|
||||
s.storageNodes[owner] = maps.Clone(subset)
|
||||
continue
|
||||
}
|
||||
for path, n := range subset {
|
||||
if orig, exist := current[path]; !exist {
|
||||
delta += int64(prefix + len(n.Blob) + len(path))
|
||||
delta += int64(common.HashLength + len(n.Blob) + len(path))
|
||||
} else {
|
||||
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
|
||||
}
|
||||
s.nodes[owner] = current
|
||||
s.storageNodes[owner] = current
|
||||
}
|
||||
overwrite.report(gcTrieNodeMeter, gcTrieNodeBytesMeter)
|
||||
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) {
|
||||
var delta int64
|
||||
for owner, subset := range nodes {
|
||||
current, ok := s.nodes[owner]
|
||||
if !ok {
|
||||
panic(fmt.Sprintf("non-existent subset (%x)", owner))
|
||||
}
|
||||
for path, n := range subset {
|
||||
orig, ok := current[path]
|
||||
if !ok {
|
||||
// There is a special case in merkle tree that one child is removed
|
||||
// from a fullNode which only has two children, and then a new child
|
||||
// with different position is immediately inserted into the fullNode.
|
||||
// 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))
|
||||
if owner == (common.Hash{}) {
|
||||
// Account trie nodes
|
||||
for path, n := range subset {
|
||||
orig, ok := s.accountNodes[path]
|
||||
if !ok {
|
||||
blob := rawdb.ReadAccountTrieNode(db, []byte(path))
|
||||
if bytes.Equal(blob, n.Blob) {
|
||||
continue
|
||||
}
|
||||
panic(fmt.Sprintf("non-existent account node (%v) blob: %v", path, crypto.Keccak256Hash(n.Blob).Hex()))
|
||||
}
|
||||
// Ignore the clean node in the case described above.
|
||||
if bytes.Equal(blob, n.Blob) {
|
||||
continue
|
||||
}
|
||||
panic(fmt.Sprintf("non-existent node (%x %v) blob: %v", owner, path, crypto.Keccak256Hash(n.Blob).Hex()))
|
||||
s.accountNodes[path] = n
|
||||
delta += int64(len(n.Blob)) - int64(len(orig.Blob))
|
||||
}
|
||||
} else {
|
||||
// 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)
|
||||
|
|
@ -184,8 +210,21 @@ type journalNodes struct {
|
|||
|
||||
// encode serializes the content of trie nodes into the provided writer.
|
||||
func (s *nodeSet) encode(w io.Writer) error {
|
||||
nodes := make([]journalNodes, 0, len(s.nodes))
|
||||
for owner, subset := range s.nodes {
|
||||
nodes := make([]journalNodes, 0, len(s.storageNodes)+1)
|
||||
|
||||
// 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}
|
||||
for path, node := range subset {
|
||||
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 {
|
||||
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 {
|
||||
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()
|
||||
if entry.Owner == (common.Hash{}) {
|
||||
// Account nodes
|
||||
for _, n := range entry.Nodes {
|
||||
if len(n.Blob) > 0 {
|
||||
s.accountNodes[string(n.Path)] = trienode.New(crypto.Keccak256Hash(n.Blob), n.Blob)
|
||||
} 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()
|
||||
return nil
|
||||
}
|
||||
|
||||
// write flushes nodes into the provided database batch as a whole.
|
||||
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.
|
||||
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
|
||||
}
|
||||
|
||||
// dbsize returns the approximate size of db write.
|
||||
func (s *nodeSet) dbsize() int {
|
||||
var m int
|
||||
for owner, nodes := range s.nodes {
|
||||
if owner == (common.Hash{}) {
|
||||
m += len(nodes) * len(rawdb.TrieNodeAccountPrefix) // database key prefix
|
||||
} else {
|
||||
m += len(nodes) * (len(rawdb.TrieNodeStoragePrefix)) // database key prefix
|
||||
}
|
||||
m += len(s.accountNodes) * len(rawdb.TrieNodeAccountPrefix) // database key prefix
|
||||
for _, nodes := range s.storageNodes {
|
||||
m += len(nodes) * (len(rawdb.TrieNodeStoragePrefix)) // database key prefix
|
||||
}
|
||||
return m + int(s.size)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -19,6 +19,6 @@ package version
|
|||
const (
|
||||
Major = 1 // Major 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
|
||||
)
|
||||
|
|
|
|||
Loading…
Reference in a new issue