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
|
.vscode
|
||||||
|
|
||||||
tests/spec-tests/
|
tests/spec-tests/
|
||||||
|
|
||||||
|
# binaries
|
||||||
|
cmd/abidump/abidump
|
||||||
|
cmd/abigen/abigen
|
||||||
|
cmd/blsync/blsync
|
||||||
|
cmd/clef/clef
|
||||||
|
cmd/devp2p/devp2p
|
||||||
|
cmd/era/era
|
||||||
|
cmd/ethkey/ethkey
|
||||||
|
cmd/evm/evm
|
||||||
|
cmd/geth/geth
|
||||||
|
cmd/rlpdump/rlpdump
|
||||||
|
cmd/workload/workload
|
||||||
23
.travis.yml
23
.travis.yml
|
|
@ -2,12 +2,6 @@ language: go
|
||||||
go_import_path: github.com/ethereum/go-ethereum
|
go_import_path: github.com/ethereum/go-ethereum
|
||||||
sudo: false
|
sudo: false
|
||||||
jobs:
|
jobs:
|
||||||
allow_failures:
|
|
||||||
- stage: build
|
|
||||||
os: osx
|
|
||||||
env:
|
|
||||||
- azure-osx
|
|
||||||
|
|
||||||
include:
|
include:
|
||||||
# This builder create and push the Docker images for all architectures
|
# This builder create and push the Docker images for all architectures
|
||||||
- stage: build
|
- stage: build
|
||||||
|
|
@ -62,23 +56,6 @@ jobs:
|
||||||
- go run build/ci.go install -dlgo -arch arm64 -cc aarch64-linux-gnu-gcc
|
- go run build/ci.go install -dlgo -arch arm64 -cc aarch64-linux-gnu-gcc
|
||||||
- go run build/ci.go archive -arch arm64 -type tar -signer LINUX_SIGNING_KEY -signify SIGNIFY_KEY -upload gethstore/builds
|
- go run build/ci.go archive -arch arm64 -type tar -signer LINUX_SIGNING_KEY -signify SIGNIFY_KEY -upload gethstore/builds
|
||||||
|
|
||||||
# This builder does the OSX Azure uploads
|
|
||||||
- stage: build
|
|
||||||
if: type = push
|
|
||||||
os: osx
|
|
||||||
osx_image: xcode14.2
|
|
||||||
go: 1.23.1 # See https://github.com/ethereum/go-ethereum/pull/30478
|
|
||||||
env:
|
|
||||||
- azure-osx
|
|
||||||
git:
|
|
||||||
submodules: false # avoid cloning ethereum/tests
|
|
||||||
script:
|
|
||||||
- ln -sf /Users/travis/gopath/bin/go1.23.1 /usr/local/bin/go # Work around travis go-setup bug
|
|
||||||
- go run build/ci.go install -dlgo
|
|
||||||
- go run build/ci.go archive -type tar -signer OSX_SIGNING_KEY -signify SIGNIFY_KEY -upload gethstore/builds
|
|
||||||
- go run build/ci.go install -dlgo -arch arm64
|
|
||||||
- go run build/ci.go archive -arch arm64 -type tar -signer OSX_SIGNING_KEY -signify SIGNIFY_KEY -upload gethstore/builds
|
|
||||||
|
|
||||||
# These builders run the tests
|
# These builders run the tests
|
||||||
- stage: build
|
- stage: build
|
||||||
if: type = push
|
if: type = push
|
||||||
|
|
|
||||||
|
|
@ -1806,7 +1806,9 @@ var bindTests = []struct {
|
||||||
[]string{"608060405234801561001057600080fd5b50610113806100206000396000f3fe6080604052348015600f57600080fd5b506004361060285760003560e01c806324ec1d3f14602d575b600080fd5b60336035565b005b7fb4b2ff75e30cb4317eaae16dd8a187dd89978df17565104caa6c2797caae27d460405180604001604052806001815260200160028152506040516078919060ba565b60405180910390a1565b6040820160008201516096600085018260ad565b50602082015160a7602085018260ad565b50505050565b60b48160d3565b82525050565b600060408201905060cd60008301846082565b92915050565b600081905091905056fea26469706673582212208823628796125bf9941ce4eda18da1be3cf2931b231708ab848e1bd7151c0c9a64736f6c63430008070033"},
|
[]string{"608060405234801561001057600080fd5b50610113806100206000396000f3fe6080604052348015600f57600080fd5b506004361060285760003560e01c806324ec1d3f14602d575b600080fd5b60336035565b005b7fb4b2ff75e30cb4317eaae16dd8a187dd89978df17565104caa6c2797caae27d460405180604001604052806001815260200160028152506040516078919060ba565b60405180910390a1565b6040820160008201516096600085018260ad565b50602082015160a7602085018260ad565b50505050565b60b48160d3565b82525050565b600060408201905060cd60008301846082565b92915050565b600081905091905056fea26469706673582212208823628796125bf9941ce4eda18da1be3cf2931b231708ab848e1bd7151c0c9a64736f6c63430008070033"},
|
||||||
[]string{`[{"anonymous":false,"inputs":[{"components":[{"internalType":"uint256","name":"a","type":"uint256"},{"internalType":"uint256","name":"b","type":"uint256"}],"indexed":false,"internalType":"struct Test.MyStruct","name":"s","type":"tuple"}],"name":"StructEvent","type":"event"},{"inputs":[],"name":"TestEvent","outputs":[],"stateMutability":"nonpayable","type":"function"}]`},
|
[]string{`[{"anonymous":false,"inputs":[{"components":[{"internalType":"uint256","name":"a","type":"uint256"},{"internalType":"uint256","name":"b","type":"uint256"}],"indexed":false,"internalType":"struct Test.MyStruct","name":"s","type":"tuple"}],"name":"StructEvent","type":"event"},{"inputs":[],"name":"TestEvent","outputs":[],"stateMutability":"nonpayable","type":"function"}]`},
|
||||||
`
|
`
|
||||||
|
"context"
|
||||||
"math/big"
|
"math/big"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/ethereum/go-ethereum/accounts/abi/bind"
|
"github.com/ethereum/go-ethereum/accounts/abi/bind"
|
||||||
"github.com/ethereum/go-ethereum/accounts/abi/bind/backends"
|
"github.com/ethereum/go-ethereum/accounts/abi/bind/backends"
|
||||||
|
|
@ -1828,12 +1830,23 @@ var bindTests = []struct {
|
||||||
}
|
}
|
||||||
sim.Commit()
|
sim.Commit()
|
||||||
|
|
||||||
_, err = d.TestEvent(user)
|
tx, err := d.TestEvent(user)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("Failed to call contract %v", err)
|
t.Fatalf("Failed to call contract %v", err)
|
||||||
}
|
}
|
||||||
sim.Commit()
|
sim.Commit()
|
||||||
|
|
||||||
|
// Wait for the transaction to be mined
|
||||||
|
ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)
|
||||||
|
defer cancel()
|
||||||
|
receipt, err := bind.WaitMined(ctx, sim, tx)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Failed to wait for tx to be mined: %v", err)
|
||||||
|
}
|
||||||
|
if receipt.Status != types.ReceiptStatusSuccessful {
|
||||||
|
t.Fatal("Transaction failed")
|
||||||
|
}
|
||||||
|
|
||||||
it, err := d.FilterStructEvent(nil)
|
it, err := d.FilterStructEvent(nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("Failed to filter contract event %v", err)
|
t.Fatalf("Failed to filter contract event %v", err)
|
||||||
|
|
|
||||||
|
|
@ -390,6 +390,7 @@ func (c *BoundContract) estimateGasLimit(opts *TransactOpts, contract *common.Ad
|
||||||
GasFeeCap: gasFeeCap,
|
GasFeeCap: gasFeeCap,
|
||||||
Value: value,
|
Value: value,
|
||||||
Data: input,
|
Data: input,
|
||||||
|
AccessList: opts.AccessList,
|
||||||
}
|
}
|
||||||
return c.transactor.EstimateGas(ensureContext(opts.Context), msg)
|
return c.transactor.EstimateGas(ensureContext(opts.Context), msg)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
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
|
package params
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
_ "embed"
|
||||||
|
|
||||||
"github.com/ethereum/go-ethereum/common"
|
"github.com/ethereum/go-ethereum/common"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
//go:embed checkpoint_mainnet.hex
|
||||||
|
var checkpointMainnet string
|
||||||
|
|
||||||
|
//go:embed checkpoint_sepolia.hex
|
||||||
|
var checkpointSepolia string
|
||||||
|
|
||||||
|
//go:embed checkpoint_holesky.hex
|
||||||
|
var checkpointHolesky string
|
||||||
|
|
||||||
var (
|
var (
|
||||||
MainnetLightConfig = (&ChainConfig{
|
MainnetLightConfig = (&ChainConfig{
|
||||||
GenesisValidatorsRoot: common.HexToHash("0x4b363db94e286120d76eb905340fdd4e54bfe9f06bf33ff6cf5ad27f511bfe95"),
|
GenesisValidatorsRoot: common.HexToHash("0x4b363db94e286120d76eb905340fdd4e54bfe9f06bf33ff6cf5ad27f511bfe95"),
|
||||||
GenesisTime: 1606824023,
|
GenesisTime: 1606824023,
|
||||||
Checkpoint: common.HexToHash("0x6509b691f4de4f7b083f2784938fd52f0e131675432b3fd85ea549af9aebd3d0"),
|
Checkpoint: common.HexToHash(checkpointMainnet),
|
||||||
}).
|
}).
|
||||||
AddFork("GENESIS", 0, []byte{0, 0, 0, 0}).
|
AddFork("GENESIS", 0, []byte{0, 0, 0, 0}).
|
||||||
AddFork("ALTAIR", 74240, []byte{1, 0, 0, 0}).
|
AddFork("ALTAIR", 74240, []byte{1, 0, 0, 0}).
|
||||||
|
|
@ -35,7 +46,7 @@ var (
|
||||||
SepoliaLightConfig = (&ChainConfig{
|
SepoliaLightConfig = (&ChainConfig{
|
||||||
GenesisValidatorsRoot: common.HexToHash("0xd8ea171f3c94aea21ebc42a1ed61052acf3f9209c00e4efbaaddac09ed9b8078"),
|
GenesisValidatorsRoot: common.HexToHash("0xd8ea171f3c94aea21ebc42a1ed61052acf3f9209c00e4efbaaddac09ed9b8078"),
|
||||||
GenesisTime: 1655733600,
|
GenesisTime: 1655733600,
|
||||||
Checkpoint: common.HexToHash("0x456e85f5608afab3465a0580bff8572255f6d97af0c5f939e3f7536b5edb2d3f"),
|
Checkpoint: common.HexToHash(checkpointSepolia),
|
||||||
}).
|
}).
|
||||||
AddFork("GENESIS", 0, []byte{144, 0, 0, 105}).
|
AddFork("GENESIS", 0, []byte{144, 0, 0, 105}).
|
||||||
AddFork("ALTAIR", 50, []byte{144, 0, 0, 112}).
|
AddFork("ALTAIR", 50, []byte{144, 0, 0, 112}).
|
||||||
|
|
@ -47,7 +58,7 @@ var (
|
||||||
HoleskyLightConfig = (&ChainConfig{
|
HoleskyLightConfig = (&ChainConfig{
|
||||||
GenesisValidatorsRoot: common.HexToHash("0x9143aa7c615a7f7115e2b6aac319c03529df8242ae705fba9df39b79c59fa8b1"),
|
GenesisValidatorsRoot: common.HexToHash("0x9143aa7c615a7f7115e2b6aac319c03529df8242ae705fba9df39b79c59fa8b1"),
|
||||||
GenesisTime: 1695902400,
|
GenesisTime: 1695902400,
|
||||||
Checkpoint: common.HexToHash("0x6456a1317f54d4b4f2cb5bc9d153b5af0988fe767ef0609f0236cf29030bcff7"),
|
Checkpoint: common.HexToHash(checkpointHolesky),
|
||||||
}).
|
}).
|
||||||
AddFork("GENESIS", 0, []byte{1, 1, 112, 0}).
|
AddFork("GENESIS", 0, []byte{1, 1, 112, 0}).
|
||||||
AddFork("ALTAIR", 0, []byte{2, 1, 112, 0}).
|
AddFork("ALTAIR", 0, []byte{2, 1, 112, 0}).
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
# Clef
|
# Clef
|
||||||
|
|
||||||
Clef can be used to sign transactions and data and is meant as a(n eventual) replacement for Geth's account management. This allows DApps to not depend on Geth's account management. When a DApp wants to sign data (or a transaction), it can send the content to Clef, which will then provide the user with context and asks for permission to sign the content. If the users grants the signing request, Clef will send the signature back to the DApp.
|
Clef can be used to sign transactions and data and is meant as a(n eventual) replacement for Geth's account management. This allows DApps to not depend on Geth's account management. When a DApp wants to sign data (or a transaction), it can send the content to Clef, which will then provide the user with context and ask for permission to sign the content. If the user grants the signing request, Clef will send the signature back to the DApp.
|
||||||
|
|
||||||
This setup allows a DApp to connect to a remote Ethereum node and send transactions that are locally signed. This can help in situations when a DApp is connected to an untrusted remote Ethereum node, because a local one is not available, not synchronized with the chain, or is a node that has no built-in (or limited) account management.
|
This setup allows a DApp to connect to a remote Ethereum node and send transactions that are locally signed. This can help in situations when a DApp is connected to an untrusted remote Ethereum node, because a local one is not available, not synchronized with the chain, or is a node that has no built-in (or limited) account management.
|
||||||
|
|
||||||
|
|
@ -123,7 +123,7 @@ The External API is **untrusted**: it does not accept credentials, nor does it e
|
||||||
|
|
||||||
Clef has one native console-based UI, for operation without any standalone tools. However, there is also an API to communicate with an external UI. To enable that UI, the signer needs to be executed with the `--stdio-ui` option, which allocates `stdin` / `stdout` for the UI API.
|
Clef has one native console-based UI, for operation without any standalone tools. However, there is also an API to communicate with an external UI. To enable that UI, the signer needs to be executed with the `--stdio-ui` option, which allocates `stdin` / `stdout` for the UI API.
|
||||||
|
|
||||||
An example (insecure) proof-of-concept of has been implemented in `pythonsigner.py`.
|
An example (insecure) proof-of-concept has been implemented in `pythonsigner.py`.
|
||||||
|
|
||||||
The model is as follows:
|
The model is as follows:
|
||||||
|
|
||||||
|
|
@ -335,7 +335,7 @@ Bash example:
|
||||||
|
|
||||||
#### Arguments
|
#### Arguments
|
||||||
- content type [string]: type of signed data
|
- content type [string]: type of signed data
|
||||||
- `text/validator`: hex data with custom validator defined in a contract
|
- `text/validator`: hex data with a custom validator defined in a contract
|
||||||
- `application/clique`: [clique](https://github.com/ethereum/EIPs/issues/225) headers
|
- `application/clique`: [clique](https://github.com/ethereum/EIPs/issues/225) headers
|
||||||
- `text/plain`: simple hex data validated by `account_ecRecover`
|
- `text/plain`: simple hex data validated by `account_ecRecover`
|
||||||
- account [address]: account to sign with
|
- account [address]: account to sign with
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@
|
||||||
|
|
||||||
The `signer` binary contains a ruleset engine, implemented with [OttoVM](https://github.com/robertkrimen/otto)
|
The `signer` binary contains a ruleset engine, implemented with [OttoVM](https://github.com/robertkrimen/otto)
|
||||||
|
|
||||||
It enables usecases like the following:
|
It enables use cases like the following:
|
||||||
|
|
||||||
* I want to auto-approve transactions with contract `CasinoDapp`, with up to `0.05 ether` in value to maximum `1 ether` per 24h period
|
* I want to auto-approve transactions with contract `CasinoDapp`, with up to `0.05 ether` in value to maximum `1 ether` per 24h period
|
||||||
* I want to auto-approve transaction to contract `EthAlarmClock` with `data`=`0xdeadbeef`, if `value=0`, `gas < 44k` and `gasPrice < 40Gwei`
|
* I want to auto-approve transaction to contract `EthAlarmClock` with `data`=`0xdeadbeef`, if `value=0`, `gas < 44k` and `gasPrice < 40Gwei`
|
||||||
|
|
|
||||||
|
|
@ -16,7 +16,7 @@ which can
|
||||||
1. Take a prestate, including
|
1. Take a prestate, including
|
||||||
- Accounts,
|
- Accounts,
|
||||||
- Block context information,
|
- Block context information,
|
||||||
- Previous blockshashes (*optional)
|
- Previous block hashes (*optional)
|
||||||
2. Apply a set of transactions,
|
2. Apply a set of transactions,
|
||||||
3. Apply a mining-reward (*optional),
|
3. Apply a mining-reward (*optional),
|
||||||
4. And generate a post-state, including
|
4. And generate a post-state, including
|
||||||
|
|
|
||||||
|
|
@ -22,7 +22,7 @@ import "regexp"
|
||||||
// within its filename by the execution spec test (EEST).
|
// within its filename by the execution spec test (EEST).
|
||||||
type testMetadata struct {
|
type testMetadata struct {
|
||||||
fork string
|
fork string
|
||||||
module string // which python module gnerated the test, e.g. eip7702
|
module string // which python module generated the test, e.g. eip7702
|
||||||
file string // exact file the test came from, e.g. test_gas.py
|
file string // exact file the test came from, e.g. test_gas.py
|
||||||
function string // func that created the test, e.g. test_valid_mcopy_operations
|
function string // func that created the test, e.g. test_valid_mcopy_operations
|
||||||
parameters string // the name of the parameters which were used to fill the test, e.g. zero_inputs
|
parameters string // the name of the parameters which were used to fill the test, e.g. zero_inputs
|
||||||
|
|
|
||||||
|
|
@ -37,7 +37,9 @@ import (
|
||||||
"github.com/ethereum/go-ethereum/crypto"
|
"github.com/ethereum/go-ethereum/crypto"
|
||||||
"github.com/ethereum/go-ethereum/eth/ethconfig"
|
"github.com/ethereum/go-ethereum/eth/ethconfig"
|
||||||
"github.com/ethereum/go-ethereum/ethdb"
|
"github.com/ethereum/go-ethereum/ethdb"
|
||||||
|
"github.com/ethereum/go-ethereum/internal/debug"
|
||||||
"github.com/ethereum/go-ethereum/internal/era"
|
"github.com/ethereum/go-ethereum/internal/era"
|
||||||
|
"github.com/ethereum/go-ethereum/internal/flags"
|
||||||
"github.com/ethereum/go-ethereum/log"
|
"github.com/ethereum/go-ethereum/log"
|
||||||
"github.com/ethereum/go-ethereum/params"
|
"github.com/ethereum/go-ethereum/params"
|
||||||
"github.com/urfave/cli/v2"
|
"github.com/urfave/cli/v2"
|
||||||
|
|
@ -66,7 +68,7 @@ It expects the genesis file as argument.`,
|
||||||
Name: "dumpgenesis",
|
Name: "dumpgenesis",
|
||||||
Usage: "Dumps genesis block JSON configuration to stdout",
|
Usage: "Dumps genesis block JSON configuration to stdout",
|
||||||
ArgsUsage: "",
|
ArgsUsage: "",
|
||||||
Flags: append([]cli.Flag{utils.DataDirFlag}, utils.NetworkFlags...),
|
Flags: slices.Concat([]cli.Flag{utils.DataDirFlag}, utils.NetworkFlags),
|
||||||
Description: `
|
Description: `
|
||||||
The dumpgenesis command prints the genesis configuration of the network preset
|
The dumpgenesis command prints the genesis configuration of the network preset
|
||||||
if one is set. Otherwise it prints the genesis from the datadir.`,
|
if one is set. Otherwise it prints the genesis from the datadir.`,
|
||||||
|
|
@ -78,11 +80,11 @@ if one is set. Otherwise it prints the genesis from the datadir.`,
|
||||||
ArgsUsage: "<filename> (<filename 2> ... <filename N>) ",
|
ArgsUsage: "<filename> (<filename 2> ... <filename N>) ",
|
||||||
Flags: slices.Concat([]cli.Flag{
|
Flags: slices.Concat([]cli.Flag{
|
||||||
utils.CacheFlag,
|
utils.CacheFlag,
|
||||||
utils.SyncModeFlag,
|
|
||||||
utils.GCModeFlag,
|
utils.GCModeFlag,
|
||||||
utils.SnapshotFlag,
|
utils.SnapshotFlag,
|
||||||
utils.CacheDatabaseFlag,
|
utils.CacheDatabaseFlag,
|
||||||
utils.CacheGCFlag,
|
utils.CacheGCFlag,
|
||||||
|
utils.NoCompactionFlag,
|
||||||
utils.MetricsEnabledFlag,
|
utils.MetricsEnabledFlag,
|
||||||
utils.MetricsEnabledExpensiveFlag,
|
utils.MetricsEnabledExpensiveFlag,
|
||||||
utils.MetricsHTTPFlag,
|
utils.MetricsHTTPFlag,
|
||||||
|
|
@ -105,7 +107,11 @@ if one is set. Otherwise it prints the genesis from the datadir.`,
|
||||||
utils.LogNoHistoryFlag,
|
utils.LogNoHistoryFlag,
|
||||||
utils.LogExportCheckpointsFlag,
|
utils.LogExportCheckpointsFlag,
|
||||||
utils.StateHistoryFlag,
|
utils.StateHistoryFlag,
|
||||||
}, utils.DatabaseFlags),
|
}, utils.DatabaseFlags, debug.Flags),
|
||||||
|
Before: func(ctx *cli.Context) error {
|
||||||
|
flags.MigrateGlobalFlags(ctx)
|
||||||
|
return debug.Setup(ctx)
|
||||||
|
},
|
||||||
Description: `
|
Description: `
|
||||||
The import command allows the import of blocks from an RLP-encoded format. This format can be a single file
|
The import command allows the import of blocks from an RLP-encoded format. This format can be a single file
|
||||||
containing multiple RLP-encoded blocks, or multiple files can be given.
|
containing multiple RLP-encoded blocks, or multiple files can be given.
|
||||||
|
|
@ -119,10 +125,7 @@ to import successfully.`,
|
||||||
Name: "export",
|
Name: "export",
|
||||||
Usage: "Export blockchain into file",
|
Usage: "Export blockchain into file",
|
||||||
ArgsUsage: "<filename> [<blockNumFirst> <blockNumLast>]",
|
ArgsUsage: "<filename> [<blockNumFirst> <blockNumLast>]",
|
||||||
Flags: slices.Concat([]cli.Flag{
|
Flags: slices.Concat([]cli.Flag{utils.CacheFlag}, utils.DatabaseFlags),
|
||||||
utils.CacheFlag,
|
|
||||||
utils.SyncModeFlag,
|
|
||||||
}, utils.DatabaseFlags),
|
|
||||||
Description: `
|
Description: `
|
||||||
Requires a first argument of the file to write to.
|
Requires a first argument of the file to write to.
|
||||||
Optional second and third arguments control the first and
|
Optional second and third arguments control the first and
|
||||||
|
|
@ -135,12 +138,7 @@ be gzipped.`,
|
||||||
Name: "import-history",
|
Name: "import-history",
|
||||||
Usage: "Import an Era archive",
|
Usage: "Import an Era archive",
|
||||||
ArgsUsage: "<dir>",
|
ArgsUsage: "<dir>",
|
||||||
Flags: slices.Concat([]cli.Flag{
|
Flags: slices.Concat([]cli.Flag{utils.TxLookupLimitFlag, utils.TransactionHistoryFlag}, utils.DatabaseFlags, utils.NetworkFlags),
|
||||||
utils.TxLookupLimitFlag,
|
|
||||||
},
|
|
||||||
utils.DatabaseFlags,
|
|
||||||
utils.NetworkFlags,
|
|
||||||
),
|
|
||||||
Description: `
|
Description: `
|
||||||
The import-history command will import blocks and their corresponding receipts
|
The import-history command will import blocks and their corresponding receipts
|
||||||
from Era archives.
|
from Era archives.
|
||||||
|
|
@ -151,7 +149,7 @@ from Era archives.
|
||||||
Name: "export-history",
|
Name: "export-history",
|
||||||
Usage: "Export blockchain history to Era archives",
|
Usage: "Export blockchain history to Era archives",
|
||||||
ArgsUsage: "<dir> <first> <last>",
|
ArgsUsage: "<dir> <first> <last>",
|
||||||
Flags: slices.Concat(utils.DatabaseFlags),
|
Flags: utils.DatabaseFlags,
|
||||||
Description: `
|
Description: `
|
||||||
The export-history command will export blocks and their corresponding receipts
|
The export-history command will export blocks and their corresponding receipts
|
||||||
into Era archives. Eras are typically packaged in steps of 8192 blocks.
|
into Era archives. Eras are typically packaged in steps of 8192 blocks.
|
||||||
|
|
@ -162,10 +160,7 @@ into Era archives. Eras are typically packaged in steps of 8192 blocks.
|
||||||
Name: "import-preimages",
|
Name: "import-preimages",
|
||||||
Usage: "Import the preimage database from an RLP stream",
|
Usage: "Import the preimage database from an RLP stream",
|
||||||
ArgsUsage: "<datafile>",
|
ArgsUsage: "<datafile>",
|
||||||
Flags: slices.Concat([]cli.Flag{
|
Flags: slices.Concat([]cli.Flag{utils.CacheFlag}, utils.DatabaseFlags),
|
||||||
utils.CacheFlag,
|
|
||||||
utils.SyncModeFlag,
|
|
||||||
}, utils.DatabaseFlags),
|
|
||||||
Description: `
|
Description: `
|
||||||
The import-preimages command imports hash preimages from an RLP encoded stream.
|
The import-preimages command imports hash preimages from an RLP encoded stream.
|
||||||
It's deprecated, please use "geth db import" instead.
|
It's deprecated, please use "geth db import" instead.
|
||||||
|
|
@ -435,6 +430,10 @@ func importHistory(ctx *cli.Context) error {
|
||||||
network = "mainnet"
|
network = "mainnet"
|
||||||
case ctx.Bool(utils.SepoliaFlag.Name):
|
case ctx.Bool(utils.SepoliaFlag.Name):
|
||||||
network = "sepolia"
|
network = "sepolia"
|
||||||
|
case ctx.Bool(utils.HoleskyFlag.Name):
|
||||||
|
network = "holesky"
|
||||||
|
case ctx.Bool(utils.HoodiFlag.Name):
|
||||||
|
network = "hoodi"
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// No network flag set, try to determine network based on files
|
// No network flag set, try to determine network based on files
|
||||||
|
|
|
||||||
|
|
@ -89,9 +89,7 @@ Remove blockchain and state databases`,
|
||||||
Action: inspect,
|
Action: inspect,
|
||||||
Name: "inspect",
|
Name: "inspect",
|
||||||
ArgsUsage: "<prefix> <start>",
|
ArgsUsage: "<prefix> <start>",
|
||||||
Flags: slices.Concat([]cli.Flag{
|
Flags: slices.Concat(utils.NetworkFlags, utils.DatabaseFlags),
|
||||||
utils.SyncModeFlag,
|
|
||||||
}, utils.NetworkFlags, utils.DatabaseFlags),
|
|
||||||
Usage: "Inspect the storage size for each type of data in the database",
|
Usage: "Inspect the storage size for each type of data in the database",
|
||||||
Description: `This commands iterates the entire database. If the optional 'prefix' and 'start' arguments are provided, then the iteration is limited to the given subset of data.`,
|
Description: `This commands iterates the entire database. If the optional 'prefix' and 'start' arguments are provided, then the iteration is limited to the given subset of data.`,
|
||||||
}
|
}
|
||||||
|
|
@ -109,16 +107,13 @@ a data corruption.`,
|
||||||
Action: dbStats,
|
Action: dbStats,
|
||||||
Name: "stats",
|
Name: "stats",
|
||||||
Usage: "Print leveldb statistics",
|
Usage: "Print leveldb statistics",
|
||||||
Flags: slices.Concat([]cli.Flag{
|
Flags: slices.Concat(utils.NetworkFlags, utils.DatabaseFlags),
|
||||||
utils.SyncModeFlag,
|
|
||||||
}, utils.NetworkFlags, utils.DatabaseFlags),
|
|
||||||
}
|
}
|
||||||
dbCompactCmd = &cli.Command{
|
dbCompactCmd = &cli.Command{
|
||||||
Action: dbCompact,
|
Action: dbCompact,
|
||||||
Name: "compact",
|
Name: "compact",
|
||||||
Usage: "Compact leveldb database. WARNING: May take a very long time",
|
Usage: "Compact leveldb database. WARNING: May take a very long time",
|
||||||
Flags: slices.Concat([]cli.Flag{
|
Flags: slices.Concat([]cli.Flag{
|
||||||
utils.SyncModeFlag,
|
|
||||||
utils.CacheFlag,
|
utils.CacheFlag,
|
||||||
utils.CacheDatabaseFlag,
|
utils.CacheDatabaseFlag,
|
||||||
}, utils.NetworkFlags, utils.DatabaseFlags),
|
}, utils.NetworkFlags, utils.DatabaseFlags),
|
||||||
|
|
@ -131,9 +126,7 @@ corruption if it is aborted during execution'!`,
|
||||||
Name: "get",
|
Name: "get",
|
||||||
Usage: "Show the value of a database key",
|
Usage: "Show the value of a database key",
|
||||||
ArgsUsage: "<hex-encoded key>",
|
ArgsUsage: "<hex-encoded key>",
|
||||||
Flags: slices.Concat([]cli.Flag{
|
Flags: slices.Concat(utils.NetworkFlags, utils.DatabaseFlags),
|
||||||
utils.SyncModeFlag,
|
|
||||||
}, utils.NetworkFlags, utils.DatabaseFlags),
|
|
||||||
Description: "This command looks up the specified database key from the database.",
|
Description: "This command looks up the specified database key from the database.",
|
||||||
}
|
}
|
||||||
dbDeleteCmd = &cli.Command{
|
dbDeleteCmd = &cli.Command{
|
||||||
|
|
@ -141,9 +134,7 @@ corruption if it is aborted during execution'!`,
|
||||||
Name: "delete",
|
Name: "delete",
|
||||||
Usage: "Delete a database key (WARNING: may corrupt your database)",
|
Usage: "Delete a database key (WARNING: may corrupt your database)",
|
||||||
ArgsUsage: "<hex-encoded key>",
|
ArgsUsage: "<hex-encoded key>",
|
||||||
Flags: slices.Concat([]cli.Flag{
|
Flags: slices.Concat(utils.NetworkFlags, utils.DatabaseFlags),
|
||||||
utils.SyncModeFlag,
|
|
||||||
}, utils.NetworkFlags, utils.DatabaseFlags),
|
|
||||||
Description: `This command deletes the specified database key from the database.
|
Description: `This command deletes the specified database key from the database.
|
||||||
WARNING: This is a low-level operation which may cause database corruption!`,
|
WARNING: This is a low-level operation which may cause database corruption!`,
|
||||||
}
|
}
|
||||||
|
|
@ -152,9 +143,7 @@ WARNING: This is a low-level operation which may cause database corruption!`,
|
||||||
Name: "put",
|
Name: "put",
|
||||||
Usage: "Set the value of a database key (WARNING: may corrupt your database)",
|
Usage: "Set the value of a database key (WARNING: may corrupt your database)",
|
||||||
ArgsUsage: "<hex-encoded key> <hex-encoded value>",
|
ArgsUsage: "<hex-encoded key> <hex-encoded value>",
|
||||||
Flags: slices.Concat([]cli.Flag{
|
Flags: slices.Concat(utils.NetworkFlags, utils.DatabaseFlags),
|
||||||
utils.SyncModeFlag,
|
|
||||||
}, utils.NetworkFlags, utils.DatabaseFlags),
|
|
||||||
Description: `This command sets a given database key to the given value.
|
Description: `This command sets a given database key to the given value.
|
||||||
WARNING: This is a low-level operation which may cause database corruption!`,
|
WARNING: This is a low-level operation which may cause database corruption!`,
|
||||||
}
|
}
|
||||||
|
|
@ -163,9 +152,7 @@ WARNING: This is a low-level operation which may cause database corruption!`,
|
||||||
Name: "dumptrie",
|
Name: "dumptrie",
|
||||||
Usage: "Show the storage key/values of a given storage trie",
|
Usage: "Show the storage key/values of a given storage trie",
|
||||||
ArgsUsage: "<hex-encoded state root> <hex-encoded account hash> <hex-encoded storage trie root> <hex-encoded start (optional)> <int max elements (optional)>",
|
ArgsUsage: "<hex-encoded state root> <hex-encoded account hash> <hex-encoded storage trie root> <hex-encoded start (optional)> <int max elements (optional)>",
|
||||||
Flags: slices.Concat([]cli.Flag{
|
Flags: slices.Concat(utils.NetworkFlags, utils.DatabaseFlags),
|
||||||
utils.SyncModeFlag,
|
|
||||||
}, utils.NetworkFlags, utils.DatabaseFlags),
|
|
||||||
Description: "This command looks up the specified database key from the database.",
|
Description: "This command looks up the specified database key from the database.",
|
||||||
}
|
}
|
||||||
dbDumpFreezerIndex = &cli.Command{
|
dbDumpFreezerIndex = &cli.Command{
|
||||||
|
|
@ -173,9 +160,7 @@ WARNING: This is a low-level operation which may cause database corruption!`,
|
||||||
Name: "freezer-index",
|
Name: "freezer-index",
|
||||||
Usage: "Dump out the index of a specific freezer table",
|
Usage: "Dump out the index of a specific freezer table",
|
||||||
ArgsUsage: "<freezer-type> <table-type> <start (int)> <end (int)>",
|
ArgsUsage: "<freezer-type> <table-type> <start (int)> <end (int)>",
|
||||||
Flags: slices.Concat([]cli.Flag{
|
Flags: slices.Concat(utils.NetworkFlags, utils.DatabaseFlags),
|
||||||
utils.SyncModeFlag,
|
|
||||||
}, utils.NetworkFlags, utils.DatabaseFlags),
|
|
||||||
Description: "This command displays information about the freezer index.",
|
Description: "This command displays information about the freezer index.",
|
||||||
}
|
}
|
||||||
dbImportCmd = &cli.Command{
|
dbImportCmd = &cli.Command{
|
||||||
|
|
@ -183,9 +168,7 @@ WARNING: This is a low-level operation which may cause database corruption!`,
|
||||||
Name: "import",
|
Name: "import",
|
||||||
Usage: "Imports leveldb-data from an exported RLP dump.",
|
Usage: "Imports leveldb-data from an exported RLP dump.",
|
||||||
ArgsUsage: "<dumpfile> <start (optional)",
|
ArgsUsage: "<dumpfile> <start (optional)",
|
||||||
Flags: slices.Concat([]cli.Flag{
|
Flags: slices.Concat(utils.NetworkFlags, utils.DatabaseFlags),
|
||||||
utils.SyncModeFlag,
|
|
||||||
}, utils.NetworkFlags, utils.DatabaseFlags),
|
|
||||||
Description: "The import command imports the specific chain data from an RLP encoded stream.",
|
Description: "The import command imports the specific chain data from an RLP encoded stream.",
|
||||||
}
|
}
|
||||||
dbExportCmd = &cli.Command{
|
dbExportCmd = &cli.Command{
|
||||||
|
|
@ -193,18 +176,14 @@ WARNING: This is a low-level operation which may cause database corruption!`,
|
||||||
Name: "export",
|
Name: "export",
|
||||||
Usage: "Exports the chain data into an RLP dump. If the <dumpfile> has .gz suffix, gzip compression will be used.",
|
Usage: "Exports the chain data into an RLP dump. If the <dumpfile> has .gz suffix, gzip compression will be used.",
|
||||||
ArgsUsage: "<type> <dumpfile>",
|
ArgsUsage: "<type> <dumpfile>",
|
||||||
Flags: slices.Concat([]cli.Flag{
|
Flags: slices.Concat(utils.NetworkFlags, utils.DatabaseFlags),
|
||||||
utils.SyncModeFlag,
|
|
||||||
}, utils.NetworkFlags, utils.DatabaseFlags),
|
|
||||||
Description: "Exports the specified chain data to an RLP encoded stream, optionally gzip-compressed.",
|
Description: "Exports the specified chain data to an RLP encoded stream, optionally gzip-compressed.",
|
||||||
}
|
}
|
||||||
dbMetadataCmd = &cli.Command{
|
dbMetadataCmd = &cli.Command{
|
||||||
Action: showMetaData,
|
Action: showMetaData,
|
||||||
Name: "metadata",
|
Name: "metadata",
|
||||||
Usage: "Shows metadata about the chain status.",
|
Usage: "Shows metadata about the chain status.",
|
||||||
Flags: slices.Concat([]cli.Flag{
|
Flags: slices.Concat(utils.NetworkFlags, utils.DatabaseFlags),
|
||||||
utils.SyncModeFlag,
|
|
||||||
}, utils.NetworkFlags, utils.DatabaseFlags),
|
|
||||||
Description: "Shows metadata about the chain status.",
|
Description: "Shows metadata about the chain status.",
|
||||||
}
|
}
|
||||||
dbInspectHistoryCmd = &cli.Command{
|
dbInspectHistoryCmd = &cli.Command{
|
||||||
|
|
@ -213,7 +192,6 @@ WARNING: This is a low-level operation which may cause database corruption!`,
|
||||||
Usage: "Inspect the state history within block range",
|
Usage: "Inspect the state history within block range",
|
||||||
ArgsUsage: "<address> [OPTIONAL <storage-slot>]",
|
ArgsUsage: "<address> [OPTIONAL <storage-slot>]",
|
||||||
Flags: slices.Concat([]cli.Flag{
|
Flags: slices.Concat([]cli.Flag{
|
||||||
utils.SyncModeFlag,
|
|
||||||
&cli.Uint64Flag{
|
&cli.Uint64Flag{
|
||||||
Name: "start",
|
Name: "start",
|
||||||
Usage: "block number of the range start, zero means earliest history",
|
Usage: "block number of the range start, zero means earliest history",
|
||||||
|
|
|
||||||
|
|
@ -142,7 +142,6 @@ var (
|
||||||
utils.VMTraceJsonConfigFlag,
|
utils.VMTraceJsonConfigFlag,
|
||||||
utils.NetworkIdFlag,
|
utils.NetworkIdFlag,
|
||||||
utils.EthStatsURLFlag,
|
utils.EthStatsURLFlag,
|
||||||
utils.NoCompactionFlag,
|
|
||||||
utils.GpoBlocksFlag,
|
utils.GpoBlocksFlag,
|
||||||
utils.GpoPercentileFlag,
|
utils.GpoPercentileFlag,
|
||||||
utils.GpoMaxGasPriceFlag,
|
utils.GpoMaxGasPriceFlag,
|
||||||
|
|
|
||||||
|
|
@ -54,8 +54,8 @@ func (iter *testIterator) Next() (byte, []byte, []byte, bool) {
|
||||||
if iter.index == 42 {
|
if iter.index == 42 {
|
||||||
iter.index += 1
|
iter.index += 1
|
||||||
}
|
}
|
||||||
return OpBatchAdd, []byte(fmt.Sprintf("key-%04d", iter.index)),
|
return OpBatchAdd, fmt.Appendf(nil, "key-%04d", iter.index),
|
||||||
[]byte(fmt.Sprintf("value %d", iter.index)), true
|
fmt.Appendf(nil, "value %d", iter.index), true
|
||||||
}
|
}
|
||||||
|
|
||||||
func (iter *testIterator) Release() {}
|
func (iter *testIterator) Release() {}
|
||||||
|
|
@ -72,7 +72,7 @@ func testExport(t *testing.T, f string) {
|
||||||
}
|
}
|
||||||
// verify
|
// verify
|
||||||
for i := 0; i < 1000; i++ {
|
for i := 0; i < 1000; i++ {
|
||||||
v, err := db.Get([]byte(fmt.Sprintf("key-%04d", i)))
|
v, err := db.Get(fmt.Appendf(nil, "key-%04d", i))
|
||||||
if (i < 5 || i == 42) && err == nil {
|
if (i < 5 || i == 42) && err == nil {
|
||||||
t.Fatalf("expected no element at idx %d, got '%v'", i, string(v))
|
t.Fatalf("expected no element at idx %d, got '%v'", i, string(v))
|
||||||
}
|
}
|
||||||
|
|
@ -85,7 +85,7 @@ func testExport(t *testing.T, f string) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
v, err := db.Get([]byte(fmt.Sprintf("key-%04d", 1000)))
|
v, err := db.Get(fmt.Appendf(nil, "key-%04d", 1000))
|
||||||
if err == nil {
|
if err == nil {
|
||||||
t.Fatalf("expected no element at idx %d, got '%v'", 1000, string(v))
|
t.Fatalf("expected no element at idx %d, got '%v'", 1000, string(v))
|
||||||
}
|
}
|
||||||
|
|
@ -120,7 +120,7 @@ func (iter *deletionIterator) Next() (byte, []byte, []byte, bool) {
|
||||||
if iter.index == 42 {
|
if iter.index == 42 {
|
||||||
iter.index += 1
|
iter.index += 1
|
||||||
}
|
}
|
||||||
return OpBatchDel, []byte(fmt.Sprintf("key-%04d", iter.index)), nil, true
|
return OpBatchDel, fmt.Appendf(nil, "key-%04d", iter.index), nil, true
|
||||||
}
|
}
|
||||||
|
|
||||||
func (iter *deletionIterator) Release() {}
|
func (iter *deletionIterator) Release() {}
|
||||||
|
|
@ -132,14 +132,14 @@ func testDeletion(t *testing.T, f string) {
|
||||||
}
|
}
|
||||||
db := rawdb.NewMemoryDatabase()
|
db := rawdb.NewMemoryDatabase()
|
||||||
for i := 0; i < 1000; i++ {
|
for i := 0; i < 1000; i++ {
|
||||||
db.Put([]byte(fmt.Sprintf("key-%04d", i)), []byte(fmt.Sprintf("value %d", i)))
|
db.Put(fmt.Appendf(nil, "key-%04d", i), fmt.Appendf(nil, "value %d", i))
|
||||||
}
|
}
|
||||||
err = ImportLDBData(db, f, 5, make(chan struct{}))
|
err = ImportLDBData(db, f, 5, make(chan struct{}))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
for i := 0; i < 1000; i++ {
|
for i := 0; i < 1000; i++ {
|
||||||
v, err := db.Get([]byte(fmt.Sprintf("key-%04d", i)))
|
v, err := db.Get(fmt.Appendf(nil, "key-%04d", i))
|
||||||
if i < 5 || i == 42 {
|
if i < 5 || i == 42 {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("expected element at idx %d, got '%v'", i, err)
|
t.Fatalf("expected element at idx %d, got '%v'", i, err)
|
||||||
|
|
|
||||||
|
|
@ -2189,6 +2189,8 @@ func MakeChain(ctx *cli.Context, stack *node.Node, readonly bool) (*core.BlockCh
|
||||||
}
|
}
|
||||||
if !ctx.Bool(SnapshotFlag.Name) {
|
if !ctx.Bool(SnapshotFlag.Name) {
|
||||||
cache.SnapshotLimit = 0 // Disabled
|
cache.SnapshotLimit = 0 // Disabled
|
||||||
|
} else if ctx.IsSet(CacheFlag.Name) || ctx.IsSet(CacheSnapshotFlag.Name) {
|
||||||
|
cache.SnapshotLimit = ctx.Int(CacheFlag.Name) * ctx.Int(CacheSnapshotFlag.Name) / 100
|
||||||
}
|
}
|
||||||
// If we're in readonly, do not bother generating snapshot data.
|
// If we're in readonly, do not bother generating snapshot data.
|
||||||
if readonly {
|
if readonly {
|
||||||
|
|
|
||||||
|
|
@ -72,7 +72,7 @@ func (i *HexOrDecimal256) MarshalText() ([]byte, error) {
|
||||||
if i == nil {
|
if i == nil {
|
||||||
return []byte("0x0"), nil
|
return []byte("0x0"), nil
|
||||||
}
|
}
|
||||||
return []byte(fmt.Sprintf("%#x", (*big.Int)(i))), nil
|
return fmt.Appendf(nil, "%#x", (*big.Int)(i)), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Decimal256 unmarshals big.Int as a decimal string. When unmarshalling,
|
// Decimal256 unmarshals big.Int as a decimal string. When unmarshalling,
|
||||||
|
|
|
||||||
|
|
@ -48,7 +48,7 @@ func (i *HexOrDecimal64) UnmarshalText(input []byte) error {
|
||||||
|
|
||||||
// MarshalText implements encoding.TextMarshaler.
|
// MarshalText implements encoding.TextMarshaler.
|
||||||
func (i HexOrDecimal64) MarshalText() ([]byte, error) {
|
func (i HexOrDecimal64) MarshalText() ([]byte, error) {
|
||||||
return []byte(fmt.Sprintf("%#x", uint64(i))), nil
|
return fmt.Appendf(nil, "%#x", uint64(i)), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// ParseUint64 parses s as an integer in decimal or hexadecimal syntax.
|
// ParseUint64 parses s as an integer in decimal or hexadecimal syntax.
|
||||||
|
|
|
||||||
|
|
@ -86,6 +86,11 @@ func (bc *BlockChain) GetHeaderByNumber(number uint64) *types.Header {
|
||||||
return bc.hc.GetHeaderByNumber(number)
|
return bc.hc.GetHeaderByNumber(number)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetBlockNumber retrieves the block number associated with a block hash.
|
||||||
|
func (bc *BlockChain) GetBlockNumber(hash common.Hash) *uint64 {
|
||||||
|
return bc.hc.GetBlockNumber(hash)
|
||||||
|
}
|
||||||
|
|
||||||
// GetHeadersFrom returns a contiguous segment of headers, in rlp-form, going
|
// GetHeadersFrom returns a contiguous segment of headers, in rlp-form, going
|
||||||
// backwards from the given number.
|
// backwards from the given number.
|
||||||
func (bc *BlockChain) GetHeadersFrom(number, count uint64) []rlp.RawValue {
|
func (bc *BlockChain) GetHeadersFrom(number, count uint64) []rlp.RawValue {
|
||||||
|
|
|
||||||
|
|
@ -504,6 +504,13 @@ func GenerateVerkleChain(config *params.ChainConfig, parent *types.Block, engine
|
||||||
if gen != nil {
|
if gen != nil {
|
||||||
gen(i, b)
|
gen(i, b)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
requests := b.collectRequests(false)
|
||||||
|
if requests != nil {
|
||||||
|
reqHash := types.CalcRequestsHash(requests)
|
||||||
|
b.header.RequestsHash = &reqHash
|
||||||
|
}
|
||||||
|
|
||||||
body := &types.Body{
|
body := &types.Body{
|
||||||
Transactions: b.txs,
|
Transactions: b.txs,
|
||||||
Uncles: b.uncles,
|
Uncles: b.uncles,
|
||||||
|
|
|
||||||
|
|
@ -83,7 +83,11 @@ func (cv *ChainView) getReceipts(number uint64) types.Receipts {
|
||||||
if number > cv.headNumber {
|
if number > cv.headNumber {
|
||||||
panic("invalid block number")
|
panic("invalid block number")
|
||||||
}
|
}
|
||||||
return cv.chain.GetReceiptsByHash(cv.blockHash(number))
|
blockHash := cv.blockHash(number)
|
||||||
|
if blockHash == (common.Hash{}) {
|
||||||
|
log.Error("Chain view: block hash unavailable", "number", number, "head", cv.headNumber)
|
||||||
|
}
|
||||||
|
return cv.chain.GetReceiptsByHash(blockHash)
|
||||||
}
|
}
|
||||||
|
|
||||||
// limitedView returns a new chain view that is a truncated version of the parent view.
|
// limitedView returns a new chain view that is a truncated version of the parent view.
|
||||||
|
|
|
||||||
|
|
@ -29,8 +29,24 @@ import (
|
||||||
"github.com/ethereum/go-ethereum/core/rawdb"
|
"github.com/ethereum/go-ethereum/core/rawdb"
|
||||||
"github.com/ethereum/go-ethereum/core/types"
|
"github.com/ethereum/go-ethereum/core/types"
|
||||||
"github.com/ethereum/go-ethereum/ethdb"
|
"github.com/ethereum/go-ethereum/ethdb"
|
||||||
"github.com/ethereum/go-ethereum/ethdb/leveldb"
|
|
||||||
"github.com/ethereum/go-ethereum/log"
|
"github.com/ethereum/go-ethereum/log"
|
||||||
|
"github.com/ethereum/go-ethereum/metrics"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
mapCountGauge = metrics.NewRegisteredGauge("filtermaps/maps/count", nil) // actual number of rendered maps
|
||||||
|
mapLogValueMeter = metrics.NewRegisteredMeter("filtermaps/maps/logvalues", nil) // number of log values processed
|
||||||
|
mapBlockMeter = metrics.NewRegisteredMeter("filtermaps/maps/blocks", nil) // number of block delimiters processed
|
||||||
|
mapRenderTimer = metrics.NewRegisteredTimer("filtermaps/maps/rendertime", nil) // time elapsed while rendering a single map
|
||||||
|
mapWriteTimer = metrics.NewRegisteredTimer("filtermaps/maps/writetime", nil) // time elapsed while writing a batch of finished maps to db
|
||||||
|
matchRequestTimer = metrics.NewRegisteredTimer("filtermaps/match/requesttime", nil) // processing time a matching request in a single epoch
|
||||||
|
matchEpochTimer = metrics.NewRegisteredTimer("filtermaps/match/epochtime", nil) // total processing time a matching request
|
||||||
|
matchBaseRowAccessMeter = metrics.NewRegisteredMeter("filtermaps/match/baserowaccess", nil) // number of accessed rows on layer 0
|
||||||
|
matchBaseRowSizeMeter = metrics.NewRegisteredMeter("filtermaps/match/baserowsize", nil) // size of accessed rows on layer 0
|
||||||
|
matchExtRowAccessMeter = metrics.NewRegisteredMeter("filtermaps/match/extrowaccess", nil) // number of accessed rows on higher layers
|
||||||
|
matchExtRowSizeMeter = metrics.NewRegisteredMeter("filtermaps/match/extrowsize", nil) // size of accessed rows on higher layers
|
||||||
|
matchLogLookup = metrics.NewRegisteredMeter("filtermaps/match/loglookup", nil) // number of log lookups based on potential matches
|
||||||
|
matchAllMeter = metrics.NewRegisteredMeter("filtermaps/match/matchall", nil) // number of requests returned with ErrMatchAll
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
|
@ -54,10 +70,12 @@ type FilterMaps struct {
|
||||||
// We chose to implement disabling this way because it requires less special
|
// We chose to implement disabling this way because it requires less special
|
||||||
// case logic in eth/filters.
|
// case logic in eth/filters.
|
||||||
disabled bool
|
disabled bool
|
||||||
|
disabledCh chan struct{} // closed by indexer if disabled
|
||||||
|
|
||||||
closeCh chan struct{}
|
closeCh chan struct{}
|
||||||
closeWg sync.WaitGroup
|
closeWg sync.WaitGroup
|
||||||
history uint64
|
history uint64
|
||||||
|
hashScheme bool // use hashdb-safe delete range method
|
||||||
exportFileName string
|
exportFileName string
|
||||||
Params
|
Params
|
||||||
|
|
||||||
|
|
@ -68,6 +86,7 @@ type FilterMaps struct {
|
||||||
// Matcher backend can read them under indexLock read lock.
|
// Matcher backend can read them under indexLock read lock.
|
||||||
indexLock sync.RWMutex
|
indexLock sync.RWMutex
|
||||||
indexedRange filterMapsRange
|
indexedRange filterMapsRange
|
||||||
|
cleanedEpochsBefore uint32 // all unindexed data cleaned before this point
|
||||||
indexedView *ChainView // always consistent with the log index
|
indexedView *ChainView // always consistent with the log index
|
||||||
hasTempRange bool
|
hasTempRange bool
|
||||||
|
|
||||||
|
|
@ -179,6 +198,10 @@ type Config struct {
|
||||||
// This option enables the checkpoint JSON file generator.
|
// This option enables the checkpoint JSON file generator.
|
||||||
// If set, the given file will be updated with checkpoint information.
|
// If set, the given file will be updated with checkpoint information.
|
||||||
ExportFileName string
|
ExportFileName string
|
||||||
|
|
||||||
|
// expect trie nodes of hash based state scheme in the filtermaps key range;
|
||||||
|
// use safe iterator based implementation of DeleteRange that skips them
|
||||||
|
HashScheme bool
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewFilterMaps creates a new FilterMaps and starts the indexer.
|
// NewFilterMaps creates a new FilterMaps and starts the indexer.
|
||||||
|
|
@ -196,6 +219,8 @@ func NewFilterMaps(db ethdb.KeyValueStore, initView *ChainView, historyCutoff, f
|
||||||
blockProcessingCh: make(chan bool, 1),
|
blockProcessingCh: make(chan bool, 1),
|
||||||
history: config.History,
|
history: config.History,
|
||||||
disabled: config.Disabled,
|
disabled: config.Disabled,
|
||||||
|
hashScheme: config.HashScheme,
|
||||||
|
disabledCh: make(chan struct{}),
|
||||||
exportFileName: config.ExportFileName,
|
exportFileName: config.ExportFileName,
|
||||||
Params: params,
|
Params: params,
|
||||||
indexedRange: filterMapsRange{
|
indexedRange: filterMapsRange{
|
||||||
|
|
@ -206,6 +231,10 @@ func NewFilterMaps(db ethdb.KeyValueStore, initView *ChainView, historyCutoff, f
|
||||||
maps: common.NewRange(rs.MapsFirst, rs.MapsAfterLast-rs.MapsFirst),
|
maps: common.NewRange(rs.MapsFirst, rs.MapsAfterLast-rs.MapsFirst),
|
||||||
tailPartialEpoch: rs.TailPartialEpoch,
|
tailPartialEpoch: rs.TailPartialEpoch,
|
||||||
},
|
},
|
||||||
|
// 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),
|
matcherSyncCh: make(chan *FilterMapsMatcherBackend),
|
||||||
matchers: make(map[*FilterMapsMatcherBackend]struct{}),
|
matchers: make(map[*FilterMapsMatcherBackend]struct{}),
|
||||||
filterMapCache: lru.NewCache[uint32, filterMap](cachedFilterMaps),
|
filterMapCache: lru.NewCache[uint32, filterMap](cachedFilterMaps),
|
||||||
|
|
@ -278,8 +307,13 @@ func (f *FilterMaps) initChainView(chainView *ChainView) *ChainView {
|
||||||
}
|
}
|
||||||
|
|
||||||
// reset un-initializes the FilterMaps structure and removes all related data from
|
// reset un-initializes the FilterMaps structure and removes all related data from
|
||||||
// the database. The function returns true if everything was successfully removed.
|
// the database.
|
||||||
func (f *FilterMaps) reset() bool {
|
// Note that in case of leveldb database the fallback implementation of DeleteRange
|
||||||
|
// might take a long time to finish and deleting the entire database may be
|
||||||
|
// interrupted by a shutdown. Deleting the filterMapsRange entry first does
|
||||||
|
// guarantee though that the next init() will not return successfully until the
|
||||||
|
// entire database has been cleaned.
|
||||||
|
func (f *FilterMaps) reset() {
|
||||||
f.indexLock.Lock()
|
f.indexLock.Lock()
|
||||||
f.indexedRange = filterMapsRange{}
|
f.indexedRange = filterMapsRange{}
|
||||||
f.indexedView = nil
|
f.indexedView = nil
|
||||||
|
|
@ -292,11 +326,26 @@ func (f *FilterMaps) reset() bool {
|
||||||
// deleting the range first ensures that resetDb will be called again at next
|
// deleting the range first ensures that resetDb will be called again at next
|
||||||
// startup and any leftover data will be removed even if it cannot finish now.
|
// startup and any leftover data will be removed even if it cannot finish now.
|
||||||
rawdb.DeleteFilterMapsRange(f.db)
|
rawdb.DeleteFilterMapsRange(f.db)
|
||||||
return f.safeDeleteRange(rawdb.DeleteFilterMapsDb, "Resetting log index database")
|
f.safeDeleteWithLogs(rawdb.DeleteFilterMapsDb, "Resetting log index database", f.isShuttingDown)
|
||||||
|
}
|
||||||
|
|
||||||
|
// isShuttingDown returns true if FilterMaps is shutting down.
|
||||||
|
func (f *FilterMaps) isShuttingDown() bool {
|
||||||
|
select {
|
||||||
|
case <-f.closeCh:
|
||||||
|
return true
|
||||||
|
default:
|
||||||
|
return false
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// init initializes an empty log index according to the current targetView.
|
// init initializes an empty log index according to the current targetView.
|
||||||
func (f *FilterMaps) init() error {
|
func (f *FilterMaps) init() error {
|
||||||
|
// ensure that there is no remaining data in the filter maps key range
|
||||||
|
if err := f.safeDeleteWithLogs(rawdb.DeleteFilterMapsDb, "Resetting log index database", f.isShuttingDown); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
f.indexLock.Lock()
|
f.indexLock.Lock()
|
||||||
defer f.indexLock.Unlock()
|
defer f.indexLock.Unlock()
|
||||||
|
|
||||||
|
|
@ -317,6 +366,13 @@ func (f *FilterMaps) init() error {
|
||||||
bestIdx, bestLen = idx, max
|
bestIdx, bestLen = idx, max
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
var initBlockNumber uint64
|
||||||
|
if bestLen > 0 {
|
||||||
|
initBlockNumber = checkpoints[bestIdx][bestLen-1].BlockNumber
|
||||||
|
}
|
||||||
|
if initBlockNumber < f.historyCutoff {
|
||||||
|
return errors.New("cannot start indexing before history cutoff point")
|
||||||
|
}
|
||||||
batch := f.db.NewBatch()
|
batch := f.db.NewBatch()
|
||||||
for epoch := range bestLen {
|
for epoch := range bestLen {
|
||||||
cp := checkpoints[bestIdx][epoch]
|
cp := checkpoints[bestIdx][epoch]
|
||||||
|
|
@ -337,38 +393,37 @@ func (f *FilterMaps) init() error {
|
||||||
|
|
||||||
// removeBloomBits removes old bloom bits data from the database.
|
// removeBloomBits removes old bloom bits data from the database.
|
||||||
func (f *FilterMaps) removeBloomBits() {
|
func (f *FilterMaps) removeBloomBits() {
|
||||||
f.safeDeleteRange(rawdb.DeleteBloomBitsDb, "Removing old bloom bits database")
|
f.safeDeleteWithLogs(rawdb.DeleteBloomBitsDb, "Removing old bloom bits database", f.isShuttingDown)
|
||||||
f.closeWg.Done()
|
f.closeWg.Done()
|
||||||
}
|
}
|
||||||
|
|
||||||
// safeDeleteRange calls the specified database range deleter function
|
// safeDeleteWithLogs is a wrapper for a function that performs a safe range
|
||||||
// repeatedly as long as it returns leveldb.ErrTooManyKeys.
|
// delete operation using rawdb.SafeDeleteRange. It emits log messages if the
|
||||||
// This wrapper is necessary because of the leveldb fallback implementation
|
// process takes long enough to call the stop callback.
|
||||||
// of DeleteRange.
|
func (f *FilterMaps) safeDeleteWithLogs(deleteFn func(db ethdb.KeyValueStore, hashScheme bool, stopCb func(bool) bool) error, action string, stopCb func() bool) error {
|
||||||
func (f *FilterMaps) safeDeleteRange(removeFn func(ethdb.KeyValueRangeDeleter) error, action string) bool {
|
var (
|
||||||
start := time.Now()
|
start = time.Now()
|
||||||
var retry bool
|
logPrinted bool
|
||||||
for {
|
lastLogPrinted = start
|
||||||
err := removeFn(f.db)
|
)
|
||||||
if err == nil {
|
switch err := deleteFn(f.db, f.hashScheme, func(deleted bool) bool {
|
||||||
if retry {
|
if deleted && !logPrinted || time.Since(lastLogPrinted) > time.Second*10 {
|
||||||
log.Info(action+" finished", "elapsed", time.Since(start))
|
log.Info(action+" in progress...", "elapsed", common.PrettyDuration(time.Since(start)))
|
||||||
|
logPrinted, lastLogPrinted = true, time.Now()
|
||||||
}
|
}
|
||||||
return true
|
return stopCb()
|
||||||
|
}); {
|
||||||
|
case err == nil:
|
||||||
|
if logPrinted {
|
||||||
|
log.Info(action+" finished", "elapsed", common.PrettyDuration(time.Since(start)))
|
||||||
}
|
}
|
||||||
if err != leveldb.ErrTooManyKeys {
|
return nil
|
||||||
log.Error(action+" failed", "error", err)
|
case errors.Is(err, rawdb.ErrDeleteRangeInterrupted):
|
||||||
return false
|
log.Warn(action+" interrupted", "elapsed", common.PrettyDuration(time.Since(start)))
|
||||||
}
|
return err
|
||||||
select {
|
|
||||||
case <-f.closeCh:
|
|
||||||
return false
|
|
||||||
default:
|
default:
|
||||||
}
|
log.Error(action+" failed", "error", err)
|
||||||
if !retry {
|
return err
|
||||||
log.Info(action+" in progress...", "elapsed", time.Since(start))
|
|
||||||
retry = true
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -391,8 +446,12 @@ func (f *FilterMaps) setRange(batch ethdb.KeyValueWriter, newView *ChainView, ne
|
||||||
TailPartialEpoch: newRange.tailPartialEpoch,
|
TailPartialEpoch: newRange.tailPartialEpoch,
|
||||||
}
|
}
|
||||||
rawdb.WriteFilterMapsRange(batch, rs)
|
rawdb.WriteFilterMapsRange(batch, rs)
|
||||||
|
if !isTempRange {
|
||||||
|
mapCountGauge.Update(int64(newRange.maps.Count() + newRange.tailPartialEpoch))
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
rawdb.DeleteFilterMapsRange(batch)
|
rawdb.DeleteFilterMapsRange(batch)
|
||||||
|
mapCountGauge.Update(0)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -637,54 +696,97 @@ func (f *FilterMaps) deleteLastBlockOfMap(batch ethdb.Batch, mapIndex uint32) {
|
||||||
rawdb.DeleteFilterMapLastBlock(batch, mapIndex)
|
rawdb.DeleteFilterMapLastBlock(batch, mapIndex)
|
||||||
}
|
}
|
||||||
|
|
||||||
// deleteTailEpoch deletes index data from the earliest, either fully or partially
|
// deleteTailEpoch deletes index data from the specified epoch. The last block
|
||||||
// indexed epoch. The last block pointer for the last map of the epoch and the
|
// pointer for the last map of the epoch and the corresponding block log value
|
||||||
// corresponding block log value pointer are retained as these are always assumed
|
// pointer are retained as these are always assumed to be available for each
|
||||||
// to be available for each epoch.
|
// epoch as boundary markers.
|
||||||
func (f *FilterMaps) deleteTailEpoch(epoch uint32) error {
|
// The function returns true if all index data related to the epoch (except for
|
||||||
|
// the boundary markers) has been fully removed.
|
||||||
|
func (f *FilterMaps) deleteTailEpoch(epoch uint32) (bool, error) {
|
||||||
f.indexLock.Lock()
|
f.indexLock.Lock()
|
||||||
defer f.indexLock.Unlock()
|
defer f.indexLock.Unlock()
|
||||||
|
|
||||||
|
// determine epoch boundaries
|
||||||
firstMap := epoch << f.logMapsPerEpoch
|
firstMap := epoch << f.logMapsPerEpoch
|
||||||
lastBlock, _, err := f.getLastBlockOfMap(firstMap + f.mapsPerEpoch - 1)
|
lastBlock, _, err := f.getLastBlockOfMap(firstMap + f.mapsPerEpoch - 1)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to retrieve last block of deleted epoch %d: %v", epoch, err)
|
return false, fmt.Errorf("failed to retrieve last block of deleted epoch %d: %v", epoch, err)
|
||||||
}
|
}
|
||||||
var firstBlock uint64
|
var firstBlock uint64
|
||||||
if epoch > 0 {
|
if epoch > 0 {
|
||||||
firstBlock, _, err = f.getLastBlockOfMap(firstMap - 1)
|
firstBlock, _, err = f.getLastBlockOfMap(firstMap - 1)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to retrieve last block before deleted epoch %d: %v", epoch, err)
|
return false, fmt.Errorf("failed to retrieve last block before deleted epoch %d: %v", epoch, err)
|
||||||
}
|
}
|
||||||
firstBlock++
|
firstBlock++
|
||||||
}
|
}
|
||||||
fmr := f.indexedRange
|
// update rendered range if necessary
|
||||||
if f.indexedRange.maps.First() == firstMap &&
|
var (
|
||||||
f.indexedRange.maps.AfterLast() > firstMap+f.mapsPerEpoch &&
|
fmr = f.indexedRange
|
||||||
f.indexedRange.tailPartialEpoch == 0 {
|
firstEpoch = f.indexedRange.maps.First() >> f.logMapsPerEpoch
|
||||||
fmr.maps.SetFirst(firstMap + f.mapsPerEpoch)
|
afterLastEpoch = (f.indexedRange.maps.AfterLast() + f.mapsPerEpoch - 1) >> f.logMapsPerEpoch
|
||||||
fmr.blocks.SetFirst(lastBlock + 1)
|
)
|
||||||
} else if f.indexedRange.maps.First() == firstMap+f.mapsPerEpoch {
|
if f.indexedRange.tailPartialEpoch != 0 && firstEpoch > 0 {
|
||||||
fmr.tailPartialEpoch = 0
|
firstEpoch--
|
||||||
} else {
|
|
||||||
return errors.New("invalid tail epoch number")
|
|
||||||
}
|
}
|
||||||
|
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)
|
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)
|
first := f.mapRowIndex(firstMap, 0)
|
||||||
count := f.mapRowIndex(firstMap+f.mapsPerEpoch, 0) - first
|
count := f.mapRowIndex(firstMap+f.mapsPerEpoch, 0) - first
|
||||||
rawdb.DeleteFilterMapRows(f.db, common.NewRange(first, count))
|
if err := rawdb.DeleteFilterMapRows(f.db, common.NewRange(first, count), hashScheme, stopCb); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
for mapIndex := firstMap; mapIndex < firstMap+f.mapsPerEpoch; mapIndex++ {
|
for mapIndex := firstMap; mapIndex < firstMap+f.mapsPerEpoch; mapIndex++ {
|
||||||
f.filterMapCache.Remove(mapIndex)
|
f.filterMapCache.Remove(mapIndex)
|
||||||
}
|
}
|
||||||
rawdb.DeleteFilterMapLastBlocks(f.db, common.NewRange(firstMap, f.mapsPerEpoch-1)) // keep last enrty
|
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++ {
|
for mapIndex := firstMap; mapIndex < firstMap+f.mapsPerEpoch-1; mapIndex++ {
|
||||||
f.lastBlockCache.Remove(mapIndex)
|
f.lastBlockCache.Remove(mapIndex)
|
||||||
}
|
}
|
||||||
rawdb.DeleteBlockLvPointers(f.db, common.NewRange(firstBlock, lastBlock-firstBlock)) // keep last enrty
|
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++ {
|
for blockNumber := firstBlock; blockNumber < lastBlock; blockNumber++ {
|
||||||
f.lvPointerCache.Remove(blockNumber)
|
f.lvPointerCache.Remove(blockNumber)
|
||||||
}
|
}
|
||||||
return nil
|
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 {
|
||||||
|
// 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
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// exportCheckpoints exports epoch checkpoints in the format used by checkpoints.go.
|
// exportCheckpoints exports epoch checkpoints in the format used by checkpoints.go.
|
||||||
|
|
|
||||||
|
|
@ -17,6 +17,7 @@
|
||||||
package filtermaps
|
package filtermaps
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"errors"
|
||||||
"math"
|
"math"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
|
@ -36,26 +37,28 @@ func (f *FilterMaps) indexerLoop() {
|
||||||
|
|
||||||
if f.disabled {
|
if f.disabled {
|
||||||
f.reset()
|
f.reset()
|
||||||
|
close(f.disabledCh)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
log.Info("Started log indexer")
|
log.Info("Started log indexer")
|
||||||
|
|
||||||
for !f.stop {
|
for !f.stop {
|
||||||
if !f.indexedRange.initialized {
|
if !f.indexedRange.initialized {
|
||||||
if err := f.init(); err != nil {
|
if f.targetView.headNumber == 0 {
|
||||||
log.Error("Error initializing log index", "error", err)
|
// initialize when chain head is available
|
||||||
// unexpected error; there is not a lot we can do here, maybe it
|
|
||||||
// recovers, maybe not. Calling event processing here ensures
|
|
||||||
// that we can still properly shutdown in case of an infinite loop.
|
|
||||||
f.processSingleEvent(true)
|
f.processSingleEvent(true)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
if err := f.init(); err != nil {
|
||||||
|
f.disableForError("initialization", err)
|
||||||
|
f.reset() // remove broken index from DB
|
||||||
|
return
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if !f.targetHeadIndexed() {
|
if !f.targetHeadIndexed() {
|
||||||
if !f.tryIndexHead() {
|
if err := f.tryIndexHead(); err != nil && err != errChainUpdate {
|
||||||
// either shutdown or unexpected error; in the latter case ensure
|
f.disableForError("head rendering", err)
|
||||||
// that proper shutdown is still possible.
|
return
|
||||||
f.processSingleEvent(true)
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if f.finalBlock != f.lastFinal {
|
if f.finalBlock != f.lastFinal {
|
||||||
|
|
@ -64,11 +67,37 @@ func (f *FilterMaps) indexerLoop() {
|
||||||
}
|
}
|
||||||
f.lastFinal = f.finalBlock
|
f.lastFinal = f.finalBlock
|
||||||
}
|
}
|
||||||
if f.tryIndexTail() && f.tryUnindexTail() {
|
// always attempt unindexing before indexing the tail in order to
|
||||||
|
// 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()
|
f.waitForNewHead()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// disableForError is called when the indexer encounters a database error, for example a
|
||||||
|
// missing receipt. We can't continue operating when the database is broken, so the
|
||||||
|
// indexer goes into disabled state.
|
||||||
|
// Note that the partial index is left in disk; maybe a client update can fix the
|
||||||
|
// issue without reindexing.
|
||||||
|
func (f *FilterMaps) disableForError(op string, err error) {
|
||||||
|
log.Error("Log index "+op+" failed, reverting to unindexed mode", "error", err)
|
||||||
|
f.disabled = true
|
||||||
|
close(f.disabledCh)
|
||||||
}
|
}
|
||||||
|
|
||||||
type targetUpdate struct {
|
type targetUpdate struct {
|
||||||
|
|
@ -111,16 +140,17 @@ func (f *FilterMaps) SetBlockProcessing(blockProcessing bool) {
|
||||||
// WaitIdle blocks until the indexer is in an idle state while synced up to the
|
// WaitIdle blocks until the indexer is in an idle state while synced up to the
|
||||||
// latest targetView.
|
// latest targetView.
|
||||||
func (f *FilterMaps) WaitIdle() {
|
func (f *FilterMaps) WaitIdle() {
|
||||||
if f.disabled {
|
|
||||||
f.closeWg.Wait()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
for {
|
for {
|
||||||
ch := make(chan bool)
|
ch := make(chan bool)
|
||||||
f.waitIdleCh <- ch
|
select {
|
||||||
|
case f.waitIdleCh <- ch:
|
||||||
if <-ch {
|
if <-ch {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
case <-f.disabledCh:
|
||||||
|
f.closeWg.Wait()
|
||||||
|
return
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -191,16 +221,16 @@ func (f *FilterMaps) setTarget(target targetUpdate) {
|
||||||
f.finalBlock = target.finalBlock
|
f.finalBlock = target.finalBlock
|
||||||
}
|
}
|
||||||
|
|
||||||
// tryIndexHead tries to render head maps according to the current targetView
|
// tryIndexHead tries to render head maps according to the current targetView.
|
||||||
// and returns true if successful.
|
// Should be called when targetHeadIndexed returns false. If this function
|
||||||
func (f *FilterMaps) tryIndexHead() bool {
|
// returns no error then either stop is true or head indexing is finished.
|
||||||
|
func (f *FilterMaps) tryIndexHead() error {
|
||||||
headRenderer, err := f.renderMapsBefore(math.MaxUint32)
|
headRenderer, err := f.renderMapsBefore(math.MaxUint32)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error("Error creating log index head renderer", "error", err)
|
return err
|
||||||
return false
|
|
||||||
}
|
}
|
||||||
if headRenderer == nil {
|
if headRenderer == nil {
|
||||||
return true
|
return errors.New("head indexer has nothing to do") // tryIndexHead should be called when head is not indexed
|
||||||
}
|
}
|
||||||
if !f.startedHeadIndex {
|
if !f.startedHeadIndex {
|
||||||
f.lastLogHeadIndex = time.Now()
|
f.lastLogHeadIndex = time.Now()
|
||||||
|
|
@ -225,8 +255,7 @@ func (f *FilterMaps) tryIndexHead() bool {
|
||||||
f.lastLogHeadIndex = time.Now()
|
f.lastLogHeadIndex = time.Now()
|
||||||
}
|
}
|
||||||
}); err != nil {
|
}); err != nil {
|
||||||
log.Error("Log index head rendering failed", "error", err)
|
return err
|
||||||
return false
|
|
||||||
}
|
}
|
||||||
if f.loggedHeadIndex && f.indexedRange.hasIndexedBlocks() {
|
if f.loggedHeadIndex && f.indexedRange.hasIndexedBlocks() {
|
||||||
log.Info("Log index head rendering finished",
|
log.Info("Log index head rendering finished",
|
||||||
|
|
@ -235,7 +264,7 @@ func (f *FilterMaps) tryIndexHead() bool {
|
||||||
"elapsed", common.PrettyDuration(time.Since(f.startedHeadIndexAt)))
|
"elapsed", common.PrettyDuration(time.Since(f.startedHeadIndexAt)))
|
||||||
}
|
}
|
||||||
f.loggedHeadIndex, f.startedHeadIndex = false, false
|
f.loggedHeadIndex, f.startedHeadIndex = false, false
|
||||||
return true
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// tryIndexTail tries to render tail epochs until the tail target block is
|
// tryIndexTail tries to render tail epochs until the tail target block is
|
||||||
|
|
@ -243,7 +272,7 @@ func (f *FilterMaps) tryIndexHead() bool {
|
||||||
// Note that tail indexing is only started if the log index head is fully
|
// Note that tail indexing is only started if the log index head is fully
|
||||||
// rendered according to targetView and is suspended as soon as the targetView
|
// rendered according to targetView and is suspended as soon as the targetView
|
||||||
// is changed.
|
// is changed.
|
||||||
func (f *FilterMaps) tryIndexTail() bool {
|
func (f *FilterMaps) tryIndexTail() (bool, error) {
|
||||||
for {
|
for {
|
||||||
firstEpoch := f.indexedRange.maps.First() >> f.logMapsPerEpoch
|
firstEpoch := f.indexedRange.maps.First() >> f.logMapsPerEpoch
|
||||||
if firstEpoch == 0 || !f.needTailEpoch(firstEpoch-1) {
|
if firstEpoch == 0 || !f.needTailEpoch(firstEpoch-1) {
|
||||||
|
|
@ -251,7 +280,7 @@ func (f *FilterMaps) tryIndexTail() bool {
|
||||||
}
|
}
|
||||||
f.processEvents()
|
f.processEvents()
|
||||||
if f.stop || !f.targetHeadIndexed() {
|
if f.stop || !f.targetHeadIndexed() {
|
||||||
return false
|
return false, nil
|
||||||
}
|
}
|
||||||
// resume process if tail rendering was interrupted because of head rendering
|
// resume process if tail rendering was interrupted because of head rendering
|
||||||
tailRenderer := f.tailRenderer
|
tailRenderer := f.tailRenderer
|
||||||
|
|
@ -263,8 +292,7 @@ func (f *FilterMaps) tryIndexTail() bool {
|
||||||
var err error
|
var err error
|
||||||
tailRenderer, err = f.renderMapsBefore(f.indexedRange.maps.First())
|
tailRenderer, err = f.renderMapsBefore(f.indexedRange.maps.First())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error("Error creating log index tail renderer", "error", err)
|
return false, err
|
||||||
return false
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if tailRenderer == nil {
|
if tailRenderer == nil {
|
||||||
|
|
@ -297,13 +325,16 @@ func (f *FilterMaps) tryIndexTail() bool {
|
||||||
f.lastLogTailIndex = time.Now()
|
f.lastLogTailIndex = time.Now()
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
if err != nil && f.needTailEpoch(firstEpoch-1) {
|
if err != nil && !f.needTailEpoch(firstEpoch-1) {
|
||||||
// stop silently if cutoff point has move beyond epoch boundary while rendering
|
// stop silently if cutoff point has move beyond epoch boundary while rendering
|
||||||
log.Error("Log index tail rendering failed", "error", err)
|
return true, nil
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
}
|
}
|
||||||
if !done {
|
if !done {
|
||||||
f.tailRenderer = tailRenderer // only keep tail renderer if interrupted by stopCb
|
f.tailRenderer = tailRenderer // only keep tail renderer if interrupted by stopCb
|
||||||
return false
|
return false, nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if f.loggedTailIndex && f.indexedRange.hasIndexedBlocks() {
|
if f.loggedTailIndex && f.indexedRange.hasIndexedBlocks() {
|
||||||
|
|
@ -313,32 +344,31 @@ func (f *FilterMaps) tryIndexTail() bool {
|
||||||
"elapsed", common.PrettyDuration(time.Since(f.startedTailIndexAt)))
|
"elapsed", common.PrettyDuration(time.Since(f.startedTailIndexAt)))
|
||||||
f.loggedTailIndex = false
|
f.loggedTailIndex = false
|
||||||
}
|
}
|
||||||
return true
|
return true, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// tryUnindexTail removes entire epochs of log index data as long as the first
|
// tryUnindexTail removes entire epochs of log index data as long as the first
|
||||||
// fully indexed block is at least as old as the tail target.
|
// fully indexed block is at least as old as the tail target.
|
||||||
// Note that unindexing is very quick as it only removes continuous ranges of
|
// Note that unindexing is very quick as it only removes continuous ranges of
|
||||||
// data from the database and is also called while running head indexing.
|
// data from the database and is also called while running head indexing.
|
||||||
func (f *FilterMaps) tryUnindexTail() bool {
|
func (f *FilterMaps) tryUnindexTail() (bool, error) {
|
||||||
for {
|
firstEpoch := f.indexedRange.maps.First() >> f.logMapsPerEpoch
|
||||||
firstEpoch := (f.indexedRange.maps.First() - f.indexedRange.tailPartialEpoch) >> f.logMapsPerEpoch
|
if f.indexedRange.tailPartialEpoch > 0 && firstEpoch > 0 {
|
||||||
if f.needTailEpoch(firstEpoch) {
|
firstEpoch--
|
||||||
break
|
|
||||||
}
|
|
||||||
f.processEvents()
|
|
||||||
if f.stop {
|
|
||||||
return false
|
|
||||||
}
|
}
|
||||||
|
for epoch := min(firstEpoch, f.cleanedEpochsBefore); !f.needTailEpoch(epoch); epoch++ {
|
||||||
if !f.startedTailUnindex {
|
if !f.startedTailUnindex {
|
||||||
f.startedTailUnindexAt = time.Now()
|
f.startedTailUnindexAt = time.Now()
|
||||||
f.startedTailUnindex = true
|
f.startedTailUnindex = true
|
||||||
f.ptrTailUnindexMap = f.indexedRange.maps.First() - f.indexedRange.tailPartialEpoch
|
f.ptrTailUnindexMap = f.indexedRange.maps.First() - f.indexedRange.tailPartialEpoch
|
||||||
f.ptrTailUnindexBlock = f.indexedRange.blocks.First() - f.tailPartialBlocks()
|
f.ptrTailUnindexBlock = f.indexedRange.blocks.First() - f.tailPartialBlocks()
|
||||||
}
|
}
|
||||||
if err := f.deleteTailEpoch(firstEpoch); err != nil {
|
if done, err := f.deleteTailEpoch(epoch); !done {
|
||||||
log.Error("Log index tail epoch unindexing failed", "error", err)
|
return false, err
|
||||||
return false
|
}
|
||||||
|
f.processEvents()
|
||||||
|
if f.stop || !f.targetHeadIndexed() {
|
||||||
|
return false, nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if f.startedTailUnindex && f.indexedRange.hasIndexedBlocks() {
|
if f.startedTailUnindex && f.indexedRange.hasIndexedBlocks() {
|
||||||
|
|
@ -349,7 +379,7 @@ func (f *FilterMaps) tryUnindexTail() bool {
|
||||||
"elapsed", common.PrettyDuration(time.Since(f.startedTailUnindexAt)))
|
"elapsed", common.PrettyDuration(time.Since(f.startedTailUnindexAt)))
|
||||||
f.startedTailUnindex = false
|
f.startedTailUnindex = false
|
||||||
}
|
}
|
||||||
return true
|
return true, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// needTailEpoch returns true if the given tail epoch needs to be kept
|
// needTailEpoch returns true if the given tail epoch needs to be kept
|
||||||
|
|
|
||||||
|
|
@ -21,6 +21,7 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"math"
|
"math"
|
||||||
"sort"
|
"sort"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/ethereum/go-ethereum/common"
|
"github.com/ethereum/go-ethereum/common"
|
||||||
"github.com/ethereum/go-ethereum/common/lru"
|
"github.com/ethereum/go-ethereum/common/lru"
|
||||||
|
|
@ -301,6 +302,11 @@ func (r *mapRenderer) run(stopCb func() bool, writeCb func()) (bool, error) {
|
||||||
|
|
||||||
// renderCurrentMap renders a single map.
|
// renderCurrentMap renders a single map.
|
||||||
func (r *mapRenderer) renderCurrentMap(stopCb func() bool) (bool, error) {
|
func (r *mapRenderer) renderCurrentMap(stopCb func() bool) (bool, error) {
|
||||||
|
var (
|
||||||
|
totalTime time.Duration
|
||||||
|
logValuesProcessed, blocksProcessed int64
|
||||||
|
)
|
||||||
|
start := time.Now()
|
||||||
if !r.iterator.updateChainView(r.f.targetView) {
|
if !r.iterator.updateChainView(r.f.targetView) {
|
||||||
return false, errChainUpdate
|
return false, errChainUpdate
|
||||||
}
|
}
|
||||||
|
|
@ -316,9 +322,11 @@ func (r *mapRenderer) renderCurrentMap(stopCb func() bool) (bool, error) {
|
||||||
for r.iterator.lvIndex < uint64(r.currentMap.mapIndex+1)<<r.f.logValuesPerMap && !r.iterator.finished {
|
for r.iterator.lvIndex < uint64(r.currentMap.mapIndex+1)<<r.f.logValuesPerMap && !r.iterator.finished {
|
||||||
waitCnt++
|
waitCnt++
|
||||||
if waitCnt >= valuesPerCallback {
|
if waitCnt >= valuesPerCallback {
|
||||||
|
totalTime += time.Since(start)
|
||||||
if stopCb() {
|
if stopCb() {
|
||||||
return false, nil
|
return false, nil
|
||||||
}
|
}
|
||||||
|
start = time.Now()
|
||||||
if !r.iterator.updateChainView(r.f.targetView) {
|
if !r.iterator.updateChainView(r.f.targetView) {
|
||||||
return false, errChainUpdate
|
return false, errChainUpdate
|
||||||
}
|
}
|
||||||
|
|
@ -343,8 +351,10 @@ func (r *mapRenderer) renderCurrentMap(stopCb func() bool) (bool, error) {
|
||||||
return false, fmt.Errorf("failed to advance log iterator at %d while rendering map %d: %v", r.iterator.lvIndex, r.currentMap.mapIndex, err)
|
return false, fmt.Errorf("failed to advance log iterator at %d while rendering map %d: %v", r.iterator.lvIndex, r.currentMap.mapIndex, err)
|
||||||
}
|
}
|
||||||
if !r.iterator.skipToBoundary {
|
if !r.iterator.skipToBoundary {
|
||||||
|
logValuesProcessed++
|
||||||
r.currentMap.lastBlock = r.iterator.blockNumber
|
r.currentMap.lastBlock = r.iterator.blockNumber
|
||||||
if r.iterator.blockStart {
|
if r.iterator.blockStart {
|
||||||
|
blocksProcessed++
|
||||||
r.currentMap.blockLvPtrs = append(r.currentMap.blockLvPtrs, r.iterator.lvIndex)
|
r.currentMap.blockLvPtrs = append(r.currentMap.blockLvPtrs, r.iterator.lvIndex)
|
||||||
}
|
}
|
||||||
if !r.f.testDisableSnapshots && r.renderBefore >= r.f.indexedRange.maps.AfterLast() &&
|
if !r.f.testDisableSnapshots && r.renderBefore >= r.f.indexedRange.maps.AfterLast() &&
|
||||||
|
|
@ -358,12 +368,18 @@ func (r *mapRenderer) renderCurrentMap(stopCb func() bool) (bool, error) {
|
||||||
r.currentMap.headDelimiter = r.iterator.lvIndex
|
r.currentMap.headDelimiter = r.iterator.lvIndex
|
||||||
}
|
}
|
||||||
r.currentMap.lastBlockId = r.f.targetView.getBlockId(r.currentMap.lastBlock)
|
r.currentMap.lastBlockId = r.f.targetView.getBlockId(r.currentMap.lastBlock)
|
||||||
|
totalTime += time.Since(start)
|
||||||
|
mapRenderTimer.Update(totalTime)
|
||||||
|
mapLogValueMeter.Mark(logValuesProcessed)
|
||||||
|
mapBlockMeter.Mark(blocksProcessed)
|
||||||
return true, nil
|
return true, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// writeFinishedMaps writes rendered maps to the database and updates
|
// writeFinishedMaps writes rendered maps to the database and updates
|
||||||
// filterMapsRange and indexedView accordingly.
|
// filterMapsRange and indexedView accordingly.
|
||||||
func (r *mapRenderer) writeFinishedMaps(pauseCb func() bool) error {
|
func (r *mapRenderer) writeFinishedMaps(pauseCb func() bool) error {
|
||||||
|
var totalTime time.Duration
|
||||||
|
start := time.Now()
|
||||||
if len(r.finishedMaps) == 0 {
|
if len(r.finishedMaps) == 0 {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
@ -379,7 +395,7 @@ func (r *mapRenderer) writeFinishedMaps(pauseCb func() bool) error {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to get updated rendered range: %v", err)
|
return fmt.Errorf("failed to get updated rendered range: %v", err)
|
||||||
}
|
}
|
||||||
renderedView := r.f.targetView // stopCb callback might still change targetView while writing finished maps
|
renderedView := r.f.targetView // pauseCb callback might still change targetView while writing finished maps
|
||||||
|
|
||||||
batch := r.f.db.NewBatch()
|
batch := r.f.db.NewBatch()
|
||||||
var writeCnt int
|
var writeCnt int
|
||||||
|
|
@ -393,7 +409,9 @@ func (r *mapRenderer) writeFinishedMaps(pauseCb func() bool) error {
|
||||||
// do not exit while in partially written state but do allow processing
|
// do not exit while in partially written state but do allow processing
|
||||||
// events and pausing while block processing is in progress
|
// events and pausing while block processing is in progress
|
||||||
r.f.indexLock.Unlock()
|
r.f.indexLock.Unlock()
|
||||||
|
totalTime += time.Since(start)
|
||||||
pauseCb()
|
pauseCb()
|
||||||
|
start = time.Now()
|
||||||
r.f.indexLock.Lock()
|
r.f.indexLock.Lock()
|
||||||
batch = r.f.db.NewBatch()
|
batch = r.f.db.NewBatch()
|
||||||
}
|
}
|
||||||
|
|
@ -477,6 +495,8 @@ func (r *mapRenderer) writeFinishedMaps(pauseCb func() bool) error {
|
||||||
if err := batch.Write(); err != nil {
|
if err := batch.Write(); err != nil {
|
||||||
log.Crit("Error writing log index update batch", "error", err)
|
log.Crit("Error writing log index update batch", "error", err)
|
||||||
}
|
}
|
||||||
|
totalTime += time.Since(start)
|
||||||
|
mapWriteTimer.Update(totalTime)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -125,6 +125,7 @@ func GetPotentialMatches(ctx context.Context, backend MatcherBackend, firstBlock
|
||||||
|
|
||||||
start := time.Now()
|
start := time.Now()
|
||||||
res, err := m.process()
|
res, err := m.process()
|
||||||
|
matchRequestTimer.Update(time.Since(start))
|
||||||
|
|
||||||
if doRuntimeStats {
|
if doRuntimeStats {
|
||||||
log.Info("Log search finished", "elapsed", time.Since(start))
|
log.Info("Log search finished", "elapsed", time.Since(start))
|
||||||
|
|
@ -202,6 +203,7 @@ func (m *matcherEnv) process() ([]*types.Log, error) {
|
||||||
logs = append(logs, tasks[waitEpoch].logs...)
|
logs = append(logs, tasks[waitEpoch].logs...)
|
||||||
if err := tasks[waitEpoch].err; err != nil {
|
if err := tasks[waitEpoch].err; err != nil {
|
||||||
if err == ErrMatchAll {
|
if err == ErrMatchAll {
|
||||||
|
matchAllMeter.Mark(1)
|
||||||
return logs, err
|
return logs, err
|
||||||
}
|
}
|
||||||
return logs, fmt.Errorf("failed to process log index epoch %d: %v", waitEpoch, err)
|
return logs, fmt.Errorf("failed to process log index epoch %d: %v", waitEpoch, err)
|
||||||
|
|
@ -220,6 +222,7 @@ func (m *matcherEnv) process() ([]*types.Log, error) {
|
||||||
|
|
||||||
// processEpoch returns the potentially matching logs from the given epoch.
|
// processEpoch returns the potentially matching logs from the given epoch.
|
||||||
func (m *matcherEnv) processEpoch(epochIndex uint32) ([]*types.Log, error) {
|
func (m *matcherEnv) processEpoch(epochIndex uint32) ([]*types.Log, error) {
|
||||||
|
start := time.Now()
|
||||||
var logs []*types.Log
|
var logs []*types.Log
|
||||||
// create a list of map indices to process
|
// create a list of map indices to process
|
||||||
fm, lm := epochIndex<<m.params.logMapsPerEpoch, (epochIndex+1)<<m.params.logMapsPerEpoch-1
|
fm, lm := epochIndex<<m.params.logMapsPerEpoch, (epochIndex+1)<<m.params.logMapsPerEpoch-1
|
||||||
|
|
@ -254,6 +257,7 @@ func (m *matcherEnv) processEpoch(epochIndex uint32) ([]*types.Log, error) {
|
||||||
logs = append(logs, mlogs...)
|
logs = append(logs, mlogs...)
|
||||||
}
|
}
|
||||||
m.getLogStats.addAmount(st, int64(len(logs)))
|
m.getLogStats.addAmount(st, int64(len(logs)))
|
||||||
|
matchEpochTimer.Update(time.Since(start))
|
||||||
return logs, nil
|
return logs, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -273,6 +277,7 @@ func (m *matcherEnv) getLogsFromMatches(matches potentialMatches) ([]*types.Log,
|
||||||
if log != nil {
|
if log != nil {
|
||||||
logs = append(logs, log)
|
logs = append(logs, log)
|
||||||
}
|
}
|
||||||
|
matchLogLookup.Mark(1)
|
||||||
}
|
}
|
||||||
return logs, nil
|
return logs, nil
|
||||||
}
|
}
|
||||||
|
|
@ -381,6 +386,13 @@ func (m *singleMatcherInstance) getMatchesForLayer(ctx context.Context, layerInd
|
||||||
m.stats.setState(&st, stNone)
|
m.stats.setState(&st, stNone)
|
||||||
return nil, fmt.Errorf("failed to retrieve filter map %d row %d: %v", mapIndex, rowIndex, err)
|
return nil, fmt.Errorf("failed to retrieve filter map %d row %d: %v", mapIndex, rowIndex, err)
|
||||||
}
|
}
|
||||||
|
if layerIndex == 0 {
|
||||||
|
matchBaseRowAccessMeter.Mark(1)
|
||||||
|
matchBaseRowSizeMeter.Mark(int64(len(filterRow)))
|
||||||
|
} else {
|
||||||
|
matchExtRowAccessMeter.Mark(1)
|
||||||
|
matchExtRowSizeMeter.Mark(int64(len(filterRow)))
|
||||||
|
}
|
||||||
m.stats.addAmount(st, int64(len(filterRow)))
|
m.stats.addAmount(st, int64(len(filterRow)))
|
||||||
m.stats.setState(&st, stOther)
|
m.stats.setState(&st, stOther)
|
||||||
filterRows = append(filterRows, filterRow)
|
filterRows = append(filterRows, filterRow)
|
||||||
|
|
|
||||||
|
|
@ -141,10 +141,6 @@ func (fm *FilterMapsMatcherBackend) synced() {
|
||||||
// range that has not been changed and has been consistent with all states of the
|
// range that has not been changed and has been consistent with all states of the
|
||||||
// chain since the previous SyncLogIndex or the creation of the matcher backend.
|
// chain since the previous SyncLogIndex or the creation of the matcher backend.
|
||||||
func (fm *FilterMapsMatcherBackend) SyncLogIndex(ctx context.Context) (SyncRange, error) {
|
func (fm *FilterMapsMatcherBackend) SyncLogIndex(ctx context.Context) (SyncRange, error) {
|
||||||
if fm.f.disabled {
|
|
||||||
return SyncRange{HeadNumber: fm.f.targetView.headNumber}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
syncCh := make(chan SyncRange, 1)
|
syncCh := make(chan SyncRange, 1)
|
||||||
fm.f.matchersLock.Lock()
|
fm.f.matchersLock.Lock()
|
||||||
fm.syncCh = syncCh
|
fm.syncCh = syncCh
|
||||||
|
|
@ -154,12 +150,16 @@ func (fm *FilterMapsMatcherBackend) SyncLogIndex(ctx context.Context) (SyncRange
|
||||||
case fm.f.matcherSyncCh <- fm:
|
case fm.f.matcherSyncCh <- fm:
|
||||||
case <-ctx.Done():
|
case <-ctx.Done():
|
||||||
return SyncRange{}, ctx.Err()
|
return SyncRange{}, ctx.Err()
|
||||||
|
case <-fm.f.disabledCh:
|
||||||
|
return SyncRange{HeadNumber: fm.f.targetView.headNumber}, nil
|
||||||
}
|
}
|
||||||
select {
|
select {
|
||||||
case vr := <-syncCh:
|
case vr := <-syncCh:
|
||||||
return vr, nil
|
return vr, nil
|
||||||
case <-ctx.Done():
|
case <-ctx.Done():
|
||||||
return SyncRange{}, ctx.Err()
|
return SyncRange{}, ctx.Err()
|
||||||
|
case <-fm.f.disabledCh:
|
||||||
|
return SyncRange{HeadNumber: fm.f.targetView.headNumber}, nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -354,10 +354,8 @@ func WriteFilterMapBaseRows(db ethdb.KeyValueWriter, mapRowIndex uint64, rows []
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func DeleteFilterMapRows(db ethdb.KeyValueRangeDeleter, mapRows common.Range[uint64]) {
|
func DeleteFilterMapRows(db ethdb.KeyValueStore, mapRows common.Range[uint64], hashScheme bool, stopCallback func(bool) bool) error {
|
||||||
if err := db.DeleteRange(filterMapRowKey(mapRows.First(), false), filterMapRowKey(mapRows.AfterLast(), false)); err != nil {
|
return SafeDeleteRange(db, filterMapRowKey(mapRows.First(), false), filterMapRowKey(mapRows.AfterLast(), false), hashScheme, stopCallback)
|
||||||
log.Crit("Failed to delete range of filter map rows", "err", err)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ReadFilterMapLastBlock retrieves the number of the block that generated the
|
// ReadFilterMapLastBlock retrieves the number of the block that generated the
|
||||||
|
|
@ -368,7 +366,7 @@ func ReadFilterMapLastBlock(db ethdb.KeyValueReader, mapIndex uint32) (uint64, c
|
||||||
return 0, common.Hash{}, err
|
return 0, common.Hash{}, err
|
||||||
}
|
}
|
||||||
if len(enc) != 40 {
|
if len(enc) != 40 {
|
||||||
return 0, common.Hash{}, errors.New("Invalid block number and id encoding")
|
return 0, common.Hash{}, errors.New("invalid block number and id encoding")
|
||||||
}
|
}
|
||||||
var id common.Hash
|
var id common.Hash
|
||||||
copy(id[:], enc[8:])
|
copy(id[:], enc[8:])
|
||||||
|
|
@ -394,10 +392,8 @@ func DeleteFilterMapLastBlock(db ethdb.KeyValueWriter, mapIndex uint32) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func DeleteFilterMapLastBlocks(db ethdb.KeyValueRangeDeleter, maps common.Range[uint32]) {
|
func DeleteFilterMapLastBlocks(db ethdb.KeyValueStore, maps common.Range[uint32], hashScheme bool, stopCallback func(bool) bool) error {
|
||||||
if err := db.DeleteRange(filterMapLastBlockKey(maps.First()), filterMapLastBlockKey(maps.AfterLast())); err != nil {
|
return SafeDeleteRange(db, filterMapLastBlockKey(maps.First()), filterMapLastBlockKey(maps.AfterLast()), hashScheme, stopCallback)
|
||||||
log.Crit("Failed to delete range of filter map last block pointers", "err", err)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ReadBlockLvPointer retrieves the starting log value index where the log values
|
// ReadBlockLvPointer retrieves the starting log value index where the log values
|
||||||
|
|
@ -408,7 +404,7 @@ func ReadBlockLvPointer(db ethdb.KeyValueReader, blockNumber uint64) (uint64, er
|
||||||
return 0, err
|
return 0, err
|
||||||
}
|
}
|
||||||
if len(encPtr) != 8 {
|
if len(encPtr) != 8 {
|
||||||
return 0, errors.New("Invalid log value pointer encoding")
|
return 0, errors.New("invalid log value pointer encoding")
|
||||||
}
|
}
|
||||||
return binary.BigEndian.Uint64(encPtr), nil
|
return binary.BigEndian.Uint64(encPtr), nil
|
||||||
}
|
}
|
||||||
|
|
@ -431,10 +427,8 @@ func DeleteBlockLvPointer(db ethdb.KeyValueWriter, blockNumber uint64) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func DeleteBlockLvPointers(db ethdb.KeyValueRangeDeleter, blocks common.Range[uint64]) {
|
func DeleteBlockLvPointers(db ethdb.KeyValueStore, blocks common.Range[uint64], hashScheme bool, stopCallback func(bool) bool) error {
|
||||||
if err := db.DeleteRange(filterMapBlockLVKey(blocks.First()), filterMapBlockLVKey(blocks.AfterLast())); err != nil {
|
return SafeDeleteRange(db, filterMapBlockLVKey(blocks.First()), filterMapBlockLVKey(blocks.AfterLast()), hashScheme, stopCallback)
|
||||||
log.Crit("Failed to delete range of block log value pointers", "err", err)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// FilterMapsRange is a storage representation of the block range covered by the
|
// FilterMapsRange is a storage representation of the block range covered by the
|
||||||
|
|
@ -485,22 +479,22 @@ func DeleteFilterMapsRange(db ethdb.KeyValueWriter) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// deletePrefixRange deletes everything with the given prefix from the database.
|
// deletePrefixRange deletes everything with the given prefix from the database.
|
||||||
func deletePrefixRange(db ethdb.KeyValueRangeDeleter, prefix []byte) error {
|
func deletePrefixRange(db ethdb.KeyValueStore, prefix []byte, hashScheme bool, stopCallback func(bool) bool) error {
|
||||||
end := bytes.Clone(prefix)
|
end := bytes.Clone(prefix)
|
||||||
end[len(end)-1]++
|
end[len(end)-1]++
|
||||||
return db.DeleteRange(prefix, end)
|
return SafeDeleteRange(db, prefix, end, hashScheme, stopCallback)
|
||||||
}
|
}
|
||||||
|
|
||||||
// DeleteFilterMapsDb removes the entire filter maps database
|
// DeleteFilterMapsDb removes the entire filter maps database
|
||||||
func DeleteFilterMapsDb(db ethdb.KeyValueRangeDeleter) error {
|
func DeleteFilterMapsDb(db ethdb.KeyValueStore, hashScheme bool, stopCallback func(bool) bool) error {
|
||||||
return deletePrefixRange(db, []byte(filterMapsPrefix))
|
return deletePrefixRange(db, []byte(filterMapsPrefix), hashScheme, stopCallback)
|
||||||
}
|
}
|
||||||
|
|
||||||
// DeleteFilterMapsDb removes the old bloombits database and the associated
|
// DeleteBloomBitsDb removes the old bloombits database and the associated
|
||||||
// chain indexer database.
|
// chain indexer database.
|
||||||
func DeleteBloomBitsDb(db ethdb.KeyValueRangeDeleter) error {
|
func DeleteBloomBitsDb(db ethdb.KeyValueStore, hashScheme bool, stopCallback func(bool) bool) error {
|
||||||
if err := deletePrefixRange(db, bloomBitsPrefix); err != nil {
|
if err := deletePrefixRange(db, bloomBitsPrefix, hashScheme, stopCallback); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
return deletePrefixRange(db, bloomBitsIndexPrefix)
|
return deletePrefixRange(db, bloomBitsMetaPrefix, hashScheme, stopCallback)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -20,18 +20,23 @@ import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"maps"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
"slices"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/ethereum/go-ethereum/common"
|
"github.com/ethereum/go-ethereum/common"
|
||||||
|
"github.com/ethereum/go-ethereum/crypto"
|
||||||
"github.com/ethereum/go-ethereum/ethdb"
|
"github.com/ethereum/go-ethereum/ethdb"
|
||||||
"github.com/ethereum/go-ethereum/ethdb/memorydb"
|
"github.com/ethereum/go-ethereum/ethdb/memorydb"
|
||||||
"github.com/ethereum/go-ethereum/log"
|
"github.com/ethereum/go-ethereum/log"
|
||||||
"github.com/olekukonko/tablewriter"
|
"github.com/olekukonko/tablewriter"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var ErrDeleteRangeInterrupted = errors.New("safe delete range operation interrupted")
|
||||||
|
|
||||||
// freezerdb is a database wrapper that enables ancient chain segment freezing.
|
// freezerdb is a database wrapper that enables ancient chain segment freezing.
|
||||||
type freezerdb struct {
|
type freezerdb struct {
|
||||||
ethdb.KeyValueStore
|
ethdb.KeyValueStore
|
||||||
|
|
@ -375,24 +380,28 @@ func InspectDatabase(db ethdb.Database, keyPrefix, keyStart []byte) error {
|
||||||
accountSnaps stat
|
accountSnaps stat
|
||||||
storageSnaps stat
|
storageSnaps stat
|
||||||
preimages stat
|
preimages stat
|
||||||
filterMaps stat
|
|
||||||
beaconHeaders stat
|
beaconHeaders stat
|
||||||
cliqueSnaps stat
|
cliqueSnaps stat
|
||||||
|
bloomBits stat
|
||||||
|
filterMapRows stat
|
||||||
|
filterMapLastBlock stat
|
||||||
|
filterMapBlockLV stat
|
||||||
|
|
||||||
// Verkle statistics
|
// Verkle statistics
|
||||||
verkleTries stat
|
verkleTries stat
|
||||||
verkleStateLookups stat
|
verkleStateLookups stat
|
||||||
|
|
||||||
// Les statistic
|
|
||||||
chtTrieNodes stat
|
|
||||||
bloomTrieNodes stat
|
|
||||||
|
|
||||||
// Meta- and unaccounted data
|
// Meta- and unaccounted data
|
||||||
metadata stat
|
metadata stat
|
||||||
unaccounted stat
|
unaccounted stat
|
||||||
|
|
||||||
// Totals
|
// Totals
|
||||||
total common.StorageSize
|
total common.StorageSize
|
||||||
|
|
||||||
|
// This map tracks example keys for unaccounted data.
|
||||||
|
// For each unique two-byte prefix, the first unaccounted key encountered
|
||||||
|
// by the iterator will be stored.
|
||||||
|
unaccountedKeys = make(map[[2]byte][]byte)
|
||||||
)
|
)
|
||||||
// Inspect key-value database first.
|
// Inspect key-value database first.
|
||||||
for it.Next() {
|
for it.Next() {
|
||||||
|
|
@ -436,20 +445,24 @@ func InspectDatabase(db ethdb.Database, keyPrefix, keyStart []byte) error {
|
||||||
metadata.Add(size)
|
metadata.Add(size)
|
||||||
case bytes.HasPrefix(key, genesisPrefix) && len(key) == (len(genesisPrefix)+common.HashLength):
|
case bytes.HasPrefix(key, genesisPrefix) && len(key) == (len(genesisPrefix)+common.HashLength):
|
||||||
metadata.Add(size)
|
metadata.Add(size)
|
||||||
case bytes.HasPrefix(key, []byte(filterMapsPrefix)):
|
|
||||||
filterMaps.Add(size)
|
|
||||||
case bytes.HasPrefix(key, skeletonHeaderPrefix) && len(key) == (len(skeletonHeaderPrefix)+8):
|
case bytes.HasPrefix(key, skeletonHeaderPrefix) && len(key) == (len(skeletonHeaderPrefix)+8):
|
||||||
beaconHeaders.Add(size)
|
beaconHeaders.Add(size)
|
||||||
case bytes.HasPrefix(key, CliqueSnapshotPrefix) && len(key) == 7+common.HashLength:
|
case bytes.HasPrefix(key, CliqueSnapshotPrefix) && len(key) == 7+common.HashLength:
|
||||||
cliqueSnaps.Add(size)
|
cliqueSnaps.Add(size)
|
||||||
case bytes.HasPrefix(key, ChtTablePrefix) ||
|
|
||||||
bytes.HasPrefix(key, ChtIndexTablePrefix) ||
|
// new log index
|
||||||
bytes.HasPrefix(key, ChtPrefix): // Canonical hash trie
|
case bytes.HasPrefix(key, filterMapRowPrefix) && len(key) <= len(filterMapRowPrefix)+9:
|
||||||
chtTrieNodes.Add(size)
|
filterMapRows.Add(size)
|
||||||
case bytes.HasPrefix(key, BloomTrieTablePrefix) ||
|
case bytes.HasPrefix(key, filterMapLastBlockPrefix) && len(key) == len(filterMapLastBlockPrefix)+4:
|
||||||
bytes.HasPrefix(key, BloomTrieIndexPrefix) ||
|
filterMapLastBlock.Add(size)
|
||||||
bytes.HasPrefix(key, BloomTriePrefix): // Bloomtrie sub
|
case bytes.HasPrefix(key, filterMapBlockLVPrefix) && len(key) == len(filterMapBlockLVPrefix)+8:
|
||||||
bloomTrieNodes.Add(size)
|
filterMapBlockLV.Add(size)
|
||||||
|
|
||||||
|
// old log index (deprecated)
|
||||||
|
case bytes.HasPrefix(key, bloomBitsPrefix) && len(key) == (len(bloomBitsPrefix)+10+common.HashLength):
|
||||||
|
bloomBits.Add(size)
|
||||||
|
case bytes.HasPrefix(key, bloomBitsMetaPrefix) && len(key) < len(bloomBitsMetaPrefix)+8:
|
||||||
|
bloomBits.Add(size)
|
||||||
|
|
||||||
// Verkle trie data is detected, determine the sub-category
|
// Verkle trie data is detected, determine the sub-category
|
||||||
case bytes.HasPrefix(key, VerklePrefix):
|
case bytes.HasPrefix(key, VerklePrefix):
|
||||||
|
|
@ -468,23 +481,18 @@ func InspectDatabase(db ethdb.Database, keyPrefix, keyStart []byte) error {
|
||||||
default:
|
default:
|
||||||
unaccounted.Add(size)
|
unaccounted.Add(size)
|
||||||
}
|
}
|
||||||
default:
|
|
||||||
var accounted bool
|
// Metadata keys
|
||||||
for _, meta := range [][]byte{
|
case slices.ContainsFunc(knownMetadataKeys, func(x []byte) bool { return bytes.Equal(x, key) }):
|
||||||
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)
|
metadata.Add(size)
|
||||||
accounted = true
|
|
||||||
break
|
default:
|
||||||
}
|
|
||||||
}
|
|
||||||
if !accounted {
|
|
||||||
unaccounted.Add(size)
|
unaccounted.Add(size)
|
||||||
|
if len(key) >= 2 {
|
||||||
|
prefix := [2]byte(key[:2])
|
||||||
|
if _, ok := unaccountedKeys[prefix]; !ok {
|
||||||
|
unaccountedKeys[prefix] = bytes.Clone(key)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
count++
|
count++
|
||||||
|
|
@ -502,7 +510,10 @@ func InspectDatabase(db ethdb.Database, keyPrefix, keyStart []byte) error {
|
||||||
{"Key-Value store", "Block number->hash", numHashPairings.Size(), numHashPairings.Count()},
|
{"Key-Value store", "Block number->hash", numHashPairings.Size(), numHashPairings.Count()},
|
||||||
{"Key-Value store", "Block hash->number", hashNumPairings.Size(), hashNumPairings.Count()},
|
{"Key-Value store", "Block hash->number", hashNumPairings.Size(), hashNumPairings.Count()},
|
||||||
{"Key-Value store", "Transaction index", txLookups.Size(), txLookups.Count()},
|
{"Key-Value store", "Transaction index", txLookups.Size(), txLookups.Count()},
|
||||||
{"Key-Value store", "Log search index", filterMaps.Size(), filterMaps.Count()},
|
{"Key-Value store", "Log index filter-map rows", filterMapRows.Size(), filterMapRows.Count()},
|
||||||
|
{"Key-Value store", "Log index last-block-of-map", filterMapLastBlock.Size(), filterMapLastBlock.Count()},
|
||||||
|
{"Key-Value store", "Log index block-lv", filterMapBlockLV.Size(), filterMapBlockLV.Count()},
|
||||||
|
{"Key-Value store", "Log bloombits (deprecated)", bloomBits.Size(), bloomBits.Count()},
|
||||||
{"Key-Value store", "Contract codes", codes.Size(), codes.Count()},
|
{"Key-Value store", "Contract codes", codes.Size(), codes.Count()},
|
||||||
{"Key-Value store", "Hash trie nodes", legacyTries.Size(), legacyTries.Count()},
|
{"Key-Value store", "Hash trie nodes", legacyTries.Size(), legacyTries.Count()},
|
||||||
{"Key-Value store", "Path trie state lookups", stateLookups.Size(), stateLookups.Count()},
|
{"Key-Value store", "Path trie state lookups", stateLookups.Size(), stateLookups.Count()},
|
||||||
|
|
@ -516,8 +527,6 @@ func InspectDatabase(db ethdb.Database, keyPrefix, keyStart []byte) error {
|
||||||
{"Key-Value store", "Beacon sync headers", beaconHeaders.Size(), beaconHeaders.Count()},
|
{"Key-Value store", "Beacon sync headers", beaconHeaders.Size(), beaconHeaders.Count()},
|
||||||
{"Key-Value store", "Clique snapshots", cliqueSnaps.Size(), cliqueSnaps.Count()},
|
{"Key-Value store", "Clique snapshots", cliqueSnaps.Size(), cliqueSnaps.Count()},
|
||||||
{"Key-Value store", "Singleton metadata", metadata.Size(), metadata.Count()},
|
{"Key-Value store", "Singleton metadata", metadata.Size(), metadata.Count()},
|
||||||
{"Light client", "CHT trie nodes", chtTrieNodes.Size(), chtTrieNodes.Count()},
|
|
||||||
{"Light client", "Bloom trie nodes", bloomTrieNodes.Size(), bloomTrieNodes.Count()},
|
|
||||||
}
|
}
|
||||||
// Inspect all registered append-only file store then.
|
// Inspect all registered append-only file store then.
|
||||||
ancients, err := inspectFreezers(db)
|
ancients, err := inspectFreezers(db)
|
||||||
|
|
@ -543,10 +552,23 @@ func InspectDatabase(db ethdb.Database, keyPrefix, keyStart []byte) error {
|
||||||
|
|
||||||
if unaccounted.size > 0 {
|
if unaccounted.size > 0 {
|
||||||
log.Error("Database contains unaccounted data", "size", unaccounted.size, "count", unaccounted.count)
|
log.Error("Database contains unaccounted data", "size", unaccounted.size, "count", unaccounted.count)
|
||||||
|
for _, e := range slices.SortedFunc(maps.Values(unaccountedKeys), bytes.Compare) {
|
||||||
|
log.Error(fmt.Sprintf(" example key: %x", e))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// This is the list of known 'metadata' keys stored in the databasse.
|
||||||
|
var knownMetadataKeys = [][]byte{
|
||||||
|
databaseVersionKey, headHeaderKey, headBlockKey, headFastBlockKey, headFinalizedBlockKey,
|
||||||
|
lastPivotKey, fastTrieProgressKey, snapshotDisabledKey, SnapshotRootKey, snapshotJournalKey,
|
||||||
|
snapshotGeneratorKey, snapshotRecoveryKey, txIndexTailKey, fastTxLookupLimitKey,
|
||||||
|
uncleanShutdownKey, badBlockKey, transitionStatusKey, skeletonSyncStatusKey,
|
||||||
|
persistentStateIDKey, trieJournalKey, snapshotSyncStatusKey, snapSyncStatusFlagKey,
|
||||||
|
filterMapsRangeKey,
|
||||||
|
}
|
||||||
|
|
||||||
// printChainMetadata prints out chain metadata to stderr.
|
// printChainMetadata prints out chain metadata to stderr.
|
||||||
func printChainMetadata(db ethdb.KeyValueStore) {
|
func printChainMetadata(db ethdb.KeyValueStore) {
|
||||||
fmt.Fprintf(os.Stderr, "Chain metadata\n")
|
fmt.Fprintf(os.Stderr, "Chain metadata\n")
|
||||||
|
|
@ -566,6 +588,7 @@ func ReadChainMetadata(db ethdb.KeyValueStore) [][]string {
|
||||||
}
|
}
|
||||||
return fmt.Sprintf("%d (%#x)", *val, *val)
|
return fmt.Sprintf("%d (%#x)", *val, *val)
|
||||||
}
|
}
|
||||||
|
|
||||||
data := [][]string{
|
data := [][]string{
|
||||||
{"databaseVersion", pp(ReadDatabaseVersion(db))},
|
{"databaseVersion", pp(ReadDatabaseVersion(db))},
|
||||||
{"headBlockHash", fmt.Sprintf("%v", ReadHeadBlockHash(db))},
|
{"headBlockHash", fmt.Sprintf("%v", ReadHeadBlockHash(db))},
|
||||||
|
|
@ -582,5 +605,78 @@ func ReadChainMetadata(db ethdb.KeyValueStore) [][]string {
|
||||||
if b := ReadSkeletonSyncStatus(db); b != nil {
|
if b := ReadSkeletonSyncStatus(db); b != nil {
|
||||||
data = append(data, []string{"SkeletonSyncStatus", string(b)})
|
data = append(data, []string{"SkeletonSyncStatus", string(b)})
|
||||||
}
|
}
|
||||||
|
if fmr, ok, _ := ReadFilterMapsRange(db); ok {
|
||||||
|
data = append(data, []string{"filterMapsRange", fmt.Sprintf("%+v", fmr)})
|
||||||
|
}
|
||||||
return data
|
return data
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SafeDeleteRange deletes all of the keys (and values) in the range
|
||||||
|
// [start,end) (inclusive on start, exclusive on end).
|
||||||
|
// If hashScheme is true then it always uses an iterator and skips hashdb trie
|
||||||
|
// node entries. If it is false and the backing db is pebble db then it uses
|
||||||
|
// the fast native range delete.
|
||||||
|
// In case of fallback mode (hashdb or leveldb) the range deletion might be
|
||||||
|
// very slow depending on the number of entries. In this case stopCallback
|
||||||
|
// is periodically called and if it returns an error then SafeDeleteRange
|
||||||
|
// stops and also returns that error. The callback is not called if native
|
||||||
|
// range delete is used or there are a small number of keys only. The bool
|
||||||
|
// argument passed to the callback is true if enrties have actually been
|
||||||
|
// deleted already.
|
||||||
|
func SafeDeleteRange(db ethdb.KeyValueStore, start, end []byte, hashScheme bool, stopCallback func(bool) bool) error {
|
||||||
|
if !hashScheme {
|
||||||
|
// delete entire range; use fast native range delete on pebble db
|
||||||
|
for {
|
||||||
|
switch err := db.DeleteRange(start, end); {
|
||||||
|
case err == nil:
|
||||||
|
return nil
|
||||||
|
case errors.Is(err, ethdb.ErrTooManyKeys):
|
||||||
|
if stopCallback(true) {
|
||||||
|
return ErrDeleteRangeInterrupted
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
count, deleted, skipped int
|
||||||
|
buff = crypto.NewKeccakState()
|
||||||
|
startTime = time.Now()
|
||||||
|
)
|
||||||
|
|
||||||
|
batch := db.NewBatch()
|
||||||
|
it := db.NewIterator(nil, start)
|
||||||
|
defer func() {
|
||||||
|
it.Release() // it might be replaced during the process
|
||||||
|
log.Debug("SafeDeleteRange finished", "deleted", deleted, "skipped", skipped, "elapsed", common.PrettyDuration(time.Since(startTime)))
|
||||||
|
}()
|
||||||
|
|
||||||
|
for it.Next() && bytes.Compare(end, it.Key()) > 0 {
|
||||||
|
// Prevent deletion for trie nodes in hash mode
|
||||||
|
if len(it.Key()) != 32 || crypto.HashData(buff, it.Value()) != common.BytesToHash(it.Key()) {
|
||||||
|
if err := batch.Delete(it.Key()); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
deleted++
|
||||||
|
} else {
|
||||||
|
skipped++
|
||||||
|
}
|
||||||
|
count++
|
||||||
|
if count > 10000 { // should not block for more than a second
|
||||||
|
if err := batch.Write(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if stopCallback(deleted != 0) {
|
||||||
|
return ErrDeleteRangeInterrupted
|
||||||
|
}
|
||||||
|
start = append(bytes.Clone(it.Key()), 0) // appending a zero gives us the next possible key
|
||||||
|
it.Release()
|
||||||
|
batch = db.NewBatch()
|
||||||
|
it = db.NewIterator(nil, start)
|
||||||
|
count = 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return batch.Write()
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -128,29 +128,22 @@ var (
|
||||||
configPrefix = []byte("ethereum-config-") // config prefix for the db
|
configPrefix = []byte("ethereum-config-") // config prefix for the db
|
||||||
genesisPrefix = []byte("ethereum-genesis-") // genesis state prefix for the db
|
genesisPrefix = []byte("ethereum-genesis-") // genesis state prefix for the db
|
||||||
|
|
||||||
// bloomBitsIndexPrefix is the data table of a chain indexer to track its progress
|
|
||||||
bloomBitsIndexPrefix = []byte("iB")
|
|
||||||
|
|
||||||
ChtPrefix = []byte("chtRootV2-") // ChtPrefix + chtNum (uint64 big endian) -> trie root hash
|
|
||||||
ChtTablePrefix = []byte("cht-")
|
|
||||||
ChtIndexTablePrefix = []byte("chtIndexV2-")
|
|
||||||
|
|
||||||
BloomTriePrefix = []byte("bltRoot-") // BloomTriePrefix + bloomTrieNum (uint64 big endian) -> trie root hash
|
|
||||||
BloomTrieTablePrefix = []byte("blt-")
|
|
||||||
BloomTrieIndexPrefix = []byte("bltIndex-")
|
|
||||||
|
|
||||||
CliqueSnapshotPrefix = []byte("clique-")
|
CliqueSnapshotPrefix = []byte("clique-")
|
||||||
|
|
||||||
BestUpdateKey = []byte("update-") // bigEndian64(syncPeriod) -> RLP(types.LightClientUpdate) (nextCommittee only referenced by root hash)
|
BestUpdateKey = []byte("update-") // bigEndian64(syncPeriod) -> RLP(types.LightClientUpdate) (nextCommittee only referenced by root hash)
|
||||||
FixedCommitteeRootKey = []byte("fixedRoot-") // bigEndian64(syncPeriod) -> committee root hash
|
FixedCommitteeRootKey = []byte("fixedRoot-") // bigEndian64(syncPeriod) -> committee root hash
|
||||||
SyncCommitteeKey = []byte("committee-") // bigEndian64(syncPeriod) -> serialized committee
|
SyncCommitteeKey = []byte("committee-") // bigEndian64(syncPeriod) -> serialized committee
|
||||||
|
|
||||||
|
// new log index
|
||||||
filterMapsPrefix = "fm-"
|
filterMapsPrefix = "fm-"
|
||||||
filterMapsRangeKey = []byte(filterMapsPrefix + "R")
|
filterMapsRangeKey = []byte(filterMapsPrefix + "R")
|
||||||
filterMapRowPrefix = []byte(filterMapsPrefix + "r") // filterMapRowPrefix + mapRowIndex (uint64 big endian) -> filter row
|
filterMapRowPrefix = []byte(filterMapsPrefix + "r") // filterMapRowPrefix + mapRowIndex (uint64 big endian) -> filter row
|
||||||
filterMapLastBlockPrefix = []byte(filterMapsPrefix + "b") // filterMapLastBlockPrefix + mapIndex (uint32 big endian) -> block number (uint64 big endian)
|
filterMapLastBlockPrefix = []byte(filterMapsPrefix + "b") // filterMapLastBlockPrefix + mapIndex (uint32 big endian) -> block number (uint64 big endian)
|
||||||
filterMapBlockLVPrefix = []byte(filterMapsPrefix + "p") // filterMapBlockLVPrefix + num (uint64 big endian) -> log value pointer (uint64 big endian)
|
filterMapBlockLVPrefix = []byte(filterMapsPrefix + "p") // filterMapBlockLVPrefix + num (uint64 big endian) -> log value pointer (uint64 big endian)
|
||||||
|
|
||||||
|
// old log index
|
||||||
|
bloomBitsMetaPrefix = []byte("iB")
|
||||||
|
|
||||||
preimageCounter = metrics.NewRegisteredCounter("db/preimage/total", nil)
|
preimageCounter = metrics.NewRegisteredCounter("db/preimage/total", nil)
|
||||||
preimageHitsCounter = metrics.NewRegisteredCounter("db/preimage/hits", nil)
|
preimageHitsCounter = metrics.NewRegisteredCounter("db/preimage/hits", nil)
|
||||||
preimageMissCounter = metrics.NewRegisteredCounter("db/preimage/miss", nil)
|
preimageMissCounter = metrics.NewRegisteredCounter("db/preimage/miss", nil)
|
||||||
|
|
|
||||||
|
|
@ -655,7 +655,7 @@ func testGenerateWithManyExtraAccounts(t *testing.T, scheme string) {
|
||||||
for i := 0; i < 1000; i++ {
|
for i := 0; i < 1000; i++ {
|
||||||
acc := &types.StateAccount{Balance: uint256.NewInt(uint64(i)), Root: types.EmptyRootHash, CodeHash: types.EmptyCodeHash.Bytes()}
|
acc := &types.StateAccount{Balance: uint256.NewInt(uint64(i)), Root: types.EmptyRootHash, CodeHash: types.EmptyCodeHash.Bytes()}
|
||||||
val, _ := rlp.EncodeToBytes(acc)
|
val, _ := rlp.EncodeToBytes(acc)
|
||||||
key := hashData([]byte(fmt.Sprintf("acc-%d", i)))
|
key := hashData(fmt.Appendf(nil, "acc-%d", i))
|
||||||
rawdb.WriteAccountSnapshot(helper.diskdb, key, val)
|
rawdb.WriteAccountSnapshot(helper.diskdb, key, val)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -329,27 +329,27 @@ func TestAccountIteratorTraversalValues(t *testing.T) {
|
||||||
h = make(map[common.Hash][]byte)
|
h = make(map[common.Hash][]byte)
|
||||||
)
|
)
|
||||||
for i := byte(2); i < 0xff; i++ {
|
for i := byte(2); i < 0xff; i++ {
|
||||||
a[common.Hash{i}] = []byte(fmt.Sprintf("layer-%d, key %d", 0, i))
|
a[common.Hash{i}] = fmt.Appendf(nil, "layer-%d, key %d", 0, i)
|
||||||
if i > 20 && i%2 == 0 {
|
if i > 20 && i%2 == 0 {
|
||||||
b[common.Hash{i}] = []byte(fmt.Sprintf("layer-%d, key %d", 1, i))
|
b[common.Hash{i}] = fmt.Appendf(nil, "layer-%d, key %d", 1, i)
|
||||||
}
|
}
|
||||||
if i%4 == 0 {
|
if i%4 == 0 {
|
||||||
c[common.Hash{i}] = []byte(fmt.Sprintf("layer-%d, key %d", 2, i))
|
c[common.Hash{i}] = fmt.Appendf(nil, "layer-%d, key %d", 2, i)
|
||||||
}
|
}
|
||||||
if i%7 == 0 {
|
if i%7 == 0 {
|
||||||
d[common.Hash{i}] = []byte(fmt.Sprintf("layer-%d, key %d", 3, i))
|
d[common.Hash{i}] = fmt.Appendf(nil, "layer-%d, key %d", 3, i)
|
||||||
}
|
}
|
||||||
if i%8 == 0 {
|
if i%8 == 0 {
|
||||||
e[common.Hash{i}] = []byte(fmt.Sprintf("layer-%d, key %d", 4, i))
|
e[common.Hash{i}] = fmt.Appendf(nil, "layer-%d, key %d", 4, i)
|
||||||
}
|
}
|
||||||
if i > 50 || i < 85 {
|
if i > 50 || i < 85 {
|
||||||
f[common.Hash{i}] = []byte(fmt.Sprintf("layer-%d, key %d", 5, i))
|
f[common.Hash{i}] = fmt.Appendf(nil, "layer-%d, key %d", 5, i)
|
||||||
}
|
}
|
||||||
if i%64 == 0 {
|
if i%64 == 0 {
|
||||||
g[common.Hash{i}] = []byte(fmt.Sprintf("layer-%d, key %d", 6, i))
|
g[common.Hash{i}] = fmt.Appendf(nil, "layer-%d, key %d", 6, i)
|
||||||
}
|
}
|
||||||
if i%128 == 0 {
|
if i%128 == 0 {
|
||||||
h[common.Hash{i}] = []byte(fmt.Sprintf("layer-%d, key %d", 7, i))
|
h[common.Hash{i}] = fmt.Appendf(nil, "layer-%d, key %d", 7, i)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Assemble a stack of snapshots from the account layers
|
// Assemble a stack of snapshots from the account layers
|
||||||
|
|
@ -428,27 +428,27 @@ func TestStorageIteratorTraversalValues(t *testing.T) {
|
||||||
h = make(map[common.Hash][]byte)
|
h = make(map[common.Hash][]byte)
|
||||||
)
|
)
|
||||||
for i := byte(2); i < 0xff; i++ {
|
for i := byte(2); i < 0xff; i++ {
|
||||||
a[common.Hash{i}] = []byte(fmt.Sprintf("layer-%d, key %d", 0, i))
|
a[common.Hash{i}] = fmt.Appendf(nil, "layer-%d, key %d", 0, i)
|
||||||
if i > 20 && i%2 == 0 {
|
if i > 20 && i%2 == 0 {
|
||||||
b[common.Hash{i}] = []byte(fmt.Sprintf("layer-%d, key %d", 1, i))
|
b[common.Hash{i}] = fmt.Appendf(nil, "layer-%d, key %d", 1, i)
|
||||||
}
|
}
|
||||||
if i%4 == 0 {
|
if i%4 == 0 {
|
||||||
c[common.Hash{i}] = []byte(fmt.Sprintf("layer-%d, key %d", 2, i))
|
c[common.Hash{i}] = fmt.Appendf(nil, "layer-%d, key %d", 2, i)
|
||||||
}
|
}
|
||||||
if i%7 == 0 {
|
if i%7 == 0 {
|
||||||
d[common.Hash{i}] = []byte(fmt.Sprintf("layer-%d, key %d", 3, i))
|
d[common.Hash{i}] = fmt.Appendf(nil, "layer-%d, key %d", 3, i)
|
||||||
}
|
}
|
||||||
if i%8 == 0 {
|
if i%8 == 0 {
|
||||||
e[common.Hash{i}] = []byte(fmt.Sprintf("layer-%d, key %d", 4, i))
|
e[common.Hash{i}] = fmt.Appendf(nil, "layer-%d, key %d", 4, i)
|
||||||
}
|
}
|
||||||
if i > 50 || i < 85 {
|
if i > 50 || i < 85 {
|
||||||
f[common.Hash{i}] = []byte(fmt.Sprintf("layer-%d, key %d", 5, i))
|
f[common.Hash{i}] = fmt.Appendf(nil, "layer-%d, key %d", 5, i)
|
||||||
}
|
}
|
||||||
if i%64 == 0 {
|
if i%64 == 0 {
|
||||||
g[common.Hash{i}] = []byte(fmt.Sprintf("layer-%d, key %d", 6, i))
|
g[common.Hash{i}] = fmt.Appendf(nil, "layer-%d, key %d", 6, i)
|
||||||
}
|
}
|
||||||
if i%128 == 0 {
|
if i%128 == 0 {
|
||||||
h[common.Hash{i}] = []byte(fmt.Sprintf("layer-%d, key %d", 7, i))
|
h[common.Hash{i}] = fmt.Appendf(nil, "layer-%d, key %d", 7, i)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Assemble a stack of snapshots from the account layers
|
// Assemble a stack of snapshots from the account layers
|
||||||
|
|
|
||||||
|
|
@ -600,7 +600,6 @@ func (s *StateDB) getStateObject(addr common.Address) *stateObject {
|
||||||
// Insert into the live set
|
// Insert into the live set
|
||||||
obj := newObject(s, addr, acct)
|
obj := newObject(s, addr, acct)
|
||||||
s.setStateObject(obj)
|
s.setStateObject(obj)
|
||||||
s.AccountLoaded++
|
|
||||||
return obj
|
return obj
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -88,7 +88,8 @@ type blobTxMeta struct {
|
||||||
vhashes []common.Hash // Blob versioned hashes to maintain the lookup table
|
vhashes []common.Hash // Blob versioned hashes to maintain the lookup table
|
||||||
|
|
||||||
id uint64 // Storage ID in the pool's persistent store
|
id uint64 // Storage ID in the pool's persistent store
|
||||||
size uint32 // Byte size in the pool's persistent store
|
storageSize uint32 // Byte size in the pool's persistent store
|
||||||
|
size uint64 // RLP-encoded size of transaction including the attached blob
|
||||||
|
|
||||||
nonce uint64 // Needed to prioritize inclusion order within an account
|
nonce uint64 // Needed to prioritize inclusion order within an account
|
||||||
costCap *uint256.Int // Needed to validate cumulative balance sufficiency
|
costCap *uint256.Int // Needed to validate cumulative balance sufficiency
|
||||||
|
|
@ -108,11 +109,12 @@ type blobTxMeta struct {
|
||||||
|
|
||||||
// newBlobTxMeta retrieves the indexed metadata fields from a blob transaction
|
// newBlobTxMeta retrieves the indexed metadata fields from a blob transaction
|
||||||
// and assembles a helper struct to track in memory.
|
// and assembles a helper struct to track in memory.
|
||||||
func newBlobTxMeta(id uint64, size uint32, tx *types.Transaction) *blobTxMeta {
|
func newBlobTxMeta(id uint64, size uint64, storageSize uint32, tx *types.Transaction) *blobTxMeta {
|
||||||
meta := &blobTxMeta{
|
meta := &blobTxMeta{
|
||||||
hash: tx.Hash(),
|
hash: tx.Hash(),
|
||||||
vhashes: tx.BlobHashes(),
|
vhashes: tx.BlobHashes(),
|
||||||
id: id,
|
id: id,
|
||||||
|
storageSize: storageSize,
|
||||||
size: size,
|
size: size,
|
||||||
nonce: tx.Nonce(),
|
nonce: tx.Nonce(),
|
||||||
costCap: uint256.MustFromBig(tx.Cost()),
|
costCap: uint256.MustFromBig(tx.Cost()),
|
||||||
|
|
@ -480,7 +482,7 @@ func (p *BlobPool) parseTransaction(id uint64, size uint32, blob []byte) error {
|
||||||
return errors.New("missing blob sidecar")
|
return errors.New("missing blob sidecar")
|
||||||
}
|
}
|
||||||
|
|
||||||
meta := newBlobTxMeta(id, size, tx)
|
meta := newBlobTxMeta(id, tx.Size(), size, tx)
|
||||||
if p.lookup.exists(meta.hash) {
|
if p.lookup.exists(meta.hash) {
|
||||||
// This path is only possible after a crash, where deleted items are not
|
// This path is only possible after a crash, where deleted items are not
|
||||||
// removed via the normal shutdown-startup procedure and thus may get
|
// removed via the normal shutdown-startup procedure and thus may get
|
||||||
|
|
@ -507,7 +509,7 @@ func (p *BlobPool) parseTransaction(id uint64, size uint32, blob []byte) error {
|
||||||
p.spent[sender] = new(uint256.Int).Add(p.spent[sender], meta.costCap)
|
p.spent[sender] = new(uint256.Int).Add(p.spent[sender], meta.costCap)
|
||||||
|
|
||||||
p.lookup.track(meta)
|
p.lookup.track(meta)
|
||||||
p.stored += uint64(meta.size)
|
p.stored += uint64(meta.storageSize)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -539,7 +541,7 @@ func (p *BlobPool) recheck(addr common.Address, inclusions map[common.Hash]uint6
|
||||||
ids = append(ids, txs[i].id)
|
ids = append(ids, txs[i].id)
|
||||||
nonces = append(nonces, txs[i].nonce)
|
nonces = append(nonces, txs[i].nonce)
|
||||||
|
|
||||||
p.stored -= uint64(txs[i].size)
|
p.stored -= uint64(txs[i].storageSize)
|
||||||
p.lookup.untrack(txs[i])
|
p.lookup.untrack(txs[i])
|
||||||
|
|
||||||
// Included transactions blobs need to be moved to the limbo
|
// Included transactions blobs need to be moved to the limbo
|
||||||
|
|
@ -580,7 +582,7 @@ func (p *BlobPool) recheck(addr common.Address, inclusions map[common.Hash]uint6
|
||||||
nonces = append(nonces, txs[0].nonce)
|
nonces = append(nonces, txs[0].nonce)
|
||||||
|
|
||||||
p.spent[addr] = new(uint256.Int).Sub(p.spent[addr], txs[0].costCap)
|
p.spent[addr] = new(uint256.Int).Sub(p.spent[addr], txs[0].costCap)
|
||||||
p.stored -= uint64(txs[0].size)
|
p.stored -= uint64(txs[0].storageSize)
|
||||||
p.lookup.untrack(txs[0])
|
p.lookup.untrack(txs[0])
|
||||||
|
|
||||||
// Included transactions blobs need to be moved to the limbo
|
// Included transactions blobs need to be moved to the limbo
|
||||||
|
|
@ -636,7 +638,7 @@ func (p *BlobPool) recheck(addr common.Address, inclusions map[common.Hash]uint6
|
||||||
dropRepeatedMeter.Mark(1)
|
dropRepeatedMeter.Mark(1)
|
||||||
|
|
||||||
p.spent[addr] = new(uint256.Int).Sub(p.spent[addr], txs[i].costCap)
|
p.spent[addr] = new(uint256.Int).Sub(p.spent[addr], txs[i].costCap)
|
||||||
p.stored -= uint64(txs[i].size)
|
p.stored -= uint64(txs[i].storageSize)
|
||||||
p.lookup.untrack(txs[i])
|
p.lookup.untrack(txs[i])
|
||||||
|
|
||||||
if err := p.store.Delete(id); err != nil {
|
if err := p.store.Delete(id); err != nil {
|
||||||
|
|
@ -658,7 +660,7 @@ func (p *BlobPool) recheck(addr common.Address, inclusions map[common.Hash]uint6
|
||||||
nonces = append(nonces, txs[j].nonce)
|
nonces = append(nonces, txs[j].nonce)
|
||||||
|
|
||||||
p.spent[addr] = new(uint256.Int).Sub(p.spent[addr], txs[j].costCap)
|
p.spent[addr] = new(uint256.Int).Sub(p.spent[addr], txs[j].costCap)
|
||||||
p.stored -= uint64(txs[j].size)
|
p.stored -= uint64(txs[j].storageSize)
|
||||||
p.lookup.untrack(txs[j])
|
p.lookup.untrack(txs[j])
|
||||||
}
|
}
|
||||||
txs = txs[:i]
|
txs = txs[:i]
|
||||||
|
|
@ -696,7 +698,7 @@ func (p *BlobPool) recheck(addr common.Address, inclusions map[common.Hash]uint6
|
||||||
nonces = append(nonces, last.nonce)
|
nonces = append(nonces, last.nonce)
|
||||||
|
|
||||||
p.spent[addr] = new(uint256.Int).Sub(p.spent[addr], last.costCap)
|
p.spent[addr] = new(uint256.Int).Sub(p.spent[addr], last.costCap)
|
||||||
p.stored -= uint64(last.size)
|
p.stored -= uint64(last.storageSize)
|
||||||
p.lookup.untrack(last)
|
p.lookup.untrack(last)
|
||||||
}
|
}
|
||||||
if len(txs) == 0 {
|
if len(txs) == 0 {
|
||||||
|
|
@ -736,7 +738,7 @@ func (p *BlobPool) recheck(addr common.Address, inclusions map[common.Hash]uint6
|
||||||
nonces = append(nonces, last.nonce)
|
nonces = append(nonces, last.nonce)
|
||||||
|
|
||||||
p.spent[addr] = new(uint256.Int).Sub(p.spent[addr], last.costCap)
|
p.spent[addr] = new(uint256.Int).Sub(p.spent[addr], last.costCap)
|
||||||
p.stored -= uint64(last.size)
|
p.stored -= uint64(last.storageSize)
|
||||||
p.lookup.untrack(last)
|
p.lookup.untrack(last)
|
||||||
}
|
}
|
||||||
p.index[addr] = txs
|
p.index[addr] = txs
|
||||||
|
|
@ -1002,7 +1004,7 @@ func (p *BlobPool) reinject(addr common.Address, txhash common.Hash) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update the indices and metrics
|
// Update the indices and metrics
|
||||||
meta := newBlobTxMeta(id, p.store.Size(id), tx)
|
meta := newBlobTxMeta(id, tx.Size(), p.store.Size(id), tx)
|
||||||
if _, ok := p.index[addr]; !ok {
|
if _, ok := p.index[addr]; !ok {
|
||||||
if err := p.reserve(addr, true); err != nil {
|
if err := p.reserve(addr, true); err != nil {
|
||||||
log.Warn("Failed to reserve account for blob pool", "tx", tx.Hash(), "from", addr, "err", err)
|
log.Warn("Failed to reserve account for blob pool", "tx", tx.Hash(), "from", addr, "err", err)
|
||||||
|
|
@ -1016,7 +1018,7 @@ func (p *BlobPool) reinject(addr common.Address, txhash common.Hash) error {
|
||||||
p.spent[addr] = new(uint256.Int).Add(p.spent[addr], meta.costCap)
|
p.spent[addr] = new(uint256.Int).Add(p.spent[addr], meta.costCap)
|
||||||
}
|
}
|
||||||
p.lookup.track(meta)
|
p.lookup.track(meta)
|
||||||
p.stored += uint64(meta.size)
|
p.stored += uint64(meta.storageSize)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1041,7 +1043,7 @@ func (p *BlobPool) SetGasTip(tip *big.Int) {
|
||||||
nonces = []uint64{tx.nonce}
|
nonces = []uint64{tx.nonce}
|
||||||
)
|
)
|
||||||
p.spent[addr] = new(uint256.Int).Sub(p.spent[addr], txs[i].costCap)
|
p.spent[addr] = new(uint256.Int).Sub(p.spent[addr], txs[i].costCap)
|
||||||
p.stored -= uint64(tx.size)
|
p.stored -= uint64(tx.storageSize)
|
||||||
p.lookup.untrack(tx)
|
p.lookup.untrack(tx)
|
||||||
txs[i] = nil
|
txs[i] = nil
|
||||||
|
|
||||||
|
|
@ -1051,7 +1053,7 @@ func (p *BlobPool) SetGasTip(tip *big.Int) {
|
||||||
nonces = append(nonces, tx.nonce)
|
nonces = append(nonces, tx.nonce)
|
||||||
|
|
||||||
p.spent[addr] = new(uint256.Int).Sub(p.spent[addr], tx.costCap)
|
p.spent[addr] = new(uint256.Int).Sub(p.spent[addr], tx.costCap)
|
||||||
p.stored -= uint64(tx.size)
|
p.stored -= uint64(tx.storageSize)
|
||||||
p.lookup.untrack(tx)
|
p.lookup.untrack(tx)
|
||||||
txs[i+1+j] = nil
|
txs[i+1+j] = nil
|
||||||
}
|
}
|
||||||
|
|
@ -1236,6 +1238,25 @@ func (p *BlobPool) GetRLP(hash common.Hash) []byte {
|
||||||
return p.getRLP(hash)
|
return p.getRLP(hash)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetMetadata returns the transaction type and transaction size with the
|
||||||
|
// given transaction hash.
|
||||||
|
//
|
||||||
|
// The size refers the length of the 'rlp encoding' of a blob transaction
|
||||||
|
// including the attached blobs.
|
||||||
|
func (p *BlobPool) GetMetadata(hash common.Hash) *txpool.TxMetadata {
|
||||||
|
p.lock.RLock()
|
||||||
|
defer p.lock.RUnlock()
|
||||||
|
|
||||||
|
size, ok := p.lookup.sizeOfTx(hash)
|
||||||
|
if !ok {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return &txpool.TxMetadata{
|
||||||
|
Type: types.BlobTxType,
|
||||||
|
Size: size,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// GetBlobs returns a number of blobs are proofs for the given versioned hashes.
|
// GetBlobs returns a number of blobs are proofs for the given versioned hashes.
|
||||||
// This is a utility method for the engine API, enabling consensus clients to
|
// This is a utility method for the engine API, enabling consensus clients to
|
||||||
// retrieve blobs from the pools directly instead of the network.
|
// retrieve blobs from the pools directly instead of the network.
|
||||||
|
|
@ -1375,7 +1396,7 @@ func (p *BlobPool) add(tx *types.Transaction) (err error) {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
meta := newBlobTxMeta(id, p.store.Size(id), tx)
|
meta := newBlobTxMeta(id, tx.Size(), p.store.Size(id), tx)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
next = p.state.GetNonce(from)
|
next = p.state.GetNonce(from)
|
||||||
|
|
@ -1403,7 +1424,7 @@ func (p *BlobPool) add(tx *types.Transaction) (err error) {
|
||||||
|
|
||||||
p.lookup.untrack(prev)
|
p.lookup.untrack(prev)
|
||||||
p.lookup.track(meta)
|
p.lookup.track(meta)
|
||||||
p.stored += uint64(meta.size) - uint64(prev.size)
|
p.stored += uint64(meta.storageSize) - uint64(prev.storageSize)
|
||||||
} else {
|
} else {
|
||||||
// Transaction extends previously scheduled ones
|
// Transaction extends previously scheduled ones
|
||||||
p.index[from] = append(p.index[from], meta)
|
p.index[from] = append(p.index[from], meta)
|
||||||
|
|
@ -1413,7 +1434,7 @@ func (p *BlobPool) add(tx *types.Transaction) (err error) {
|
||||||
}
|
}
|
||||||
p.spent[from] = new(uint256.Int).Add(p.spent[from], meta.costCap)
|
p.spent[from] = new(uint256.Int).Add(p.spent[from], meta.costCap)
|
||||||
p.lookup.track(meta)
|
p.lookup.track(meta)
|
||||||
p.stored += uint64(meta.size)
|
p.stored += uint64(meta.storageSize)
|
||||||
}
|
}
|
||||||
// Recompute the rolling eviction fields. In case of a replacement, this will
|
// Recompute the rolling eviction fields. In case of a replacement, this will
|
||||||
// recompute all subsequent fields. In case of an append, this will only do
|
// recompute all subsequent fields. In case of an append, this will only do
|
||||||
|
|
@ -1500,7 +1521,7 @@ func (p *BlobPool) drop() {
|
||||||
p.index[from] = txs
|
p.index[from] = txs
|
||||||
p.spent[from] = new(uint256.Int).Sub(p.spent[from], drop.costCap)
|
p.spent[from] = new(uint256.Int).Sub(p.spent[from], drop.costCap)
|
||||||
}
|
}
|
||||||
p.stored -= uint64(drop.size)
|
p.stored -= uint64(drop.storageSize)
|
||||||
p.lookup.untrack(drop)
|
p.lookup.untrack(drop)
|
||||||
|
|
||||||
// Remove the transaction from the pool's eviction heap:
|
// Remove the transaction from the pool's eviction heap:
|
||||||
|
|
|
||||||
|
|
@ -376,7 +376,7 @@ func verifyPoolInternals(t *testing.T, pool *BlobPool) {
|
||||||
var stored uint64
|
var stored uint64
|
||||||
for _, txs := range pool.index {
|
for _, txs := range pool.index {
|
||||||
for _, tx := range txs {
|
for _, tx := range txs {
|
||||||
stored += uint64(tx.size)
|
stored += uint64(tx.storageSize)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if pool.stored != stored {
|
if pool.stored != stored {
|
||||||
|
|
@ -1553,6 +1553,16 @@ func TestAdd(t *testing.T) {
|
||||||
if err := pool.add(signed); !errors.Is(err, add.err) {
|
if err := pool.add(signed); !errors.Is(err, add.err) {
|
||||||
t.Errorf("test %d, tx %d: adding transaction error mismatch: have %v, want %v", i, j, err, add.err)
|
t.Errorf("test %d, tx %d: adding transaction error mismatch: have %v, want %v", i, j, err, add.err)
|
||||||
}
|
}
|
||||||
|
if add.err == nil {
|
||||||
|
size, exist := pool.lookup.sizeOfTx(signed.Hash())
|
||||||
|
if !exist {
|
||||||
|
t.Errorf("test %d, tx %d: failed to lookup transaction's size", i, j)
|
||||||
|
}
|
||||||
|
if size != signed.Size() {
|
||||||
|
t.Errorf("test %d, tx %d: transaction's size mismatches: have %v, want %v",
|
||||||
|
i, j, size, signed.Size())
|
||||||
|
}
|
||||||
|
}
|
||||||
verifyPoolInternals(t, pool)
|
verifyPoolInternals(t, pool)
|
||||||
}
|
}
|
||||||
verifyPoolInternals(t, pool)
|
verifyPoolInternals(t, pool)
|
||||||
|
|
|
||||||
|
|
@ -146,7 +146,7 @@ func TestPriceHeapSorting(t *testing.T) {
|
||||||
)
|
)
|
||||||
index[addr] = []*blobTxMeta{{
|
index[addr] = []*blobTxMeta{{
|
||||||
id: uint64(j),
|
id: uint64(j),
|
||||||
size: 128 * 1024,
|
storageSize: 128 * 1024,
|
||||||
nonce: 0,
|
nonce: 0,
|
||||||
execTipCap: execTip,
|
execTipCap: execTip,
|
||||||
execFeeCap: execFee,
|
execFeeCap: execFee,
|
||||||
|
|
@ -205,7 +205,7 @@ func benchmarkPriceHeapReinit(b *testing.B, datacap uint64) {
|
||||||
)
|
)
|
||||||
index[addr] = []*blobTxMeta{{
|
index[addr] = []*blobTxMeta{{
|
||||||
id: uint64(i),
|
id: uint64(i),
|
||||||
size: 128 * 1024,
|
storageSize: 128 * 1024,
|
||||||
nonce: 0,
|
nonce: 0,
|
||||||
execTipCap: execTip,
|
execTipCap: execTip,
|
||||||
execFeeCap: execFee,
|
execFeeCap: execFee,
|
||||||
|
|
@ -281,7 +281,7 @@ func benchmarkPriceHeapOverflow(b *testing.B, datacap uint64) {
|
||||||
)
|
)
|
||||||
index[addr] = []*blobTxMeta{{
|
index[addr] = []*blobTxMeta{{
|
||||||
id: uint64(i),
|
id: uint64(i),
|
||||||
size: 128 * 1024,
|
storageSize: 128 * 1024,
|
||||||
nonce: 0,
|
nonce: 0,
|
||||||
execTipCap: execTip,
|
execTipCap: execTip,
|
||||||
execFeeCap: execFee,
|
execFeeCap: execFee,
|
||||||
|
|
@ -312,7 +312,7 @@ func benchmarkPriceHeapOverflow(b *testing.B, datacap uint64) {
|
||||||
)
|
)
|
||||||
metas[i] = &blobTxMeta{
|
metas[i] = &blobTxMeta{
|
||||||
id: uint64(int(blobs) + i),
|
id: uint64(int(blobs) + i),
|
||||||
size: 128 * 1024,
|
storageSize: 128 * 1024,
|
||||||
nonce: 0,
|
nonce: 0,
|
||||||
execTipCap: execTip,
|
execTipCap: execTip,
|
||||||
execFeeCap: execFee,
|
execFeeCap: execFee,
|
||||||
|
|
|
||||||
|
|
@ -20,18 +20,24 @@ import (
|
||||||
"github.com/ethereum/go-ethereum/common"
|
"github.com/ethereum/go-ethereum/common"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
type txMetadata struct {
|
||||||
|
id uint64 // the billy id of transction
|
||||||
|
size uint64 // the RLP encoded size of transaction (blobs are included)
|
||||||
|
}
|
||||||
|
|
||||||
// lookup maps blob versioned hashes to transaction hashes that include them,
|
// lookup maps blob versioned hashes to transaction hashes that include them,
|
||||||
// and transaction hashes to billy entries that include them.
|
// transaction hashes to billy entries that include them, transaction hashes
|
||||||
|
// to the transaction size
|
||||||
type lookup struct {
|
type lookup struct {
|
||||||
blobIndex map[common.Hash]map[common.Hash]struct{}
|
blobIndex map[common.Hash]map[common.Hash]struct{}
|
||||||
txIndex map[common.Hash]uint64
|
txIndex map[common.Hash]*txMetadata
|
||||||
}
|
}
|
||||||
|
|
||||||
// newLookup creates a new index for tracking blob to tx; and tx to billy mappings.
|
// newLookup creates a new index for tracking blob to tx; and tx to billy mappings.
|
||||||
func newLookup() *lookup {
|
func newLookup() *lookup {
|
||||||
return &lookup{
|
return &lookup{
|
||||||
blobIndex: make(map[common.Hash]map[common.Hash]struct{}),
|
blobIndex: make(map[common.Hash]map[common.Hash]struct{}),
|
||||||
txIndex: make(map[common.Hash]uint64),
|
txIndex: make(map[common.Hash]*txMetadata),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -43,8 +49,11 @@ func (l *lookup) exists(txhash common.Hash) bool {
|
||||||
|
|
||||||
// storeidOfTx returns the datastore storage item id of a transaction.
|
// storeidOfTx returns the datastore storage item id of a transaction.
|
||||||
func (l *lookup) storeidOfTx(txhash common.Hash) (uint64, bool) {
|
func (l *lookup) storeidOfTx(txhash common.Hash) (uint64, bool) {
|
||||||
id, ok := l.txIndex[txhash]
|
meta, ok := l.txIndex[txhash]
|
||||||
return id, ok
|
if !ok {
|
||||||
|
return 0, false
|
||||||
|
}
|
||||||
|
return meta.id, true
|
||||||
}
|
}
|
||||||
|
|
||||||
// storeidOfBlob returns the datastore storage item id of a blob.
|
// storeidOfBlob returns the datastore storage item id of a blob.
|
||||||
|
|
@ -61,6 +70,15 @@ func (l *lookup) storeidOfBlob(vhash common.Hash) (uint64, bool) {
|
||||||
return 0, false // Weird, don't choke
|
return 0, false // Weird, don't choke
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// sizeOfTx returns the RLP-encoded size of transaction
|
||||||
|
func (l *lookup) sizeOfTx(txhash common.Hash) (uint64, bool) {
|
||||||
|
meta, ok := l.txIndex[txhash]
|
||||||
|
if !ok {
|
||||||
|
return 0, false
|
||||||
|
}
|
||||||
|
return meta.size, true
|
||||||
|
}
|
||||||
|
|
||||||
// track inserts a new set of mappings from blob versioned hashes to transaction
|
// track inserts a new set of mappings from blob versioned hashes to transaction
|
||||||
// hashes; and from transaction hashes to datastore storage item ids.
|
// hashes; and from transaction hashes to datastore storage item ids.
|
||||||
func (l *lookup) track(tx *blobTxMeta) {
|
func (l *lookup) track(tx *blobTxMeta) {
|
||||||
|
|
@ -71,8 +89,11 @@ func (l *lookup) track(tx *blobTxMeta) {
|
||||||
}
|
}
|
||||||
l.blobIndex[vhash][tx.hash] = struct{}{} // may be double mapped if a tx contains the same blob twice
|
l.blobIndex[vhash][tx.hash] = struct{}{} // may be double mapped if a tx contains the same blob twice
|
||||||
}
|
}
|
||||||
// Map the transaction hash to the datastore id
|
// Map the transaction hash to the datastore id and RLP-encoded transaction size
|
||||||
l.txIndex[tx.hash] = tx.id
|
l.txIndex[tx.hash] = &txMetadata{
|
||||||
|
id: tx.id,
|
||||||
|
size: tx.size,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// untrack removes a set of mappings from blob versioned hashes to transaction
|
// untrack removes a set of mappings from blob versioned hashes to transaction
|
||||||
|
|
|
||||||
|
|
@ -618,7 +618,7 @@ func (pool *LegacyPool) checkDelegationLimit(tx *types.Transaction) error {
|
||||||
from, _ := types.Sender(pool.signer, tx) // validated
|
from, _ := types.Sender(pool.signer, tx) // validated
|
||||||
|
|
||||||
// Short circuit if the sender has neither delegation nor pending delegation.
|
// Short circuit if the sender has neither delegation nor pending delegation.
|
||||||
if pool.currentState.GetCodeHash(from) == types.EmptyCodeHash && len(pool.all.auths[from]) == 0 {
|
if pool.currentState.GetCodeHash(from) == types.EmptyCodeHash && pool.all.delegationTxsCount(from) == 0 {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
pending := pool.pending[from]
|
pending := pool.pending[from]
|
||||||
|
|
@ -1035,6 +1035,19 @@ func (pool *LegacyPool) GetRLP(hash common.Hash) []byte {
|
||||||
return encoded
|
return encoded
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetMetadata returns the transaction type and transaction size with the
|
||||||
|
// given transaction hash.
|
||||||
|
func (pool *LegacyPool) GetMetadata(hash common.Hash) *txpool.TxMetadata {
|
||||||
|
tx := pool.all.Get(hash)
|
||||||
|
if tx == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return &txpool.TxMetadata{
|
||||||
|
Type: tx.Type(),
|
||||||
|
Size: tx.Size(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// GetBlobs is not supported by the legacy transaction pool, it is just here to
|
// GetBlobs is not supported by the legacy transaction pool, it is just here to
|
||||||
// implement the txpool.SubPool interface.
|
// implement the txpool.SubPool interface.
|
||||||
func (pool *LegacyPool) GetBlobs(vhashes []common.Hash) ([]*kzg4844.Blob, []*kzg4844.Proof) {
|
func (pool *LegacyPool) GetBlobs(vhashes []common.Hash) ([]*kzg4844.Blob, []*kzg4844.Proof) {
|
||||||
|
|
@ -1849,6 +1862,13 @@ func (t *lookup) removeAuthorities(tx *types.Transaction) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// delegationTxsCount returns the number of pending authorizations for the specified address.
|
||||||
|
func (t *lookup) delegationTxsCount(addr common.Address) int {
|
||||||
|
t.lock.RLock()
|
||||||
|
defer t.lock.RUnlock()
|
||||||
|
return len(t.auths[addr])
|
||||||
|
}
|
||||||
|
|
||||||
// numSlots calculates the number of slots needed for a single transaction.
|
// numSlots calculates the number of slots needed for a single transaction.
|
||||||
func numSlots(tx *types.Transaction) int {
|
func numSlots(tx *types.Transaction) int {
|
||||||
return int((tx.Size() + txSlotSize - 1) / txSlotSize)
|
return int((tx.Size() + txSlotSize - 1) / txSlotSize)
|
||||||
|
|
|
||||||
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)
|
OnlyBlobTxs bool // Return only blob transactions (block blob-space filling)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TxMetadata denotes the metadata of a transaction.
|
||||||
|
type TxMetadata struct {
|
||||||
|
Type uint8 // The type of the transaction
|
||||||
|
Size uint64 // The length of the 'rlp encoding' of a transaction
|
||||||
|
}
|
||||||
|
|
||||||
// SubPool represents a specialized transaction pool that lives on its own (e.g.
|
// SubPool represents a specialized transaction pool that lives on its own (e.g.
|
||||||
// blob pool). Since independent of how many specialized pools we have, they do
|
// blob pool). Since independent of how many specialized pools we have, they do
|
||||||
// need to be updated in lockstep and assemble into one coherent view for block
|
// need to be updated in lockstep and assemble into one coherent view for block
|
||||||
|
|
@ -127,6 +133,10 @@ type SubPool interface {
|
||||||
// GetRLP returns a RLP-encoded transaction if it is contained in the pool.
|
// GetRLP returns a RLP-encoded transaction if it is contained in the pool.
|
||||||
GetRLP(hash common.Hash) []byte
|
GetRLP(hash common.Hash) []byte
|
||||||
|
|
||||||
|
// GetMetadata returns the transaction type and transaction size with the
|
||||||
|
// given transaction hash.
|
||||||
|
GetMetadata(hash common.Hash) *TxMetadata
|
||||||
|
|
||||||
// GetBlobs returns a number of blobs are proofs for the given versioned hashes.
|
// GetBlobs returns a number of blobs are proofs for the given versioned hashes.
|
||||||
// This is a utility method for the engine API, enabling consensus clients to
|
// This is a utility method for the engine API, enabling consensus clients to
|
||||||
// retrieve blobs from the pools directly instead of the network.
|
// retrieve blobs from the pools directly instead of the network.
|
||||||
|
|
|
||||||
|
|
@ -24,11 +24,13 @@ import (
|
||||||
|
|
||||||
"github.com/ethereum/go-ethereum/common"
|
"github.com/ethereum/go-ethereum/common"
|
||||||
"github.com/ethereum/go-ethereum/core"
|
"github.com/ethereum/go-ethereum/core"
|
||||||
|
"github.com/ethereum/go-ethereum/core/state"
|
||||||
"github.com/ethereum/go-ethereum/core/types"
|
"github.com/ethereum/go-ethereum/core/types"
|
||||||
"github.com/ethereum/go-ethereum/crypto/kzg4844"
|
"github.com/ethereum/go-ethereum/crypto/kzg4844"
|
||||||
"github.com/ethereum/go-ethereum/event"
|
"github.com/ethereum/go-ethereum/event"
|
||||||
"github.com/ethereum/go-ethereum/log"
|
"github.com/ethereum/go-ethereum/log"
|
||||||
"github.com/ethereum/go-ethereum/metrics"
|
"github.com/ethereum/go-ethereum/metrics"
|
||||||
|
"github.com/ethereum/go-ethereum/params"
|
||||||
)
|
)
|
||||||
|
|
||||||
// TxStatus is the current status of a transaction as seen by the pool.
|
// TxStatus is the current status of a transaction as seen by the pool.
|
||||||
|
|
@ -53,11 +55,17 @@ var (
|
||||||
// BlockChain defines the minimal set of methods needed to back a tx pool with
|
// BlockChain defines the minimal set of methods needed to back a tx pool with
|
||||||
// a chain. Exists to allow mocking the live chain out of tests.
|
// a chain. Exists to allow mocking the live chain out of tests.
|
||||||
type BlockChain interface {
|
type BlockChain interface {
|
||||||
|
// Config retrieves the chain's fork configuration.
|
||||||
|
Config() *params.ChainConfig
|
||||||
|
|
||||||
// CurrentBlock returns the current head of the chain.
|
// CurrentBlock returns the current head of the chain.
|
||||||
CurrentBlock() *types.Header
|
CurrentBlock() *types.Header
|
||||||
|
|
||||||
// SubscribeChainHeadEvent subscribes to new blocks being added to the chain.
|
// SubscribeChainHeadEvent subscribes to new blocks being added to the chain.
|
||||||
SubscribeChainHeadEvent(ch chan<- core.ChainHeadEvent) event.Subscription
|
SubscribeChainHeadEvent(ch chan<- core.ChainHeadEvent) event.Subscription
|
||||||
|
|
||||||
|
// StateAt returns a state database for a given root hash (generally the head).
|
||||||
|
StateAt(root common.Hash) (*state.StateDB, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
// TxPool is an aggregator for various transaction specific pools, collectively
|
// TxPool is an aggregator for various transaction specific pools, collectively
|
||||||
|
|
@ -67,6 +75,11 @@ type BlockChain interface {
|
||||||
// resource constraints.
|
// resource constraints.
|
||||||
type TxPool struct {
|
type TxPool struct {
|
||||||
subpools []SubPool // List of subpools for specialized transaction handling
|
subpools []SubPool // List of subpools for specialized transaction handling
|
||||||
|
chain BlockChain
|
||||||
|
signer types.Signer
|
||||||
|
|
||||||
|
stateLock sync.RWMutex // The lock for protecting state instance
|
||||||
|
state *state.StateDB // Current state at the blockchain head
|
||||||
|
|
||||||
reservations map[common.Address]SubPool // Map with the account to pool reservations
|
reservations map[common.Address]SubPool // Map with the account to pool reservations
|
||||||
reserveLock sync.Mutex // Lock protecting the account reservations
|
reserveLock sync.Mutex // Lock protecting the account reservations
|
||||||
|
|
@ -86,8 +99,21 @@ func New(gasTip uint64, chain BlockChain, subpools []SubPool) (*TxPool, error) {
|
||||||
// during initialization.
|
// during initialization.
|
||||||
head := chain.CurrentBlock()
|
head := chain.CurrentBlock()
|
||||||
|
|
||||||
|
// Initialize the state with head block, or fallback to empty one in
|
||||||
|
// case the head state is not available (might occur when node is not
|
||||||
|
// fully synced).
|
||||||
|
statedb, err := chain.StateAt(head.Root)
|
||||||
|
if err != nil {
|
||||||
|
statedb, err = chain.StateAt(types.EmptyRootHash)
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
pool := &TxPool{
|
pool := &TxPool{
|
||||||
subpools: subpools,
|
subpools: subpools,
|
||||||
|
chain: chain,
|
||||||
|
signer: types.LatestSigner(chain.Config()),
|
||||||
|
state: statedb,
|
||||||
reservations: make(map[common.Address]SubPool),
|
reservations: make(map[common.Address]SubPool),
|
||||||
quit: make(chan chan error),
|
quit: make(chan chan error),
|
||||||
term: make(chan struct{}),
|
term: make(chan struct{}),
|
||||||
|
|
@ -101,7 +127,7 @@ func New(gasTip uint64, chain BlockChain, subpools []SubPool) (*TxPool, error) {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
go pool.loop(head, chain)
|
go pool.loop(head)
|
||||||
return pool, nil
|
return pool, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -179,14 +205,14 @@ func (p *TxPool) Close() error {
|
||||||
// loop is the transaction pool's main event loop, waiting for and reacting to
|
// loop is the transaction pool's main event loop, waiting for and reacting to
|
||||||
// outside blockchain events as well as for various reporting and transaction
|
// outside blockchain events as well as for various reporting and transaction
|
||||||
// eviction events.
|
// eviction events.
|
||||||
func (p *TxPool) loop(head *types.Header, chain BlockChain) {
|
func (p *TxPool) loop(head *types.Header) {
|
||||||
// Close the termination marker when the pool stops
|
// Close the termination marker when the pool stops
|
||||||
defer close(p.term)
|
defer close(p.term)
|
||||||
|
|
||||||
// Subscribe to chain head events to trigger subpool resets
|
// Subscribe to chain head events to trigger subpool resets
|
||||||
var (
|
var (
|
||||||
newHeadCh = make(chan core.ChainHeadEvent)
|
newHeadCh = make(chan core.ChainHeadEvent)
|
||||||
newHeadSub = chain.SubscribeChainHeadEvent(newHeadCh)
|
newHeadSub = p.chain.SubscribeChainHeadEvent(newHeadCh)
|
||||||
)
|
)
|
||||||
defer newHeadSub.Unsubscribe()
|
defer newHeadSub.Unsubscribe()
|
||||||
|
|
||||||
|
|
@ -219,6 +245,14 @@ func (p *TxPool) loop(head *types.Header, chain BlockChain) {
|
||||||
// Try to inject a busy marker and start a reset if successful
|
// Try to inject a busy marker and start a reset if successful
|
||||||
select {
|
select {
|
||||||
case resetBusy <- struct{}{}:
|
case resetBusy <- struct{}{}:
|
||||||
|
statedb, err := p.chain.StateAt(newHead.Root)
|
||||||
|
if err != nil {
|
||||||
|
log.Crit("Failed to reset txpool state", "err", err)
|
||||||
|
}
|
||||||
|
p.stateLock.Lock()
|
||||||
|
p.state = statedb
|
||||||
|
p.stateLock.Unlock()
|
||||||
|
|
||||||
// Busy marker injected, start a new subpool reset
|
// Busy marker injected, start a new subpool reset
|
||||||
go func(oldHead, newHead *types.Header) {
|
go func(oldHead, newHead *types.Header) {
|
||||||
for _, subpool := range p.subpools {
|
for _, subpool := range p.subpools {
|
||||||
|
|
@ -320,6 +354,17 @@ func (p *TxPool) GetRLP(hash common.Hash) []byte {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetMetadata returns the transaction type and transaction size with the given
|
||||||
|
// hash.
|
||||||
|
func (p *TxPool) GetMetadata(hash common.Hash) *TxMetadata {
|
||||||
|
for _, subpool := range p.subpools {
|
||||||
|
if meta := subpool.GetMetadata(hash); meta != nil {
|
||||||
|
return meta
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
// GetBlobs returns a number of blobs are proofs for the given versioned hashes.
|
// GetBlobs returns a number of blobs are proofs for the given versioned hashes.
|
||||||
// This is a utility method for the engine API, enabling consensus clients to
|
// This is a utility method for the engine API, enabling consensus clients to
|
||||||
// retrieve blobs from the pools directly instead of the network.
|
// retrieve blobs from the pools directly instead of the network.
|
||||||
|
|
@ -339,6 +384,20 @@ func (p *TxPool) GetBlobs(vhashes []common.Hash) ([]*kzg4844.Blob, []*kzg4844.Pr
|
||||||
// ValidateTxBasics checks whether a transaction is valid according to the consensus
|
// ValidateTxBasics checks whether a transaction is valid according to the consensus
|
||||||
// rules, but does not check state-dependent validation such as sufficient balance.
|
// rules, but does not check state-dependent validation such as sufficient balance.
|
||||||
func (p *TxPool) ValidateTxBasics(tx *types.Transaction) error {
|
func (p *TxPool) ValidateTxBasics(tx *types.Transaction) error {
|
||||||
|
addr, err := types.Sender(p.signer, tx)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
// Reject transactions with stale nonce. Gapped-nonce future transactions
|
||||||
|
// are considered valid and will be handled by the subpool according to its
|
||||||
|
// internal policy.
|
||||||
|
p.stateLock.RLock()
|
||||||
|
nonce := p.state.GetNonce(addr)
|
||||||
|
p.stateLock.RUnlock()
|
||||||
|
|
||||||
|
if nonce > tx.Nonce() {
|
||||||
|
return core.ErrNonceTooLow
|
||||||
|
}
|
||||||
for _, subpool := range p.subpools {
|
for _, subpool := range p.subpools {
|
||||||
if subpool.Filter(tx) {
|
if subpool.Filter(tx) {
|
||||||
return subpool.ValidateTxBasics(tx)
|
return subpool.ValidateTxBasics(tx)
|
||||||
|
|
@ -418,9 +477,9 @@ func (p *TxPool) SubscribeTransactions(ch chan<- core.NewTxsEvent, reorgs bool)
|
||||||
return p.subs.Track(event.JoinSubscriptions(subs...))
|
return p.subs.Track(event.JoinSubscriptions(subs...))
|
||||||
}
|
}
|
||||||
|
|
||||||
// Nonce returns the next nonce of an account, with all transactions executable
|
// PoolNonce returns the next nonce of an account, with all transactions executable
|
||||||
// by the pool already applied on top.
|
// by the pool already applied on top.
|
||||||
func (p *TxPool) Nonce(addr common.Address) uint64 {
|
func (p *TxPool) PoolNonce(addr common.Address) uint64 {
|
||||||
// Since (for now) accounts are unique to subpools, only one pool will have
|
// Since (for now) accounts are unique to subpools, only one pool will have
|
||||||
// (at max) a non-state nonce. To avoid stateful lookups, just return the
|
// (at max) a non-state nonce. To avoid stateful lookups, just return the
|
||||||
// highest nonce for now.
|
// highest nonce for now.
|
||||||
|
|
@ -433,6 +492,15 @@ func (p *TxPool) Nonce(addr common.Address) uint64 {
|
||||||
return nonce
|
return nonce
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Nonce returns the next nonce of an account at the current chain head. Unlike
|
||||||
|
// PoolNonce, this function does not account for pending executable transactions.
|
||||||
|
func (p *TxPool) Nonce(addr common.Address) uint64 {
|
||||||
|
p.stateLock.RLock()
|
||||||
|
defer p.stateLock.RUnlock()
|
||||||
|
|
||||||
|
return p.state.GetNonce(addr)
|
||||||
|
}
|
||||||
|
|
||||||
// Stats retrieves the current pool stats, namely the number of pending and the
|
// Stats retrieves the current pool stats, namely the number of pending and the
|
||||||
// number of queued (non-executable) transactions.
|
// number of queued (non-executable) transactions.
|
||||||
func (p *TxPool) Stats() (int, int) {
|
func (p *TxPool) Stats() (int, int) {
|
||||||
|
|
@ -508,6 +576,9 @@ func (p *TxPool) Sync() error {
|
||||||
|
|
||||||
// Clear removes all tracked txs from the subpools.
|
// Clear removes all tracked txs from the subpools.
|
||||||
func (p *TxPool) Clear() {
|
func (p *TxPool) Clear() {
|
||||||
|
// Invoke Sync to ensure that txs pending addition don't get added to the pool after
|
||||||
|
// the subpools are subsequently cleared
|
||||||
|
p.Sync()
|
||||||
for _, subpool := range p.subpools {
|
for _, subpool := range p.subpools {
|
||||||
subpool.Clear()
|
subpool.Clear()
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -34,6 +34,7 @@ import (
|
||||||
"github.com/ethereum/go-ethereum/core/txpool"
|
"github.com/ethereum/go-ethereum/core/txpool"
|
||||||
"github.com/ethereum/go-ethereum/core/types"
|
"github.com/ethereum/go-ethereum/core/types"
|
||||||
"github.com/ethereum/go-ethereum/core/vm"
|
"github.com/ethereum/go-ethereum/core/vm"
|
||||||
|
"github.com/ethereum/go-ethereum/eth/ethconfig"
|
||||||
"github.com/ethereum/go-ethereum/eth/gasprice"
|
"github.com/ethereum/go-ethereum/eth/gasprice"
|
||||||
"github.com/ethereum/go-ethereum/eth/tracers"
|
"github.com/ethereum/go-ethereum/eth/tracers"
|
||||||
"github.com/ethereum/go-ethereum/ethdb"
|
"github.com/ethereum/go-ethereum/ethdb"
|
||||||
|
|
@ -91,7 +92,13 @@ func (b *EthAPIBackend) HeaderByNumber(ctx context.Context, number rpc.BlockNumb
|
||||||
}
|
}
|
||||||
return block, nil
|
return block, nil
|
||||||
}
|
}
|
||||||
return b.eth.blockchain.GetHeaderByNumber(uint64(number)), nil
|
var bn uint64
|
||||||
|
if number == rpc.EarliestBlockNumber {
|
||||||
|
bn = b.eth.blockchain.HistoryPruningCutoff()
|
||||||
|
} else {
|
||||||
|
bn = uint64(number)
|
||||||
|
}
|
||||||
|
return b.eth.blockchain.GetHeaderByNumber(bn), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *EthAPIBackend) HeaderByNumberOrHash(ctx context.Context, blockNrOrHash rpc.BlockNumberOrHash) (*types.Header, error) {
|
func (b *EthAPIBackend) HeaderByNumberOrHash(ctx context.Context, blockNrOrHash rpc.BlockNumberOrHash) (*types.Header, error) {
|
||||||
|
|
@ -143,11 +150,27 @@ func (b *EthAPIBackend) BlockByNumber(ctx context.Context, number rpc.BlockNumbe
|
||||||
}
|
}
|
||||||
return b.eth.blockchain.GetBlock(header.Hash(), header.Number.Uint64()), nil
|
return b.eth.blockchain.GetBlock(header.Hash(), header.Number.Uint64()), nil
|
||||||
}
|
}
|
||||||
return b.eth.blockchain.GetBlockByNumber(uint64(number)), nil
|
bn := uint64(number) // the resolved number
|
||||||
|
if number == rpc.EarliestBlockNumber {
|
||||||
|
bn = b.eth.blockchain.HistoryPruningCutoff()
|
||||||
|
}
|
||||||
|
block := b.eth.blockchain.GetBlockByNumber(bn)
|
||||||
|
if block == nil && bn < b.eth.blockchain.HistoryPruningCutoff() {
|
||||||
|
return nil, ðconfig.PrunedHistoryError{}
|
||||||
|
}
|
||||||
|
return block, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *EthAPIBackend) BlockByHash(ctx context.Context, hash common.Hash) (*types.Block, error) {
|
func (b *EthAPIBackend) BlockByHash(ctx context.Context, hash common.Hash) (*types.Block, error) {
|
||||||
return b.eth.blockchain.GetBlockByHash(hash), nil
|
number := b.eth.blockchain.GetBlockNumber(hash)
|
||||||
|
if number == nil {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
block := b.eth.blockchain.GetBlock(hash, *number)
|
||||||
|
if block == nil && *number < b.eth.blockchain.HistoryPruningCutoff() {
|
||||||
|
return nil, ðconfig.PrunedHistoryError{}
|
||||||
|
}
|
||||||
|
return block, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetBody returns body of a block. It does not resolve special block numbers.
|
// GetBody returns body of a block. It does not resolve special block numbers.
|
||||||
|
|
@ -155,10 +178,14 @@ func (b *EthAPIBackend) GetBody(ctx context.Context, hash common.Hash, number rp
|
||||||
if number < 0 || hash == (common.Hash{}) {
|
if number < 0 || hash == (common.Hash{}) {
|
||||||
return nil, errors.New("invalid arguments; expect hash and no special block numbers")
|
return nil, errors.New("invalid arguments; expect hash and no special block numbers")
|
||||||
}
|
}
|
||||||
if body := b.eth.blockchain.GetBody(hash); body != nil {
|
body := b.eth.blockchain.GetBody(hash)
|
||||||
return body, nil
|
if body == nil {
|
||||||
|
if uint64(number) < b.eth.blockchain.HistoryPruningCutoff() {
|
||||||
|
return nil, ð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) {
|
func (b *EthAPIBackend) BlockByNumberOrHash(ctx context.Context, blockNrOrHash rpc.BlockNumberOrHash) (*types.Block, error) {
|
||||||
|
|
@ -175,6 +202,9 @@ func (b *EthAPIBackend) BlockByNumberOrHash(ctx context.Context, blockNrOrHash r
|
||||||
}
|
}
|
||||||
block := b.eth.blockchain.GetBlock(hash, header.Number.Uint64())
|
block := b.eth.blockchain.GetBlock(hash, header.Number.Uint64())
|
||||||
if block == nil {
|
if block == nil {
|
||||||
|
if header.Number.Uint64() < b.eth.blockchain.HistoryPruningCutoff() {
|
||||||
|
return nil, ðconfig.PrunedHistoryError{}
|
||||||
|
}
|
||||||
return nil, errors.New("header found, but block body is missing")
|
return nil, errors.New("header found, but block body is missing")
|
||||||
}
|
}
|
||||||
return block, nil
|
return block, nil
|
||||||
|
|
@ -234,6 +264,10 @@ func (b *EthAPIBackend) StateAndHeaderByNumberOrHash(ctx context.Context, blockN
|
||||||
return nil, nil, errors.New("invalid arguments; neither block nor hash specified")
|
return nil, nil, errors.New("invalid arguments; neither block nor hash specified")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (b *EthAPIBackend) HistoryPruningCutoff() uint64 {
|
||||||
|
return b.eth.blockchain.HistoryPruningCutoff()
|
||||||
|
}
|
||||||
|
|
||||||
func (b *EthAPIBackend) GetReceipts(ctx context.Context, hash common.Hash) (types.Receipts, error) {
|
func (b *EthAPIBackend) GetReceipts(ctx context.Context, hash common.Hash) (types.Receipts, error) {
|
||||||
return b.eth.blockchain.GetReceiptsByHash(hash), nil
|
return b.eth.blockchain.GetReceiptsByHash(hash), nil
|
||||||
}
|
}
|
||||||
|
|
@ -327,7 +361,7 @@ func (b *EthAPIBackend) GetTransaction(ctx context.Context, txHash common.Hash)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *EthAPIBackend) GetPoolNonce(ctx context.Context, addr common.Address) (uint64, error) {
|
func (b *EthAPIBackend) GetPoolNonce(ctx context.Context, addr common.Address) (uint64, error) {
|
||||||
return b.eth.txPool.Nonce(addr), nil
|
return b.eth.txPool.PoolNonce(addr), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *EthAPIBackend) Stats() (runnable int, blocked int) {
|
func (b *EthAPIBackend) Stats() (runnable int, blocked int) {
|
||||||
|
|
|
||||||
|
|
@ -239,9 +239,19 @@ func New(stack *node.Node, config *ethconfig.Config) (*Ethereum, error) {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
fmConfig := filtermaps.Config{History: config.LogHistory, Disabled: config.LogNoHistory, ExportFileName: config.LogExportCheckpoints}
|
fmConfig := filtermaps.Config{
|
||||||
|
History: config.LogHistory,
|
||||||
|
Disabled: config.LogNoHistory,
|
||||||
|
ExportFileName: config.LogExportCheckpoints,
|
||||||
|
HashScheme: scheme == rawdb.HashScheme,
|
||||||
|
}
|
||||||
chainView := eth.newChainView(eth.blockchain.CurrentBlock())
|
chainView := eth.newChainView(eth.blockchain.CurrentBlock())
|
||||||
eth.filterMaps = filtermaps.NewFilterMaps(chainDb, chainView, 0, 0, filtermaps.DefaultParams, fmConfig)
|
historyCutoff := eth.blockchain.HistoryPruningCutoff()
|
||||||
|
var finalBlock uint64
|
||||||
|
if fb := eth.blockchain.CurrentFinalBlock(); fb != nil {
|
||||||
|
finalBlock = fb.Number.Uint64()
|
||||||
|
}
|
||||||
|
eth.filterMaps = filtermaps.NewFilterMaps(chainDb, chainView, historyCutoff, finalBlock, filtermaps.DefaultParams, fmConfig)
|
||||||
eth.closeFilterMaps = make(chan chan struct{})
|
eth.closeFilterMaps = make(chan chan struct{})
|
||||||
|
|
||||||
if config.BlobPool.Datadir != "" {
|
if config.BlobPool.Datadir != "" {
|
||||||
|
|
|
||||||
|
|
@ -275,7 +275,7 @@ func (s *skeleton) startup() {
|
||||||
for {
|
for {
|
||||||
// If the sync cycle terminated or was terminated, propagate up when
|
// If the sync cycle terminated or was terminated, propagate up when
|
||||||
// higher layers request termination. There's no fancy explicit error
|
// higher layers request termination. There's no fancy explicit error
|
||||||
// signalling as the sync loop should never terminate (TM).
|
// signaling as the sync loop should never terminate (TM).
|
||||||
newhead, err := s.sync(head)
|
newhead, err := s.sync(head)
|
||||||
switch {
|
switch {
|
||||||
case err == errSyncLinked:
|
case err == errSyncLinked:
|
||||||
|
|
|
||||||
|
|
@ -90,3 +90,9 @@ var HistoryPrunePoints = map[common.Hash]*HistoryPrunePoint{
|
||||||
BlockHash: common.HexToHash("0x229f6b18ca1552f1d5146deceb5387333f40dc6275aebee3f2c5c4ece07d02db"),
|
BlockHash: common.HexToHash("0x229f6b18ca1552f1d5146deceb5387333f40dc6275aebee3f2c5c4ece07d02db"),
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// PrunedHistoryError is returned when the requested history is pruned.
|
||||||
|
type PrunedHistoryError struct{}
|
||||||
|
|
||||||
|
func (e *PrunedHistoryError) Error() string { return "pruned history unavailable" }
|
||||||
|
func (e *PrunedHistoryError) ErrorCode() int { return 4444 }
|
||||||
|
|
|
||||||
|
|
@ -29,6 +29,7 @@ import (
|
||||||
"github.com/ethereum/go-ethereum/common"
|
"github.com/ethereum/go-ethereum/common"
|
||||||
"github.com/ethereum/go-ethereum/common/hexutil"
|
"github.com/ethereum/go-ethereum/common/hexutil"
|
||||||
"github.com/ethereum/go-ethereum/core/types"
|
"github.com/ethereum/go-ethereum/core/types"
|
||||||
|
"github.com/ethereum/go-ethereum/eth/ethconfig"
|
||||||
"github.com/ethereum/go-ethereum/internal/ethapi"
|
"github.com/ethereum/go-ethereum/internal/ethapi"
|
||||||
"github.com/ethereum/go-ethereum/rpc"
|
"github.com/ethereum/go-ethereum/rpc"
|
||||||
)
|
)
|
||||||
|
|
@ -354,9 +355,13 @@ func (api *FilterAPI) GetLogs(ctx context.Context, crit FilterCriteria) ([]*type
|
||||||
if crit.ToBlock != nil {
|
if crit.ToBlock != nil {
|
||||||
end = crit.ToBlock.Int64()
|
end = crit.ToBlock.Int64()
|
||||||
}
|
}
|
||||||
|
// Block numbers below 0 are special cases.
|
||||||
if begin > 0 && end > 0 && begin > end {
|
if begin > 0 && end > 0 && begin > end {
|
||||||
return nil, errInvalidBlockRange
|
return nil, errInvalidBlockRange
|
||||||
}
|
}
|
||||||
|
if begin > 0 && begin < int64(api.events.backend.HistoryPruningCutoff()) {
|
||||||
|
return nil, ðconfig.PrunedHistoryError{}
|
||||||
|
}
|
||||||
// Construct the range filter
|
// Construct the range filter
|
||||||
filter = api.sys.NewRangeFilter(begin, end, crit.Addresses, crit.Topics)
|
filter = api.sys.NewRangeFilter(begin, end, crit.Addresses, crit.Topics)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -27,6 +27,7 @@ import (
|
||||||
"github.com/ethereum/go-ethereum/common"
|
"github.com/ethereum/go-ethereum/common"
|
||||||
"github.com/ethereum/go-ethereum/core/filtermaps"
|
"github.com/ethereum/go-ethereum/core/filtermaps"
|
||||||
"github.com/ethereum/go-ethereum/core/types"
|
"github.com/ethereum/go-ethereum/core/types"
|
||||||
|
"github.com/ethereum/go-ethereum/eth/ethconfig"
|
||||||
"github.com/ethereum/go-ethereum/log"
|
"github.com/ethereum/go-ethereum/log"
|
||||||
"github.com/ethereum/go-ethereum/rpc"
|
"github.com/ethereum/go-ethereum/rpc"
|
||||||
)
|
)
|
||||||
|
|
@ -86,6 +87,9 @@ func (f *Filter) Logs(ctx context.Context) ([]*types.Log, error) {
|
||||||
if header == nil {
|
if header == nil {
|
||||||
return nil, errors.New("unknown block")
|
return nil, errors.New("unknown block")
|
||||||
}
|
}
|
||||||
|
if header.Number.Uint64() < f.sys.backend.HistoryPruningCutoff() {
|
||||||
|
return nil, ðconfig.PrunedHistoryError{}
|
||||||
|
}
|
||||||
return f.blockLogs(ctx, header)
|
return f.blockLogs(ctx, header)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -114,12 +118,20 @@ func (f *Filter) Logs(ctx context.Context) ([]*types.Log, error) {
|
||||||
return 0, errors.New("safe header not found")
|
return 0, errors.New("safe header not found")
|
||||||
}
|
}
|
||||||
return hdr.Number.Uint64(), nil
|
return hdr.Number.Uint64(), nil
|
||||||
|
case rpc.EarliestBlockNumber.Int64():
|
||||||
|
earliest := f.sys.backend.HistoryPruningCutoff()
|
||||||
|
hdr, _ := f.sys.backend.HeaderByNumber(ctx, rpc.BlockNumber(earliest))
|
||||||
|
if hdr == nil {
|
||||||
|
return 0, errors.New("earliest header not found")
|
||||||
}
|
}
|
||||||
|
return hdr.Number.Uint64(), nil
|
||||||
|
default:
|
||||||
if number < 0 {
|
if number < 0 {
|
||||||
return 0, errors.New("negative block number")
|
return 0, errors.New("negative block number")
|
||||||
}
|
}
|
||||||
return uint64(number), nil
|
return uint64(number), nil
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// range query need to resolve the special begin/end block number
|
// range query need to resolve the special begin/end block number
|
||||||
begin, err := resolveSpecial(f.begin)
|
begin, err := resolveSpecial(f.begin)
|
||||||
|
|
|
||||||
|
|
@ -31,6 +31,7 @@ import (
|
||||||
"github.com/ethereum/go-ethereum/core"
|
"github.com/ethereum/go-ethereum/core"
|
||||||
"github.com/ethereum/go-ethereum/core/filtermaps"
|
"github.com/ethereum/go-ethereum/core/filtermaps"
|
||||||
"github.com/ethereum/go-ethereum/core/types"
|
"github.com/ethereum/go-ethereum/core/types"
|
||||||
|
"github.com/ethereum/go-ethereum/eth/ethconfig"
|
||||||
"github.com/ethereum/go-ethereum/ethdb"
|
"github.com/ethereum/go-ethereum/ethdb"
|
||||||
"github.com/ethereum/go-ethereum/event"
|
"github.com/ethereum/go-ethereum/event"
|
||||||
"github.com/ethereum/go-ethereum/log"
|
"github.com/ethereum/go-ethereum/log"
|
||||||
|
|
@ -64,6 +65,7 @@ type Backend interface {
|
||||||
|
|
||||||
CurrentHeader() *types.Header
|
CurrentHeader() *types.Header
|
||||||
ChainConfig() *params.ChainConfig
|
ChainConfig() *params.ChainConfig
|
||||||
|
HistoryPruningCutoff() uint64
|
||||||
SubscribeNewTxsEvent(chan<- core.NewTxsEvent) event.Subscription
|
SubscribeNewTxsEvent(chan<- core.NewTxsEvent) event.Subscription
|
||||||
SubscribeChainEvent(ch chan<- core.ChainEvent) event.Subscription
|
SubscribeChainEvent(ch chan<- core.ChainEvent) event.Subscription
|
||||||
SubscribeRemovedLogsEvent(ch chan<- core.RemovedLogsEvent) event.Subscription
|
SubscribeRemovedLogsEvent(ch chan<- core.RemovedLogsEvent) event.Subscription
|
||||||
|
|
@ -304,6 +306,14 @@ func (es *EventSystem) SubscribeLogs(crit ethereum.FilterQuery, logs chan []*typ
|
||||||
return nil, errPendingLogsUnsupported
|
return nil, errPendingLogsUnsupported
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if from == rpc.EarliestBlockNumber {
|
||||||
|
from = rpc.BlockNumber(es.backend.HistoryPruningCutoff())
|
||||||
|
}
|
||||||
|
// Queries beyond the pruning cutoff are not supported.
|
||||||
|
if uint64(from) < es.backend.HistoryPruningCutoff() {
|
||||||
|
return nil, ðconfig.PrunedHistoryError{}
|
||||||
|
}
|
||||||
|
|
||||||
// only interested in new mined logs
|
// only interested in new mined logs
|
||||||
if from == rpc.LatestBlockNumber && to == rpc.LatestBlockNumber {
|
if from == rpc.LatestBlockNumber && to == rpc.LatestBlockNumber {
|
||||||
return es.subscribeLogs(crit, logs), nil
|
return es.subscribeLogs(crit, logs), nil
|
||||||
|
|
|
||||||
|
|
@ -181,6 +181,10 @@ func (b *testBackend) setPending(block *types.Block, receipts types.Receipts) {
|
||||||
b.pendingReceipts = receipts
|
b.pendingReceipts = receipts
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (b *testBackend) HistoryPruningCutoff() uint64 {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
func newTestFilterSystem(db ethdb.Database, cfg Config) (*testBackend, *FilterSystem) {
|
func newTestFilterSystem(db ethdb.Database, cfg Config) (*testBackend, *FilterSystem) {
|
||||||
backend := &testBackend{db: db}
|
backend := &testBackend{db: db}
|
||||||
sys := NewFilterSystem(backend, cfg)
|
sys := NewFilterSystem(backend, cfg)
|
||||||
|
|
|
||||||
|
|
@ -223,7 +223,9 @@ func run(ctx context.Context, call *core.Message, opts *Options) (*core.Executio
|
||||||
dirtyState = opts.State.Copy()
|
dirtyState = opts.State.Copy()
|
||||||
)
|
)
|
||||||
if opts.BlockOverrides != nil {
|
if opts.BlockOverrides != nil {
|
||||||
opts.BlockOverrides.Apply(&evmContext)
|
if err := opts.BlockOverrides.Apply(&evmContext); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
}
|
}
|
||||||
// Lower the basefee to 0 to avoid breaking EVM
|
// Lower the basefee to 0 to avoid breaking EVM
|
||||||
// invariants (basefee < feecap).
|
// invariants (basefee < feecap).
|
||||||
|
|
|
||||||
|
|
@ -71,6 +71,10 @@ type txPool interface {
|
||||||
// with given tx hash.
|
// with given tx hash.
|
||||||
GetRLP(hash common.Hash) []byte
|
GetRLP(hash common.Hash) []byte
|
||||||
|
|
||||||
|
// GetMetadata returns the transaction type and transaction size with the
|
||||||
|
// given transaction hash.
|
||||||
|
GetMetadata(hash common.Hash) *txpool.TxMetadata
|
||||||
|
|
||||||
// Add should add the given transactions to the pool.
|
// Add should add the given transactions to the pool.
|
||||||
Add(txs []*types.Transaction, sync bool) []error
|
Add(txs []*types.Transaction, sync bool) []error
|
||||||
|
|
||||||
|
|
@ -403,7 +407,7 @@ func (h *handler) unregisterPeer(id string) {
|
||||||
// Abort if the peer does not exist
|
// Abort if the peer does not exist
|
||||||
peer := h.peers.peer(id)
|
peer := h.peers.peer(id)
|
||||||
if peer == nil {
|
if peer == nil {
|
||||||
logger.Error("Ethereum peer removal failed", "err", errPeerNotRegistered)
|
logger.Warn("Ethereum peer removal failed", "err", errPeerNotRegistered)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
// Remove the `eth` peer if it exists
|
// Remove the `eth` peer if it exists
|
||||||
|
|
|
||||||
|
|
@ -93,6 +93,22 @@ func (p *testTxPool) GetRLP(hash common.Hash) []byte {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetMetadata returns the transaction type and transaction size with the given
|
||||||
|
// hash.
|
||||||
|
func (p *testTxPool) GetMetadata(hash common.Hash) *txpool.TxMetadata {
|
||||||
|
p.lock.Lock()
|
||||||
|
defer p.lock.Unlock()
|
||||||
|
|
||||||
|
tx := p.pool[hash]
|
||||||
|
if tx != nil {
|
||||||
|
return &txpool.TxMetadata{
|
||||||
|
Type: tx.Type(),
|
||||||
|
Size: tx.Size(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
// Add appends a batch of transactions to the pool, and notifies any
|
// Add appends a batch of transactions to the pool, and notifies any
|
||||||
// listeners if the addition channel is non nil
|
// listeners if the addition channel is non nil
|
||||||
func (p *testTxPool) Add(txs []*types.Transaction, sync bool) []error {
|
func (p *testTxPool) Add(txs []*types.Transaction, sync bool) []error {
|
||||||
|
|
|
||||||
|
|
@ -116,10 +116,10 @@ func (p *Peer) announceTransactions() {
|
||||||
size common.StorageSize
|
size common.StorageSize
|
||||||
)
|
)
|
||||||
for count = 0; count < len(queue) && size < maxTxPacketSize; count++ {
|
for count = 0; count < len(queue) && size < maxTxPacketSize; count++ {
|
||||||
if tx := p.txpool.Get(queue[count]); tx != nil {
|
if meta := p.txpool.GetMetadata(queue[count]); meta != nil {
|
||||||
pending = append(pending, queue[count])
|
pending = append(pending, queue[count])
|
||||||
pendingTypes = append(pendingTypes, tx.Type())
|
pendingTypes = append(pendingTypes, meta.Type)
|
||||||
pendingSizes = append(pendingSizes, uint32(tx.Size()))
|
pendingSizes = append(pendingSizes, uint32(meta.Size))
|
||||||
size += common.HashLength
|
size += common.HashLength
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -22,6 +22,7 @@ import (
|
||||||
|
|
||||||
"github.com/ethereum/go-ethereum/common"
|
"github.com/ethereum/go-ethereum/common"
|
||||||
"github.com/ethereum/go-ethereum/core"
|
"github.com/ethereum/go-ethereum/core"
|
||||||
|
"github.com/ethereum/go-ethereum/core/txpool"
|
||||||
"github.com/ethereum/go-ethereum/core/types"
|
"github.com/ethereum/go-ethereum/core/types"
|
||||||
"github.com/ethereum/go-ethereum/metrics"
|
"github.com/ethereum/go-ethereum/metrics"
|
||||||
"github.com/ethereum/go-ethereum/p2p"
|
"github.com/ethereum/go-ethereum/p2p"
|
||||||
|
|
@ -90,6 +91,10 @@ type TxPool interface {
|
||||||
// GetRLP retrieves the RLP-encoded transaction from the local txpool with
|
// GetRLP retrieves the RLP-encoded transaction from the local txpool with
|
||||||
// the given hash.
|
// the given hash.
|
||||||
GetRLP(hash common.Hash) []byte
|
GetRLP(hash common.Hash) []byte
|
||||||
|
|
||||||
|
// GetMetadata returns the transaction type and transaction size with the
|
||||||
|
// given transaction hash.
|
||||||
|
GetMetadata(hash common.Hash) *txpool.TxMetadata
|
||||||
}
|
}
|
||||||
|
|
||||||
// MakeProtocols constructs the P2P protocol definitions for `eth`.
|
// MakeProtocols constructs the P2P protocol definitions for `eth`.
|
||||||
|
|
|
||||||
|
|
@ -950,7 +950,9 @@ func (api *API) TraceCall(ctx context.Context, args ethapi.TransactionArgs, bloc
|
||||||
vmctx := core.NewEVMBlockContext(block.Header(), api.chainContext(ctx), nil)
|
vmctx := core.NewEVMBlockContext(block.Header(), api.chainContext(ctx), nil)
|
||||||
// Apply the customization rules if required.
|
// Apply the customization rules if required.
|
||||||
if config != nil {
|
if config != nil {
|
||||||
config.BlockOverrides.Apply(&vmctx)
|
if overrideErr := config.BlockOverrides.Apply(&vmctx); overrideErr != nil {
|
||||||
|
return nil, overrideErr
|
||||||
|
}
|
||||||
rules := api.backend.ChainConfig().Rules(vmctx.BlockNumber, vmctx.Random != nil, vmctx.Time)
|
rules := api.backend.ChainConfig().Rules(vmctx.BlockNumber, vmctx.Random != nil, vmctx.Time)
|
||||||
|
|
||||||
precompiles = vm.ActivePrecompiledContracts(rules)
|
precompiles = vm.ActivePrecompiledContracts(rules)
|
||||||
|
|
|
||||||
|
|
@ -103,16 +103,10 @@ type AccessListTracer struct {
|
||||||
// NewAccessListTracer creates a new tracer that can generate AccessLists.
|
// NewAccessListTracer creates a new tracer that can generate AccessLists.
|
||||||
// An optional AccessList can be specified to occupy slots and addresses in
|
// An optional AccessList can be specified to occupy slots and addresses in
|
||||||
// the resulting accesslist.
|
// the resulting accesslist.
|
||||||
func NewAccessListTracer(acl types.AccessList, from, to common.Address, precompiles []common.Address) *AccessListTracer {
|
func NewAccessListTracer(acl types.AccessList, addressesToExclude map[common.Address]struct{}) *AccessListTracer {
|
||||||
excl := map[common.Address]struct{}{
|
|
||||||
from: {}, to: {},
|
|
||||||
}
|
|
||||||
for _, addr := range precompiles {
|
|
||||||
excl[addr] = struct{}{}
|
|
||||||
}
|
|
||||||
list := newAccessList()
|
list := newAccessList()
|
||||||
for _, al := range acl {
|
for _, al := range acl {
|
||||||
if _, ok := excl[al.Address]; !ok {
|
if _, ok := addressesToExclude[al.Address]; !ok {
|
||||||
list.addAddress(al.Address)
|
list.addAddress(al.Address)
|
||||||
}
|
}
|
||||||
for _, slot := range al.StorageKeys {
|
for _, slot := range al.StorageKeys {
|
||||||
|
|
@ -120,7 +114,7 @@ func NewAccessListTracer(acl types.AccessList, from, to common.Address, precompi
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return &AccessListTracer{
|
return &AccessListTracer{
|
||||||
excl: excl,
|
excl: addressesToExclude,
|
||||||
list: list,
|
list: list,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -17,7 +17,10 @@
|
||||||
// Package ethdb defines the interfaces for an Ethereum data store.
|
// Package ethdb defines the interfaces for an Ethereum data store.
|
||||||
package ethdb
|
package ethdb
|
||||||
|
|
||||||
import "io"
|
import (
|
||||||
|
"errors"
|
||||||
|
"io"
|
||||||
|
)
|
||||||
|
|
||||||
// KeyValueReader wraps the Has and Get method of a backing data store.
|
// KeyValueReader wraps the Has and Get method of a backing data store.
|
||||||
type KeyValueReader interface {
|
type KeyValueReader interface {
|
||||||
|
|
@ -37,10 +40,14 @@ type KeyValueWriter interface {
|
||||||
Delete(key []byte) error
|
Delete(key []byte) error
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var ErrTooManyKeys = errors.New("too many keys in deleted range")
|
||||||
|
|
||||||
// KeyValueRangeDeleter wraps the DeleteRange method of a backing data store.
|
// KeyValueRangeDeleter wraps the DeleteRange method of a backing data store.
|
||||||
type KeyValueRangeDeleter interface {
|
type KeyValueRangeDeleter interface {
|
||||||
// DeleteRange deletes all of the keys (and values) in the range [start,end)
|
// DeleteRange deletes all of the keys (and values) in the range [start,end)
|
||||||
// (inclusive on start, exclusive on end).
|
// (inclusive on start, exclusive on end).
|
||||||
|
// Some implementations of DeleteRange may return ErrTooManyKeys after
|
||||||
|
// partially deleting entries in the given range.
|
||||||
DeleteRange(start, end []byte) error
|
DeleteRange(start, end []byte) error
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -207,8 +207,6 @@ func (db *Database) Delete(key []byte) error {
|
||||||
return db.db.Delete(key, nil)
|
return db.db.Delete(key, nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
var ErrTooManyKeys = errors.New("too many keys in deleted range")
|
|
||||||
|
|
||||||
// DeleteRange deletes all of the keys (and values) in the range [start,end)
|
// DeleteRange deletes all of the keys (and values) in the range [start,end)
|
||||||
// (inclusive on start, exclusive on end).
|
// (inclusive on start, exclusive on end).
|
||||||
// Note that this is a fallback implementation as leveldb does not natively
|
// Note that this is a fallback implementation as leveldb does not natively
|
||||||
|
|
@ -228,7 +226,7 @@ func (db *Database) DeleteRange(start, end []byte) error {
|
||||||
if err := batch.Write(); err != nil {
|
if err := batch.Write(); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
return ErrTooManyKeys
|
return ethdb.ErrTooManyKeys
|
||||||
}
|
}
|
||||||
if err := batch.Delete(it.Key()); err != nil {
|
if err := batch.Delete(it.Key()); err != nil {
|
||||||
return err
|
return err
|
||||||
|
|
|
||||||
|
|
@ -549,21 +549,23 @@ func (api *BlockChainAPI) GetUncleByBlockHashAndIndex(ctx context.Context, block
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetUncleCountByBlockNumber returns number of uncles in the block for the given block number
|
// GetUncleCountByBlockNumber returns number of uncles in the block for the given block number
|
||||||
func (api *BlockChainAPI) GetUncleCountByBlockNumber(ctx context.Context, blockNr rpc.BlockNumber) *hexutil.Uint {
|
func (api *BlockChainAPI) GetUncleCountByBlockNumber(ctx context.Context, blockNr rpc.BlockNumber) (*hexutil.Uint, error) {
|
||||||
if block, _ := api.b.BlockByNumber(ctx, blockNr); block != nil {
|
block, err := api.b.BlockByNumber(ctx, blockNr)
|
||||||
|
if block != nil {
|
||||||
n := hexutil.Uint(len(block.Uncles()))
|
n := hexutil.Uint(len(block.Uncles()))
|
||||||
return &n
|
return &n, nil
|
||||||
}
|
}
|
||||||
return nil
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetUncleCountByBlockHash returns number of uncles in the block for the given block hash
|
// GetUncleCountByBlockHash returns number of uncles in the block for the given block hash
|
||||||
func (api *BlockChainAPI) GetUncleCountByBlockHash(ctx context.Context, blockHash common.Hash) *hexutil.Uint {
|
func (api *BlockChainAPI) GetUncleCountByBlockHash(ctx context.Context, blockHash common.Hash) (*hexutil.Uint, error) {
|
||||||
if block, _ := api.b.BlockByHash(ctx, blockHash); block != nil {
|
block, err := api.b.BlockByHash(ctx, blockHash)
|
||||||
|
if block != nil {
|
||||||
n := hexutil.Uint(len(block.Uncles()))
|
n := hexutil.Uint(len(block.Uncles()))
|
||||||
return &n
|
return &n, nil
|
||||||
}
|
}
|
||||||
return nil
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetCode returns the code stored at the given address in the state for the given block number.
|
// GetCode returns the code stored at the given address in the state for the given block number.
|
||||||
|
|
@ -596,9 +598,7 @@ func (api *BlockChainAPI) GetStorageAt(ctx context.Context, address common.Addre
|
||||||
func (api *BlockChainAPI) GetBlockReceipts(ctx context.Context, blockNrOrHash rpc.BlockNumberOrHash) ([]map[string]interface{}, error) {
|
func (api *BlockChainAPI) GetBlockReceipts(ctx context.Context, blockNrOrHash rpc.BlockNumberOrHash) ([]map[string]interface{}, error) {
|
||||||
block, err := api.b.BlockByNumberOrHash(ctx, blockNrOrHash)
|
block, err := api.b.BlockByNumberOrHash(ctx, blockNrOrHash)
|
||||||
if block == nil || err != nil {
|
if block == nil || err != nil {
|
||||||
// When the block doesn't exist, the RPC method should return JSON null
|
return nil, err
|
||||||
// as per specification.
|
|
||||||
return nil, nil
|
|
||||||
}
|
}
|
||||||
receipts, err := api.b.GetReceipts(ctx, block.Hash())
|
receipts, err := api.b.GetReceipts(ctx, block.Hash())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
@ -660,7 +660,9 @@ func (context *ChainContext) Config() *params.ChainConfig {
|
||||||
func doCall(ctx context.Context, b Backend, args TransactionArgs, state *state.StateDB, header *types.Header, overrides *override.StateOverride, blockOverrides *override.BlockOverrides, timeout time.Duration, globalGasCap uint64) (*core.ExecutionResult, error) {
|
func doCall(ctx context.Context, b Backend, args TransactionArgs, state *state.StateDB, header *types.Header, overrides *override.StateOverride, blockOverrides *override.BlockOverrides, timeout time.Duration, globalGasCap uint64) (*core.ExecutionResult, error) {
|
||||||
blockCtx := core.NewEVMBlockContext(header, NewChainContext(ctx, b), nil)
|
blockCtx := core.NewEVMBlockContext(header, NewChainContext(ctx, b), nil)
|
||||||
if blockOverrides != nil {
|
if blockOverrides != nil {
|
||||||
blockOverrides.Apply(&blockCtx)
|
if err := blockOverrides.Apply(&blockCtx); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
}
|
}
|
||||||
rules := b.ChainConfig().Rules(blockCtx.BlockNumber, blockCtx.Random != nil, blockCtx.Time)
|
rules := b.ChainConfig().Rules(blockCtx.BlockNumber, blockCtx.Random != nil, blockCtx.Time)
|
||||||
precompiles := vm.ActivePrecompiledContracts(rules)
|
precompiles := vm.ActivePrecompiledContracts(rules)
|
||||||
|
|
@ -1115,12 +1117,13 @@ type accessListResult struct {
|
||||||
|
|
||||||
// CreateAccessList creates an EIP-2930 type AccessList for the given transaction.
|
// CreateAccessList creates an EIP-2930 type AccessList for the given transaction.
|
||||||
// Reexec and BlockNrOrHash can be specified to create the accessList on top of a certain state.
|
// Reexec and BlockNrOrHash can be specified to create the accessList on top of a certain state.
|
||||||
func (api *BlockChainAPI) CreateAccessList(ctx context.Context, args TransactionArgs, blockNrOrHash *rpc.BlockNumberOrHash) (*accessListResult, error) {
|
// StateOverrides can be used to create the accessList while taking into account state changes from previous transactions.
|
||||||
|
func (api *BlockChainAPI) CreateAccessList(ctx context.Context, args TransactionArgs, blockNrOrHash *rpc.BlockNumberOrHash, stateOverrides *override.StateOverride) (*accessListResult, error) {
|
||||||
bNrOrHash := rpc.BlockNumberOrHashWithNumber(rpc.LatestBlockNumber)
|
bNrOrHash := rpc.BlockNumberOrHashWithNumber(rpc.LatestBlockNumber)
|
||||||
if blockNrOrHash != nil {
|
if blockNrOrHash != nil {
|
||||||
bNrOrHash = *blockNrOrHash
|
bNrOrHash = *blockNrOrHash
|
||||||
}
|
}
|
||||||
acl, gasUsed, vmerr, err := AccessList(ctx, api.b, bNrOrHash, args)
|
acl, gasUsed, vmerr, err := AccessList(ctx, api.b, bNrOrHash, args, stateOverrides)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
@ -1134,13 +1137,22 @@ func (api *BlockChainAPI) CreateAccessList(ctx context.Context, args Transaction
|
||||||
// AccessList creates an access list for the given transaction.
|
// AccessList creates an access list for the given transaction.
|
||||||
// If the accesslist creation fails an error is returned.
|
// If the accesslist creation fails an error is returned.
|
||||||
// If the transaction itself fails, an vmErr is returned.
|
// If the transaction itself fails, an vmErr is returned.
|
||||||
func AccessList(ctx context.Context, b Backend, blockNrOrHash rpc.BlockNumberOrHash, args TransactionArgs) (acl types.AccessList, gasUsed uint64, vmErr error, err error) {
|
func AccessList(ctx context.Context, b Backend, blockNrOrHash rpc.BlockNumberOrHash, args TransactionArgs, stateOverrides *override.StateOverride) (acl types.AccessList, gasUsed uint64, vmErr error, err error) {
|
||||||
// Retrieve the execution context
|
// Retrieve the execution context
|
||||||
db, header, err := b.StateAndHeaderByNumberOrHash(ctx, blockNrOrHash)
|
db, header, err := b.StateAndHeaderByNumberOrHash(ctx, blockNrOrHash)
|
||||||
if db == nil || err != nil {
|
if db == nil || err != nil {
|
||||||
return nil, 0, nil, err
|
return nil, 0, nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Apply state overrides immediately after StateAndHeaderByNumberOrHash.
|
||||||
|
// If not applied here, there could be cases where user-specified overrides (e.g., nonce)
|
||||||
|
// may conflict with default values from the database, leading to inconsistencies.
|
||||||
|
if stateOverrides != nil {
|
||||||
|
if err := stateOverrides.Apply(db, nil); err != nil {
|
||||||
|
return nil, 0, nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Ensure any missing fields are filled, extract the recipient and input data
|
// Ensure any missing fields are filled, extract the recipient and input data
|
||||||
if err = args.setFeeDefaults(ctx, b, header); err != nil {
|
if err = args.setFeeDefaults(ctx, b, header); err != nil {
|
||||||
return nil, 0, nil, err
|
return nil, 0, nil, err
|
||||||
|
|
@ -1164,10 +1176,33 @@ func AccessList(ctx context.Context, b Backend, blockNrOrHash rpc.BlockNumberOrH
|
||||||
// Retrieve the precompiles since they don't need to be added to the access list
|
// Retrieve the precompiles since they don't need to be added to the access list
|
||||||
precompiles := vm.ActivePrecompiles(b.ChainConfig().Rules(header.Number, isPostMerge, header.Time))
|
precompiles := vm.ActivePrecompiles(b.ChainConfig().Rules(header.Number, isPostMerge, header.Time))
|
||||||
|
|
||||||
|
// addressesToExclude contains sender, receiver, precompiles and valid authorizations
|
||||||
|
addressesToExclude := map[common.Address]struct{}{args.from(): {}, to: {}}
|
||||||
|
for _, addr := range precompiles {
|
||||||
|
addressesToExclude[addr] = struct{}{}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Prevent redundant operations if args contain more authorizations than EVM may handle
|
||||||
|
maxAuthorizations := uint64(*args.Gas) / params.CallNewAccountGas
|
||||||
|
if uint64(len(args.AuthorizationList)) > maxAuthorizations {
|
||||||
|
return nil, 0, nil, errors.New("insufficient gas to process all authorizations")
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, auth := range args.AuthorizationList {
|
||||||
|
// Duplicating stateTransition.validateAuthorization() logic
|
||||||
|
if (!auth.ChainID.IsZero() && auth.ChainID.CmpBig(b.ChainConfig().ChainID) != 0) || auth.Nonce+1 < auth.Nonce {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if authority, err := auth.Authority(); err == nil {
|
||||||
|
addressesToExclude[authority] = struct{}{}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Create an initial tracer
|
// Create an initial tracer
|
||||||
prevTracer := logger.NewAccessListTracer(nil, args.from(), to, precompiles)
|
prevTracer := logger.NewAccessListTracer(nil, addressesToExclude)
|
||||||
if args.AccessList != nil {
|
if args.AccessList != nil {
|
||||||
prevTracer = logger.NewAccessListTracer(*args.AccessList, args.from(), to, precompiles)
|
prevTracer = logger.NewAccessListTracer(*args.AccessList, addressesToExclude)
|
||||||
}
|
}
|
||||||
for {
|
for {
|
||||||
if err := ctx.Err(); err != nil {
|
if err := ctx.Err(); err != nil {
|
||||||
|
|
@ -1184,7 +1219,7 @@ func AccessList(ctx context.Context, b Backend, blockNrOrHash rpc.BlockNumberOrH
|
||||||
msg := args.ToMessage(header.BaseFee, true, true)
|
msg := args.ToMessage(header.BaseFee, true, true)
|
||||||
|
|
||||||
// Apply the transaction with the access list tracer
|
// Apply the transaction with the access list tracer
|
||||||
tracer := logger.NewAccessListTracer(accessList, args.from(), to, precompiles)
|
tracer := logger.NewAccessListTracer(accessList, addressesToExclude)
|
||||||
config := vm.Config{Tracer: tracer.Hooks(), NoBaseFee: true}
|
config := vm.Config{Tracer: tracer.Hooks(), NoBaseFee: true}
|
||||||
evm := b.GetEVM(ctx, statedb, header, &config, nil)
|
evm := b.GetEVM(ctx, statedb, header, &config, nil)
|
||||||
|
|
||||||
|
|
@ -1223,37 +1258,41 @@ func NewTransactionAPI(b Backend, nonceLock *AddrLocker) *TransactionAPI {
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetBlockTransactionCountByNumber returns the number of transactions in the block with the given block number.
|
// GetBlockTransactionCountByNumber returns the number of transactions in the block with the given block number.
|
||||||
func (api *TransactionAPI) GetBlockTransactionCountByNumber(ctx context.Context, blockNr rpc.BlockNumber) *hexutil.Uint {
|
func (api *TransactionAPI) GetBlockTransactionCountByNumber(ctx context.Context, blockNr rpc.BlockNumber) (*hexutil.Uint, error) {
|
||||||
if block, _ := api.b.BlockByNumber(ctx, blockNr); block != nil {
|
block, err := api.b.BlockByNumber(ctx, blockNr)
|
||||||
|
if block != nil {
|
||||||
n := hexutil.Uint(len(block.Transactions()))
|
n := hexutil.Uint(len(block.Transactions()))
|
||||||
return &n
|
return &n, nil
|
||||||
}
|
}
|
||||||
return nil
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetBlockTransactionCountByHash returns the number of transactions in the block with the given hash.
|
// GetBlockTransactionCountByHash returns the number of transactions in the block with the given hash.
|
||||||
func (api *TransactionAPI) GetBlockTransactionCountByHash(ctx context.Context, blockHash common.Hash) *hexutil.Uint {
|
func (api *TransactionAPI) GetBlockTransactionCountByHash(ctx context.Context, blockHash common.Hash) (*hexutil.Uint, error) {
|
||||||
if block, _ := api.b.BlockByHash(ctx, blockHash); block != nil {
|
block, err := api.b.BlockByHash(ctx, blockHash)
|
||||||
|
if block != nil {
|
||||||
n := hexutil.Uint(len(block.Transactions()))
|
n := hexutil.Uint(len(block.Transactions()))
|
||||||
return &n
|
return &n, nil
|
||||||
}
|
}
|
||||||
return nil
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetTransactionByBlockNumberAndIndex returns the transaction for the given block number and index.
|
// GetTransactionByBlockNumberAndIndex returns the transaction for the given block number and index.
|
||||||
func (api *TransactionAPI) GetTransactionByBlockNumberAndIndex(ctx context.Context, blockNr rpc.BlockNumber, index hexutil.Uint) *RPCTransaction {
|
func (api *TransactionAPI) GetTransactionByBlockNumberAndIndex(ctx context.Context, blockNr rpc.BlockNumber, index hexutil.Uint) (*RPCTransaction, error) {
|
||||||
if block, _ := api.b.BlockByNumber(ctx, blockNr); block != nil {
|
block, err := api.b.BlockByNumber(ctx, blockNr)
|
||||||
return newRPCTransactionFromBlockIndex(block, uint64(index), api.b.ChainConfig())
|
if block != nil {
|
||||||
|
return newRPCTransactionFromBlockIndex(block, uint64(index), api.b.ChainConfig()), nil
|
||||||
}
|
}
|
||||||
return nil
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetTransactionByBlockHashAndIndex returns the transaction for the given block hash and index.
|
// GetTransactionByBlockHashAndIndex returns the transaction for the given block hash and index.
|
||||||
func (api *TransactionAPI) GetTransactionByBlockHashAndIndex(ctx context.Context, blockHash common.Hash, index hexutil.Uint) *RPCTransaction {
|
func (api *TransactionAPI) GetTransactionByBlockHashAndIndex(ctx context.Context, blockHash common.Hash, index hexutil.Uint) (*RPCTransaction, error) {
|
||||||
if block, _ := api.b.BlockByHash(ctx, blockHash); block != nil {
|
block, err := api.b.BlockByHash(ctx, blockHash)
|
||||||
return newRPCTransactionFromBlockIndex(block, uint64(index), api.b.ChainConfig())
|
if block != nil {
|
||||||
|
return newRPCTransactionFromBlockIndex(block, uint64(index), api.b.ChainConfig()), nil
|
||||||
}
|
}
|
||||||
return nil
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetRawTransactionByBlockNumberAndIndex returns the bytes of the transaction for the given block number and index.
|
// GetRawTransactionByBlockNumberAndIndex returns the bytes of the transaction for the given block number and index.
|
||||||
|
|
|
||||||
|
|
@ -520,8 +520,12 @@ func (b testBackend) BlockByNumber(ctx context.Context, number rpc.BlockNumber)
|
||||||
if number == rpc.PendingBlockNumber {
|
if number == rpc.PendingBlockNumber {
|
||||||
return b.pending, nil
|
return b.pending, nil
|
||||||
}
|
}
|
||||||
|
if number == rpc.EarliestBlockNumber {
|
||||||
|
number = 0
|
||||||
|
}
|
||||||
return b.chain.GetBlockByNumber(uint64(number)), nil
|
return b.chain.GetBlockByNumber(uint64(number)), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b testBackend) BlockByHash(ctx context.Context, hash common.Hash) (*types.Block, error) {
|
func (b testBackend) BlockByHash(ctx context.Context, hash common.Hash) (*types.Block, error) {
|
||||||
return b.chain.GetBlockByHash(hash), nil
|
return b.chain.GetBlockByHash(hash), nil
|
||||||
}
|
}
|
||||||
|
|
@ -618,6 +622,9 @@ func (b testBackend) SubscribeLogsEvent(ch chan<- []*types.Log) event.Subscripti
|
||||||
func (b testBackend) NewMatcherBackend() filtermaps.MatcherBackend {
|
func (b testBackend) NewMatcherBackend() filtermaps.MatcherBackend {
|
||||||
panic("implement me")
|
panic("implement me")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (b testBackend) HistoryPruningCutoff() uint64 { return b.chain.HistoryPruningCutoff() }
|
||||||
|
|
||||||
func TestEstimateGas(t *testing.T) {
|
func TestEstimateGas(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
// Initialize test accounts
|
// Initialize test accounts
|
||||||
|
|
@ -1134,6 +1141,24 @@ func TestCall(t *testing.T) {
|
||||||
},
|
},
|
||||||
want: "0x0000000000000000000000000000000000000000000000000000000000000000",
|
want: "0x0000000000000000000000000000000000000000000000000000000000000000",
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: "unsupported block override beaconRoot",
|
||||||
|
blockNumber: rpc.LatestBlockNumber,
|
||||||
|
call: TransactionArgs{},
|
||||||
|
blockOverrides: override.BlockOverrides{
|
||||||
|
BeaconRoot: &common.Hash{0, 1, 2},
|
||||||
|
},
|
||||||
|
expectErr: errors.New(`block override "beaconRoot" is not supported for this RPC method`),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "unsupported block override withdrawals",
|
||||||
|
blockNumber: rpc.LatestBlockNumber,
|
||||||
|
call: TransactionArgs{},
|
||||||
|
blockOverrides: override.BlockOverrides{
|
||||||
|
Withdrawals: &types.Withdrawals{},
|
||||||
|
},
|
||||||
|
expectErr: errors.New(`block override "withdrawals" is not supported for this RPC method`),
|
||||||
|
},
|
||||||
}
|
}
|
||||||
for _, tc := range testSuite {
|
for _, tc := range testSuite {
|
||||||
result, err := api.Call(context.Background(), tc.call, &rpc.BlockNumberOrHash{BlockNumber: &tc.blockNumber}, &tc.overrides, &tc.blockOverrides)
|
result, err := api.Call(context.Background(), tc.call, &rpc.BlockNumberOrHash{BlockNumber: &tc.blockNumber}, &tc.overrides, &tc.blockOverrides)
|
||||||
|
|
@ -3511,3 +3536,76 @@ func testRPCResponseWithFile(t *testing.T, testid int, result interface{}, rpc s
|
||||||
func addressToHash(a common.Address) common.Hash {
|
func addressToHash(a common.Address) common.Hash {
|
||||||
return common.BytesToHash(a.Bytes())
|
return common.BytesToHash(a.Bytes())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestCreateAccessListWithStateOverrides(t *testing.T) {
|
||||||
|
// Initialize test backend
|
||||||
|
genesis := &core.Genesis{
|
||||||
|
Config: params.TestChainConfig,
|
||||||
|
Alloc: types.GenesisAlloc{
|
||||||
|
common.HexToAddress("0x71562b71999873db5b286df957af199ec94617f7"): {Balance: big.NewInt(1000000000000000000)},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
backend := newTestBackend(t, 1, genesis, ethash.NewFaker(), nil)
|
||||||
|
|
||||||
|
// Create a new BlockChainAPI instance
|
||||||
|
api := NewBlockChainAPI(backend)
|
||||||
|
|
||||||
|
// Create test contract code - a simple storage contract
|
||||||
|
//
|
||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
// pragma solidity ^0.8.0;
|
||||||
|
//
|
||||||
|
// contract SimpleStorage {
|
||||||
|
// uint256 private value;
|
||||||
|
//
|
||||||
|
// function retrieve() public view returns (uint256) {
|
||||||
|
// return value;
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
var (
|
||||||
|
contractCode = hexutil.Bytes(common.Hex2Bytes("6080604052348015600f57600080fd5b506004361060285760003560e01c80632e64cec114602d575b600080fd5b60336047565b604051603e91906067565b60405180910390f35b60008054905090565b6000819050919050565b6061816050565b82525050565b6000602082019050607a6000830184605a565b9291505056"))
|
||||||
|
// Create state overrides with more complete state
|
||||||
|
contractAddr = common.HexToAddress("0x1234567890123456789012345678901234567890")
|
||||||
|
nonce = hexutil.Uint64(1)
|
||||||
|
overrides = &override.StateOverride{
|
||||||
|
contractAddr: override.OverrideAccount{
|
||||||
|
Code: &contractCode,
|
||||||
|
Balance: (*hexutil.Big)(big.NewInt(1000000000000000000)),
|
||||||
|
Nonce: &nonce,
|
||||||
|
State: map[common.Hash]common.Hash{
|
||||||
|
common.Hash{}: common.HexToHash("0x000000000000000000000000000000000000000000000000000000000000002a"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
// Create transaction arguments with gas and value
|
||||||
|
var (
|
||||||
|
from = common.HexToAddress("0x71562b71999873db5b286df957af199ec94617f7")
|
||||||
|
data = hexutil.Bytes(common.Hex2Bytes("2e64cec1")) // retrieve()
|
||||||
|
gas = hexutil.Uint64(100000)
|
||||||
|
args = TransactionArgs{
|
||||||
|
From: &from,
|
||||||
|
To: &contractAddr,
|
||||||
|
Data: &data,
|
||||||
|
Gas: &gas,
|
||||||
|
Value: new(hexutil.Big),
|
||||||
|
}
|
||||||
|
)
|
||||||
|
// Call CreateAccessList
|
||||||
|
result, err := api.CreateAccessList(context.Background(), args, nil, overrides)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Failed to create access list: %v", err)
|
||||||
|
}
|
||||||
|
if err != nil || result == nil {
|
||||||
|
t.Fatalf("Failed to create access list: %v", err)
|
||||||
|
}
|
||||||
|
require.NotNil(t, result.Accesslist)
|
||||||
|
|
||||||
|
// Verify access list contains the contract address and storage slot
|
||||||
|
expected := &types.AccessList{{
|
||||||
|
Address: contractAddr,
|
||||||
|
StorageKeys: []common.Hash{{}},
|
||||||
|
}}
|
||||||
|
require.Equal(t, expected, result.Accesslist)
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -85,6 +85,7 @@ type Backend interface {
|
||||||
|
|
||||||
ChainConfig() *params.ChainConfig
|
ChainConfig() *params.ChainConfig
|
||||||
Engine() consensus.Engine
|
Engine() consensus.Engine
|
||||||
|
HistoryPruningCutoff() uint64
|
||||||
|
|
||||||
// This is copied from filters.Backend
|
// This is copied from filters.Backend
|
||||||
// eth/filters needs to be initialized from this backend type, so methods needed by
|
// eth/filters needs to be initialized from this backend type, so methods needed by
|
||||||
|
|
|
||||||
|
|
@ -17,6 +17,7 @@
|
||||||
package override
|
package override
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"math/big"
|
"math/big"
|
||||||
|
|
||||||
|
|
@ -128,12 +129,20 @@ type BlockOverrides struct {
|
||||||
PrevRandao *common.Hash
|
PrevRandao *common.Hash
|
||||||
BaseFeePerGas *hexutil.Big
|
BaseFeePerGas *hexutil.Big
|
||||||
BlobBaseFee *hexutil.Big
|
BlobBaseFee *hexutil.Big
|
||||||
|
BeaconRoot *common.Hash
|
||||||
|
Withdrawals *types.Withdrawals
|
||||||
}
|
}
|
||||||
|
|
||||||
// Apply overrides the given header fields into the given block context.
|
// Apply overrides the given header fields into the given block context.
|
||||||
func (o *BlockOverrides) Apply(blockCtx *vm.BlockContext) {
|
func (o *BlockOverrides) Apply(blockCtx *vm.BlockContext) error {
|
||||||
if o == nil {
|
if o == nil {
|
||||||
return
|
return nil
|
||||||
|
}
|
||||||
|
if o.BeaconRoot != nil {
|
||||||
|
return errors.New(`block override "beaconRoot" is not supported for this RPC method`)
|
||||||
|
}
|
||||||
|
if o.Withdrawals != nil {
|
||||||
|
return errors.New(`block override "withdrawals" is not supported for this RPC method`)
|
||||||
}
|
}
|
||||||
if o.Number != nil {
|
if o.Number != nil {
|
||||||
blockCtx.BlockNumber = o.Number.ToInt()
|
blockCtx.BlockNumber = o.Number.ToInt()
|
||||||
|
|
@ -159,6 +168,7 @@ func (o *BlockOverrides) Apply(blockCtx *vm.BlockContext) {
|
||||||
if o.BlobBaseFee != nil {
|
if o.BlobBaseFee != nil {
|
||||||
blockCtx.BlobBaseFee = o.BlobBaseFee.ToInt()
|
blockCtx.BlobBaseFee = o.BlobBaseFee.ToInt()
|
||||||
}
|
}
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// MakeHeader returns a new header object with the overridden
|
// MakeHeader returns a new header object with the overridden
|
||||||
|
|
|
||||||
|
|
@ -36,7 +36,6 @@ import (
|
||||||
"github.com/ethereum/go-ethereum/internal/ethapi/override"
|
"github.com/ethereum/go-ethereum/internal/ethapi/override"
|
||||||
"github.com/ethereum/go-ethereum/params"
|
"github.com/ethereum/go-ethereum/params"
|
||||||
"github.com/ethereum/go-ethereum/rpc"
|
"github.com/ethereum/go-ethereum/rpc"
|
||||||
"github.com/ethereum/go-ethereum/trie"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
|
@ -95,6 +94,47 @@ type simOpts struct {
|
||||||
ReturnFullTransactions bool
|
ReturnFullTransactions bool
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// simChainHeadReader implements ChainHeaderReader which is needed as input for FinalizeAndAssemble.
|
||||||
|
type simChainHeadReader struct {
|
||||||
|
context.Context
|
||||||
|
Backend
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *simChainHeadReader) Config() *params.ChainConfig {
|
||||||
|
return m.Backend.ChainConfig()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *simChainHeadReader) CurrentHeader() *types.Header {
|
||||||
|
return m.Backend.CurrentHeader()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *simChainHeadReader) GetHeader(hash common.Hash, number uint64) *types.Header {
|
||||||
|
header, err := m.Backend.HeaderByNumber(m.Context, rpc.BlockNumber(number))
|
||||||
|
if err != nil || header == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
if header.Hash() != hash {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return header
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *simChainHeadReader) GetHeaderByNumber(number uint64) *types.Header {
|
||||||
|
header, err := m.Backend.HeaderByNumber(m.Context, rpc.BlockNumber(number))
|
||||||
|
if err != nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return header
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *simChainHeadReader) GetHeaderByHash(hash common.Hash) *types.Header {
|
||||||
|
header, err := m.Backend.HeaderByHash(m.Context, hash)
|
||||||
|
if err != nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return header
|
||||||
|
}
|
||||||
|
|
||||||
// simulator is a stateful object that simulates a series of blocks.
|
// simulator is a stateful object that simulates a series of blocks.
|
||||||
// it is not safe for concurrent use.
|
// it is not safe for concurrent use.
|
||||||
type simulator struct {
|
type simulator struct {
|
||||||
|
|
@ -209,6 +249,9 @@ func (sim *simulator) processBlock(ctx context.Context, block *simBlock, header,
|
||||||
if sim.chainConfig.IsPrague(header.Number, header.Time) || sim.chainConfig.IsVerkle(header.Number, header.Time) {
|
if sim.chainConfig.IsPrague(header.Number, header.Time) || sim.chainConfig.IsVerkle(header.Number, header.Time) {
|
||||||
core.ProcessParentBlockHash(header.ParentHash, evm)
|
core.ProcessParentBlockHash(header.ParentHash, evm)
|
||||||
}
|
}
|
||||||
|
if header.ParentBeaconRoot != nil {
|
||||||
|
core.ProcessBeaconBlockRoot(*header.ParentBeaconRoot, evm)
|
||||||
|
}
|
||||||
var allLogs []*types.Log
|
var allLogs []*types.Log
|
||||||
for i, call := range block.Calls {
|
for i, call := range block.Calls {
|
||||||
if err := ctx.Err(); err != nil {
|
if err := ctx.Err(); err != nil {
|
||||||
|
|
@ -258,6 +301,10 @@ func (sim *simulator) processBlock(ctx context.Context, block *simBlock, header,
|
||||||
}
|
}
|
||||||
callResults[i] = callRes
|
callResults[i] = callRes
|
||||||
}
|
}
|
||||||
|
header.GasUsed = gasUsed
|
||||||
|
if sim.chainConfig.IsCancun(header.Number, header.Time) {
|
||||||
|
header.BlobGasUsed = &blobGasUsed
|
||||||
|
}
|
||||||
var requests [][]byte
|
var requests [][]byte
|
||||||
// Process EIP-7685 requests
|
// Process EIP-7685 requests
|
||||||
if sim.chainConfig.IsPrague(header.Number, header.Time) {
|
if sim.chainConfig.IsPrague(header.Number, header.Time) {
|
||||||
|
|
@ -271,20 +318,16 @@ func (sim *simulator) processBlock(ctx context.Context, block *simBlock, header,
|
||||||
// EIP-7251
|
// EIP-7251
|
||||||
core.ProcessConsolidationQueue(&requests, evm)
|
core.ProcessConsolidationQueue(&requests, evm)
|
||||||
}
|
}
|
||||||
header.Root = sim.state.IntermediateRoot(true)
|
|
||||||
header.GasUsed = gasUsed
|
|
||||||
if sim.chainConfig.IsCancun(header.Number, header.Time) {
|
|
||||||
header.BlobGasUsed = &blobGasUsed
|
|
||||||
}
|
|
||||||
var withdrawals types.Withdrawals
|
|
||||||
if sim.chainConfig.IsShanghai(header.Number, header.Time) {
|
|
||||||
withdrawals = make([]*types.Withdrawal, 0)
|
|
||||||
}
|
|
||||||
if requests != nil {
|
if requests != nil {
|
||||||
reqHash := types.CalcRequestsHash(requests)
|
reqHash := types.CalcRequestsHash(requests)
|
||||||
header.RequestsHash = &reqHash
|
header.RequestsHash = &reqHash
|
||||||
}
|
}
|
||||||
b := types.NewBlock(header, &types.Body{Transactions: txes, Withdrawals: withdrawals}, receipts, trie.NewStackTrie(nil))
|
blockBody := &types.Body{Transactions: txes, Withdrawals: *block.BlockOverrides.Withdrawals}
|
||||||
|
chainHeadReader := &simChainHeadReader{ctx, sim.b}
|
||||||
|
b, err := sim.b.Engine().FinalizeAndAssemble(chainHeadReader, header, sim.state, blockBody, receipts)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
repairLogs(callResults, b.Hash())
|
repairLogs(callResults, b.Hash())
|
||||||
return b, callResults, nil
|
return b, callResults, nil
|
||||||
}
|
}
|
||||||
|
|
@ -346,6 +389,9 @@ func (sim *simulator) sanitizeChain(blocks []simBlock) ([]simBlock, error) {
|
||||||
n := new(big.Int).Add(prevNumber, big.NewInt(1))
|
n := new(big.Int).Add(prevNumber, big.NewInt(1))
|
||||||
block.BlockOverrides.Number = (*hexutil.Big)(n)
|
block.BlockOverrides.Number = (*hexutil.Big)(n)
|
||||||
}
|
}
|
||||||
|
if block.BlockOverrides.Withdrawals == nil {
|
||||||
|
block.BlockOverrides.Withdrawals = &types.Withdrawals{}
|
||||||
|
}
|
||||||
diff := new(big.Int).Sub(block.BlockOverrides.Number.ToInt(), prevNumber)
|
diff := new(big.Int).Sub(block.BlockOverrides.Number.ToInt(), prevNumber)
|
||||||
if diff.Cmp(common.Big0) <= 0 {
|
if diff.Cmp(common.Big0) <= 0 {
|
||||||
return nil, &invalidBlockNumberError{fmt.Sprintf("block numbers must be in order: %d <= %d", block.BlockOverrides.Number.ToInt().Uint64(), prevNumber)}
|
return nil, &invalidBlockNumberError{fmt.Sprintf("block numbers must be in order: %d <= %d", block.BlockOverrides.Number.ToInt().Uint64(), prevNumber)}
|
||||||
|
|
@ -360,7 +406,13 @@ func (sim *simulator) sanitizeChain(blocks []simBlock) ([]simBlock, error) {
|
||||||
for i := uint64(0); i < gap.Uint64(); i++ {
|
for i := uint64(0); i < gap.Uint64(); i++ {
|
||||||
n := new(big.Int).Add(prevNumber, big.NewInt(int64(i+1)))
|
n := new(big.Int).Add(prevNumber, big.NewInt(int64(i+1)))
|
||||||
t := prevTimestamp + timestampIncrement
|
t := prevTimestamp + timestampIncrement
|
||||||
b := simBlock{BlockOverrides: &override.BlockOverrides{Number: (*hexutil.Big)(n), Time: (*hexutil.Uint64)(&t)}}
|
b := simBlock{
|
||||||
|
BlockOverrides: &override.BlockOverrides{
|
||||||
|
Number: (*hexutil.Big)(n),
|
||||||
|
Time: (*hexutil.Uint64)(&t),
|
||||||
|
Withdrawals: &types.Withdrawals{},
|
||||||
|
},
|
||||||
|
}
|
||||||
prevTimestamp = t
|
prevTimestamp = t
|
||||||
res = append(res, b)
|
res = append(res, b)
|
||||||
}
|
}
|
||||||
|
|
@ -405,6 +457,9 @@ func (sim *simulator) makeHeaders(blocks []simBlock) ([]*types.Header, error) {
|
||||||
var parentBeaconRoot *common.Hash
|
var parentBeaconRoot *common.Hash
|
||||||
if sim.chainConfig.IsCancun(overrides.Number.ToInt(), (uint64)(*overrides.Time)) {
|
if sim.chainConfig.IsCancun(overrides.Number.ToInt(), (uint64)(*overrides.Time)) {
|
||||||
parentBeaconRoot = &common.Hash{}
|
parentBeaconRoot = &common.Hash{}
|
||||||
|
if overrides.BeaconRoot != nil {
|
||||||
|
parentBeaconRoot = overrides.BeaconRoot
|
||||||
|
}
|
||||||
}
|
}
|
||||||
header = overrides.MakeHeader(&types.Header{
|
header = overrides.MakeHeader(&types.Header{
|
||||||
UncleHash: types.EmptyUncleHash,
|
UncleHash: types.EmptyUncleHash,
|
||||||
|
|
|
||||||
|
|
@ -402,3 +402,5 @@ func (b *backendMock) SubscribeRemovedLogsEvent(ch chan<- core.RemovedLogsEvent)
|
||||||
func (b *backendMock) Engine() consensus.Engine { return nil }
|
func (b *backendMock) Engine() consensus.Engine { return nil }
|
||||||
|
|
||||||
func (b *backendMock) NewMatcherBackend() filtermaps.MatcherBackend { return nil }
|
func (b *backendMock) NewMatcherBackend() filtermaps.MatcherBackend { return nil }
|
||||||
|
|
||||||
|
func (b *backendMock) HistoryPruningCutoff() uint64 { return 0 }
|
||||||
|
|
|
||||||
|
|
@ -15,7 +15,7 @@
|
||||||
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
// This file contains the code snippets from the developer's guide embedded into
|
// This file contains the code snippets from the developer's guide embedded into
|
||||||
// Go tests. This ensures that any code published in out guides will not break
|
// Go tests. This ensures that any code published in our guides will not break
|
||||||
// accidentally via some code update. If some API changes nonetheless that needs
|
// accidentally via some code update. If some API changes nonetheless that needs
|
||||||
// modifying this file, please port any modification over into the developer's
|
// modifying this file, please port any modification over into the developer's
|
||||||
// guide wiki pages too!
|
// guide wiki pages too!
|
||||||
|
|
|
||||||
|
|
@ -39,7 +39,7 @@ const talkHandlerLaunchTimeout = 400 * time.Millisecond
|
||||||
// Note that talk handlers are expected to come up with a response very quickly, within at
|
// Note that talk handlers are expected to come up with a response very quickly, within at
|
||||||
// most 200ms or so. If the handler takes longer than that, the remote end may time out
|
// most 200ms or so. If the handler takes longer than that, the remote end may time out
|
||||||
// and wont receive the response.
|
// and wont receive the response.
|
||||||
type TalkRequestHandler func(enode.ID, *net.UDPAddr, []byte) []byte
|
type TalkRequestHandler func(*enode.Node, *net.UDPAddr, []byte) []byte
|
||||||
|
|
||||||
type talkSystem struct {
|
type talkSystem struct {
|
||||||
transport *UDPv5
|
transport *UDPv5
|
||||||
|
|
@ -72,13 +72,19 @@ func (t *talkSystem) register(protocol string, handler TalkRequestHandler) {
|
||||||
|
|
||||||
// handleRequest handles a talk request.
|
// handleRequest handles a talk request.
|
||||||
func (t *talkSystem) handleRequest(id enode.ID, addr netip.AddrPort, req *v5wire.TalkRequest) {
|
func (t *talkSystem) handleRequest(id enode.ID, addr netip.AddrPort, req *v5wire.TalkRequest) {
|
||||||
|
n := t.transport.codec.SessionNode(id, addr.String())
|
||||||
|
if n == nil {
|
||||||
|
// The node must be contained in the session here, since we wouldn't have
|
||||||
|
// received the request otherwise.
|
||||||
|
panic("missing node in session")
|
||||||
|
}
|
||||||
t.mutex.Lock()
|
t.mutex.Lock()
|
||||||
handler, ok := t.handlers[req.Protocol]
|
handler, ok := t.handlers[req.Protocol]
|
||||||
t.mutex.Unlock()
|
t.mutex.Unlock()
|
||||||
|
|
||||||
if !ok {
|
if !ok {
|
||||||
resp := &v5wire.TalkResponse{ReqID: req.ReqID}
|
resp := &v5wire.TalkResponse{ReqID: req.ReqID}
|
||||||
t.transport.sendResponse(id, addr, resp)
|
t.transport.sendResponse(n.ID(), addr, resp)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -90,9 +96,9 @@ func (t *talkSystem) handleRequest(id enode.ID, addr netip.AddrPort, req *v5wire
|
||||||
go func() {
|
go func() {
|
||||||
defer func() { t.slots <- struct{}{} }()
|
defer func() { t.slots <- struct{}{} }()
|
||||||
udpAddr := &net.UDPAddr{IP: addr.Addr().AsSlice(), Port: int(addr.Port())}
|
udpAddr := &net.UDPAddr{IP: addr.Addr().AsSlice(), Port: int(addr.Port())}
|
||||||
respMessage := handler(id, udpAddr, req.Message)
|
respMessage := handler(n, udpAddr, req.Message)
|
||||||
resp := &v5wire.TalkResponse{ReqID: req.ReqID, Message: respMessage}
|
resp := &v5wire.TalkResponse{ReqID: req.ReqID, Message: respMessage}
|
||||||
t.transport.sendFromAnotherThread(id, addr, resp)
|
t.transport.sendFromAnotherThread(n.ID(), addr, resp)
|
||||||
}()
|
}()
|
||||||
case <-timeout.C:
|
case <-timeout.C:
|
||||||
// Couldn't get it in time, drop the request.
|
// Couldn't get it in time, drop the request.
|
||||||
|
|
|
||||||
|
|
@ -64,6 +64,9 @@ type codecV5 interface {
|
||||||
// CurrentChallenge returns the most recent WHOAREYOU challenge that was encoded to given node.
|
// CurrentChallenge returns the most recent WHOAREYOU challenge that was encoded to given node.
|
||||||
// This will return a non-nil value if there is an active handshake attempt with the node, and nil otherwise.
|
// This will return a non-nil value if there is an active handshake attempt with the node, and nil otherwise.
|
||||||
CurrentChallenge(id enode.ID, addr string) *v5wire.Whoareyou
|
CurrentChallenge(id enode.ID, addr string) *v5wire.Whoareyou
|
||||||
|
|
||||||
|
// SessionNode returns a node that has completed the handshake.
|
||||||
|
SessionNode(id enode.ID, addr string) *enode.Node
|
||||||
}
|
}
|
||||||
|
|
||||||
// UDPv5 is the implementation of protocol version 5.
|
// UDPv5 is the implementation of protocol version 5.
|
||||||
|
|
|
||||||
|
|
@ -181,29 +181,35 @@ func TestUDPv5_handshakeRepeatChallenge(t *testing.T) {
|
||||||
nonce1 := v5wire.Nonce{1}
|
nonce1 := v5wire.Nonce{1}
|
||||||
nonce2 := v5wire.Nonce{2}
|
nonce2 := v5wire.Nonce{2}
|
||||||
nonce3 := v5wire.Nonce{3}
|
nonce3 := v5wire.Nonce{3}
|
||||||
check := func(p *v5wire.Whoareyou, wantNonce v5wire.Nonce) {
|
var firstAuthTag *v5wire.Nonce
|
||||||
|
check := func(p *v5wire.Whoareyou, authTag, wantNonce v5wire.Nonce) {
|
||||||
t.Helper()
|
t.Helper()
|
||||||
if p.Nonce != wantNonce {
|
if p.Nonce != wantNonce {
|
||||||
t.Error("wrong nonce in WHOAREYOU:", p.Nonce, wantNonce)
|
t.Error("wrong nonce in WHOAREYOU:", p.Nonce, "want:", wantNonce)
|
||||||
|
}
|
||||||
|
if firstAuthTag == nil {
|
||||||
|
firstAuthTag = &authTag
|
||||||
|
} else if authTag != *firstAuthTag {
|
||||||
|
t.Error("wrong auth tag in WHOAREYOU header:", authTag, "want:", *firstAuthTag)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Unknown packet from unknown node.
|
// Unknown packet from unknown node.
|
||||||
test.packetIn(&v5wire.Unknown{Nonce: nonce1})
|
test.packetIn(&v5wire.Unknown{Nonce: nonce1})
|
||||||
test.waitPacketOut(func(p *v5wire.Whoareyou, addr netip.AddrPort, _ v5wire.Nonce) {
|
test.waitPacketOut(func(p *v5wire.Whoareyou, addr netip.AddrPort, authTag v5wire.Nonce) {
|
||||||
check(p, nonce1)
|
check(p, authTag, nonce1)
|
||||||
})
|
})
|
||||||
|
|
||||||
// Second unknown packet. Here we expect the response to reference the
|
// Second unknown packet. Here we expect the response to reference the
|
||||||
// first unknown packet.
|
// first unknown packet.
|
||||||
test.packetIn(&v5wire.Unknown{Nonce: nonce2})
|
test.packetIn(&v5wire.Unknown{Nonce: nonce2})
|
||||||
test.waitPacketOut(func(p *v5wire.Whoareyou, addr netip.AddrPort, _ v5wire.Nonce) {
|
test.waitPacketOut(func(p *v5wire.Whoareyou, addr netip.AddrPort, authTag v5wire.Nonce) {
|
||||||
check(p, nonce1)
|
check(p, authTag, nonce1)
|
||||||
})
|
})
|
||||||
// Third unknown packet. This should still return the first nonce.
|
// Third unknown packet. This should still return the first nonce.
|
||||||
test.packetIn(&v5wire.Unknown{Nonce: nonce3})
|
test.packetIn(&v5wire.Unknown{Nonce: nonce3})
|
||||||
test.waitPacketOut(func(p *v5wire.Whoareyou, addr netip.AddrPort, _ v5wire.Nonce) {
|
test.waitPacketOut(func(p *v5wire.Whoareyou, addr netip.AddrPort, authTag v5wire.Nonce) {
|
||||||
check(p, nonce1)
|
check(p, authTag, nonce1)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -486,7 +492,7 @@ func TestUDPv5_talkHandling(t *testing.T) {
|
||||||
defer test.close()
|
defer test.close()
|
||||||
|
|
||||||
var recvMessage []byte
|
var recvMessage []byte
|
||||||
test.udp.RegisterTalkHandler("test", func(id enode.ID, addr *net.UDPAddr, message []byte) []byte {
|
test.udp.RegisterTalkHandler("test", func(n *enode.Node, addr *net.UDPAddr, message []byte) []byte {
|
||||||
recvMessage = message
|
recvMessage = message
|
||||||
return []byte("test response")
|
return []byte("test response")
|
||||||
})
|
})
|
||||||
|
|
@ -766,20 +772,30 @@ type testCodecFrame struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *testCodec) Encode(toID enode.ID, addr string, p v5wire.Packet, _ *v5wire.Whoareyou) ([]byte, v5wire.Nonce, error) {
|
func (c *testCodec) Encode(toID enode.ID, addr string, p v5wire.Packet, _ *v5wire.Whoareyou) ([]byte, v5wire.Nonce, error) {
|
||||||
|
// To match the behavior of v5wire.Codec, we return the cached encoding of
|
||||||
|
// WHOAREYOU challenges.
|
||||||
|
if wp, ok := p.(*v5wire.Whoareyou); ok && len(wp.Encoded) > 0 {
|
||||||
|
return wp.Encoded, wp.Nonce, nil
|
||||||
|
}
|
||||||
|
|
||||||
c.ctr++
|
c.ctr++
|
||||||
var authTag v5wire.Nonce
|
var authTag v5wire.Nonce
|
||||||
binary.BigEndian.PutUint64(authTag[:], c.ctr)
|
binary.BigEndian.PutUint64(authTag[:], c.ctr)
|
||||||
|
penc, _ := rlp.EncodeToBytes(p)
|
||||||
|
frame, err := rlp.EncodeToBytes(testCodecFrame{c.id, authTag, p.Kind(), penc})
|
||||||
|
if err != nil {
|
||||||
|
return frame, authTag, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Store recently sent challenges.
|
||||||
if w, ok := p.(*v5wire.Whoareyou); ok {
|
if w, ok := p.(*v5wire.Whoareyou); ok {
|
||||||
// Store recently sent Whoareyou challenges.
|
w.Nonce = authTag
|
||||||
|
w.Encoded = frame
|
||||||
if c.sentChallenges == nil {
|
if c.sentChallenges == nil {
|
||||||
c.sentChallenges = make(map[enode.ID]*v5wire.Whoareyou)
|
c.sentChallenges = make(map[enode.ID]*v5wire.Whoareyou)
|
||||||
}
|
}
|
||||||
c.sentChallenges[toID] = w
|
c.sentChallenges[toID] = w
|
||||||
}
|
}
|
||||||
|
|
||||||
penc, _ := rlp.EncodeToBytes(p)
|
|
||||||
frame, err := rlp.EncodeToBytes(testCodecFrame{c.id, authTag, p.Kind(), penc})
|
|
||||||
return frame, authTag, err
|
return frame, authTag, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -795,6 +811,10 @@ func (c *testCodec) Decode(input []byte, addr string) (enode.ID, *enode.Node, v5
|
||||||
return frame.NodeID, nil, p, nil
|
return frame.NodeID, nil, p, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c *testCodec) SessionNode(id enode.ID, addr string) *enode.Node {
|
||||||
|
return c.test.nodesByID[id].Node()
|
||||||
|
}
|
||||||
|
|
||||||
func (c *testCodec) decodeFrame(input []byte) (frame testCodecFrame, p v5wire.Packet, err error) {
|
func (c *testCodec) decodeFrame(input []byte) (frame testCodecFrame, p v5wire.Packet, err error) {
|
||||||
if err = rlp.DecodeBytes(input, &frame); err != nil {
|
if err = rlp.DecodeBytes(input, &frame); err != nil {
|
||||||
return frame, nil, fmt.Errorf("invalid frame: %v", err)
|
return frame, nil, fmt.Errorf("invalid frame: %v", err)
|
||||||
|
|
|
||||||
|
|
@ -189,6 +189,11 @@ func (c *Codec) Encode(id enode.ID, addr string, packet Packet, challenge *Whoar
|
||||||
)
|
)
|
||||||
switch {
|
switch {
|
||||||
case packet.Kind() == WhoareyouPacket:
|
case packet.Kind() == WhoareyouPacket:
|
||||||
|
// just send the WHOAREYOU packet raw again, rather than the re-encoded challenge data
|
||||||
|
w := packet.(*Whoareyou)
|
||||||
|
if len(w.Encoded) > 0 {
|
||||||
|
return w.Encoded, w.Nonce, nil
|
||||||
|
}
|
||||||
head, err = c.encodeWhoareyou(id, packet.(*Whoareyou))
|
head, err = c.encodeWhoareyou(id, packet.(*Whoareyou))
|
||||||
case challenge != nil:
|
case challenge != nil:
|
||||||
// We have an unanswered challenge, send handshake.
|
// We have an unanswered challenge, send handshake.
|
||||||
|
|
@ -218,15 +223,22 @@ func (c *Codec) Encode(id enode.ID, addr string, packet Packet, challenge *Whoar
|
||||||
// Store sent WHOAREYOU challenges.
|
// Store sent WHOAREYOU challenges.
|
||||||
if challenge, ok := packet.(*Whoareyou); ok {
|
if challenge, ok := packet.(*Whoareyou); ok {
|
||||||
challenge.ChallengeData = bytesCopy(&c.buf)
|
challenge.ChallengeData = bytesCopy(&c.buf)
|
||||||
|
enc, err := c.EncodeRaw(id, head, msgData)
|
||||||
|
if err != nil {
|
||||||
|
return nil, Nonce{}, err
|
||||||
|
}
|
||||||
|
challenge.Encoded = bytes.Clone(enc)
|
||||||
c.sc.storeSentHandshake(id, addr, challenge)
|
c.sc.storeSentHandshake(id, addr, challenge)
|
||||||
} else if msgData == nil {
|
return enc, head.Nonce, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if msgData == nil {
|
||||||
headerData := c.buf.Bytes()
|
headerData := c.buf.Bytes()
|
||||||
msgData, err = c.encryptMessage(session, packet, &head, headerData)
|
msgData, err = c.encryptMessage(session, packet, &head, headerData)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, Nonce{}, err
|
return nil, Nonce{}, err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
enc, err := c.EncodeRaw(id, head, msgData)
|
enc, err := c.EncodeRaw(id, head, msgData)
|
||||||
return enc, head.Nonce, err
|
return enc, head.Nonce, err
|
||||||
}
|
}
|
||||||
|
|
@ -347,7 +359,7 @@ func (c *Codec) encodeHandshakeHeader(toID enode.ID, addr string, challenge *Who
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: this should happen when the first authenticated message is received
|
// TODO: this should happen when the first authenticated message is received
|
||||||
c.sc.storeNewSession(toID, addr, session)
|
c.sc.storeNewSession(toID, addr, session, challenge.Node)
|
||||||
|
|
||||||
// Encode the auth header.
|
// Encode the auth header.
|
||||||
var (
|
var (
|
||||||
|
|
@ -522,7 +534,7 @@ func (c *Codec) decodeHandshakeMessage(fromAddr string, head *Header, headerData
|
||||||
}
|
}
|
||||||
|
|
||||||
// Handshake OK, drop the challenge and store the new session keys.
|
// Handshake OK, drop the challenge and store the new session keys.
|
||||||
c.sc.storeNewSession(auth.h.SrcID, fromAddr, session)
|
c.sc.storeNewSession(auth.h.SrcID, fromAddr, session, node)
|
||||||
c.sc.deleteHandshake(auth.h.SrcID, fromAddr)
|
c.sc.deleteHandshake(auth.h.SrcID, fromAddr)
|
||||||
return node, msg, nil
|
return node, msg, nil
|
||||||
}
|
}
|
||||||
|
|
@ -644,6 +656,10 @@ func (c *Codec) decryptMessage(input, nonce, headerData, readKey []byte) (Packet
|
||||||
return DecodeMessage(msgdata[0], msgdata[1:])
|
return DecodeMessage(msgdata[0], msgdata[1:])
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c *Codec) SessionNode(id enode.ID, addr string) *enode.Node {
|
||||||
|
return c.sc.readNode(id, addr)
|
||||||
|
}
|
||||||
|
|
||||||
// checkValid performs some basic validity checks on the header.
|
// checkValid performs some basic validity checks on the header.
|
||||||
// The packetLen here is the length remaining after the static header.
|
// The packetLen here is the length remaining after the static header.
|
||||||
func (h *StaticHeader) checkValid(packetLen int, protocolID [6]byte) error {
|
func (h *StaticHeader) checkValid(packetLen int, protocolID [6]byte) error {
|
||||||
|
|
|
||||||
|
|
@ -166,7 +166,7 @@ func TestHandshake_rekey(t *testing.T) {
|
||||||
readKey: []byte("BBBBBBBBBBBBBBBB"),
|
readKey: []byte("BBBBBBBBBBBBBBBB"),
|
||||||
writeKey: []byte("AAAAAAAAAAAAAAAA"),
|
writeKey: []byte("AAAAAAAAAAAAAAAA"),
|
||||||
}
|
}
|
||||||
net.nodeA.c.sc.storeNewSession(net.nodeB.id(), net.nodeB.addr(), session)
|
net.nodeA.c.sc.storeNewSession(net.nodeB.id(), net.nodeB.addr(), session, net.nodeB.n())
|
||||||
|
|
||||||
// A -> B FINDNODE (encrypted with zero keys)
|
// A -> B FINDNODE (encrypted with zero keys)
|
||||||
findnode, authTag := net.nodeA.encode(t, net.nodeB, &Findnode{})
|
findnode, authTag := net.nodeA.encode(t, net.nodeB, &Findnode{})
|
||||||
|
|
@ -209,8 +209,8 @@ func TestHandshake_rekey2(t *testing.T) {
|
||||||
readKey: []byte("CCCCCCCCCCCCCCCC"),
|
readKey: []byte("CCCCCCCCCCCCCCCC"),
|
||||||
writeKey: []byte("DDDDDDDDDDDDDDDD"),
|
writeKey: []byte("DDDDDDDDDDDDDDDD"),
|
||||||
}
|
}
|
||||||
net.nodeA.c.sc.storeNewSession(net.nodeB.id(), net.nodeB.addr(), initKeysA)
|
net.nodeA.c.sc.storeNewSession(net.nodeB.id(), net.nodeB.addr(), initKeysA, net.nodeB.n())
|
||||||
net.nodeB.c.sc.storeNewSession(net.nodeA.id(), net.nodeA.addr(), initKeysB)
|
net.nodeB.c.sc.storeNewSession(net.nodeA.id(), net.nodeA.addr(), initKeysB, net.nodeA.n())
|
||||||
|
|
||||||
// A -> B FINDNODE encrypted with initKeysA
|
// A -> B FINDNODE encrypted with initKeysA
|
||||||
findnode, authTag := net.nodeA.encode(t, net.nodeB, &Findnode{Distances: []uint{3}})
|
findnode, authTag := net.nodeA.encode(t, net.nodeB, &Findnode{Distances: []uint{3}})
|
||||||
|
|
@ -362,8 +362,8 @@ func TestTestVectorsV5(t *testing.T) {
|
||||||
ENRSeq: 2,
|
ENRSeq: 2,
|
||||||
},
|
},
|
||||||
prep: func(net *handshakeTest) {
|
prep: func(net *handshakeTest) {
|
||||||
net.nodeA.c.sc.storeNewSession(idB, addr, session)
|
net.nodeA.c.sc.storeNewSession(idB, addr, session, net.nodeB.n())
|
||||||
net.nodeB.c.sc.storeNewSession(idA, addr, session.keysFlipped())
|
net.nodeB.c.sc.storeNewSession(idA, addr, session.keysFlipped(), net.nodeA.n())
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|
@ -499,8 +499,8 @@ func BenchmarkV5_DecodePing(b *testing.B) {
|
||||||
readKey: []byte{233, 203, 93, 195, 86, 47, 177, 186, 227, 43, 2, 141, 244, 230, 120, 17},
|
readKey: []byte{233, 203, 93, 195, 86, 47, 177, 186, 227, 43, 2, 141, 244, 230, 120, 17},
|
||||||
writeKey: []byte{79, 145, 252, 171, 167, 216, 252, 161, 208, 190, 176, 106, 214, 39, 178, 134},
|
writeKey: []byte{79, 145, 252, 171, 167, 216, 252, 161, 208, 190, 176, 106, 214, 39, 178, 134},
|
||||||
}
|
}
|
||||||
net.nodeA.c.sc.storeNewSession(net.nodeB.id(), net.nodeB.addr(), session)
|
net.nodeA.c.sc.storeNewSession(net.nodeB.id(), net.nodeB.addr(), session, net.nodeB.n())
|
||||||
net.nodeB.c.sc.storeNewSession(net.nodeA.id(), net.nodeA.addr(), session.keysFlipped())
|
net.nodeB.c.sc.storeNewSession(net.nodeA.id(), net.nodeA.addr(), session.keysFlipped(), net.nodeA.n())
|
||||||
addrB := net.nodeA.addr()
|
addrB := net.nodeA.addr()
|
||||||
ping := &Ping{ReqID: []byte("reqid"), ENRSeq: 5}
|
ping := &Ping{ReqID: []byte("reqid"), ENRSeq: 5}
|
||||||
enc, _, err := net.nodeA.c.Encode(net.nodeB.id(), addrB, ping, nil)
|
enc, _, err := net.nodeA.c.Encode(net.nodeB.id(), addrB, ping, nil)
|
||||||
|
|
|
||||||
|
|
@ -73,6 +73,9 @@ type (
|
||||||
Node *enode.Node
|
Node *enode.Node
|
||||||
|
|
||||||
sent mclock.AbsTime // for handshake GC.
|
sent mclock.AbsTime // for handshake GC.
|
||||||
|
|
||||||
|
// Encoded is packet raw data for sending out, but should not be include in the RLP encoding.
|
||||||
|
Encoded []byte `rlp:"-"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// PING is sent during liveness checks.
|
// PING is sent during liveness checks.
|
||||||
|
|
|
||||||
|
|
@ -54,11 +54,12 @@ type session struct {
|
||||||
writeKey []byte
|
writeKey []byte
|
||||||
readKey []byte
|
readKey []byte
|
||||||
nonceCounter uint32
|
nonceCounter uint32
|
||||||
|
node *enode.Node
|
||||||
}
|
}
|
||||||
|
|
||||||
// keysFlipped returns a copy of s with the read and write keys flipped.
|
// keysFlipped returns a copy of s with the read and write keys flipped.
|
||||||
func (s *session) keysFlipped() *session {
|
func (s *session) keysFlipped() *session {
|
||||||
return &session{s.readKey, s.writeKey, s.nonceCounter}
|
return &session{s.readKey, s.writeKey, s.nonceCounter, s.node}
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewSessionCache(maxItems int, clock mclock.Clock) *SessionCache {
|
func NewSessionCache(maxItems int, clock mclock.Clock) *SessionCache {
|
||||||
|
|
@ -103,8 +104,19 @@ func (sc *SessionCache) readKey(id enode.ID, addr string) []byte {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (sc *SessionCache) readNode(id enode.ID, addr string) *enode.Node {
|
||||||
|
if s := sc.session(id, addr); s != nil {
|
||||||
|
return s.node
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
// storeNewSession stores new encryption keys in the cache.
|
// storeNewSession stores new encryption keys in the cache.
|
||||||
func (sc *SessionCache) storeNewSession(id enode.ID, addr string, s *session) {
|
func (sc *SessionCache) storeNewSession(id enode.ID, addr string, s *session, n *enode.Node) {
|
||||||
|
if n == nil {
|
||||||
|
panic("nil node in storeNewSession")
|
||||||
|
}
|
||||||
|
s.node = n
|
||||||
sc.sessions.Add(sessionID{id, addr}, s)
|
sc.sessions.Add(sessionID{id, addr}, s)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -140,7 +140,7 @@ type ExtIP net.IP
|
||||||
|
|
||||||
func (n ExtIP) ExternalIP() (net.IP, error) { return net.IP(n), nil }
|
func (n ExtIP) ExternalIP() (net.IP, error) { return net.IP(n), nil }
|
||||||
func (n ExtIP) String() string { return fmt.Sprintf("ExtIP(%v)", net.IP(n)) }
|
func (n ExtIP) String() string { return fmt.Sprintf("ExtIP(%v)", net.IP(n)) }
|
||||||
func (n ExtIP) MarshalText() ([]byte, error) { return []byte(fmt.Sprintf("extip:%v", net.IP(n))), nil }
|
func (n ExtIP) MarshalText() ([]byte, error) { return fmt.Appendf(nil, "extip:%v", net.IP(n)), nil }
|
||||||
|
|
||||||
// These do nothing.
|
// These do nothing.
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -71,7 +71,7 @@ func (n *pmp) DeleteMapping(protocol string, extport, intport int) (err error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (n *pmp) MarshalText() ([]byte, error) {
|
func (n *pmp) MarshalText() ([]byte, error) {
|
||||||
return []byte(fmt.Sprintf("natpmp:%v", n.gw)), nil
|
return fmt.Appendf(nil, "natpmp:%v", n.gw), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func discoverPMP() Interface {
|
func discoverPMP() Interface {
|
||||||
|
|
|
||||||
|
|
@ -82,7 +82,7 @@ func (n *upnp) ExternalIP() (addr net.IP, err error) {
|
||||||
func (n *upnp) AddMapping(protocol string, extport, intport int, desc string, lifetime time.Duration) (uint16, error) {
|
func (n *upnp) AddMapping(protocol string, extport, intport int, desc string, lifetime time.Duration) (uint16, error) {
|
||||||
ip, err := n.internalAddress()
|
ip, err := n.internalAddress()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return 0, nil // TODO: Shouldn't we return the error?
|
return 0, err
|
||||||
}
|
}
|
||||||
protocol = strings.ToUpper(protocol)
|
protocol = strings.ToUpper(protocol)
|
||||||
lifetimeS := uint32(lifetime / time.Second)
|
lifetimeS := uint32(lifetime / time.Second)
|
||||||
|
|
@ -94,14 +94,15 @@ func (n *upnp) AddMapping(protocol string, extport, intport int, desc string, li
|
||||||
if err == nil {
|
if err == nil {
|
||||||
return uint16(extport), nil
|
return uint16(extport), nil
|
||||||
}
|
}
|
||||||
|
// Try addAnyPortMapping if mapping specified port didn't work.
|
||||||
return uint16(extport), n.withRateLimit(func() error {
|
err = n.withRateLimit(func() error {
|
||||||
p, err := n.addAnyPortMapping(protocol, extport, intport, ip, desc, lifetimeS)
|
p, err := n.addAnyPortMapping(protocol, extport, intport, ip, desc, lifetimeS)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
extport = int(p)
|
extport = int(p)
|
||||||
}
|
}
|
||||||
return err
|
return err
|
||||||
})
|
})
|
||||||
|
return uint16(extport), err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (n *upnp) addAnyPortMapping(protocol string, extport, intport int, ip net.IP, desc string, lifetimeS uint32) (uint16, error) {
|
func (n *upnp) addAnyPortMapping(protocol string, extport, intport int, ip net.IP, desc string, lifetimeS uint32) (uint16, error) {
|
||||||
|
|
|
||||||
|
|
@ -63,11 +63,11 @@ type jsonWriter interface {
|
||||||
type BlockNumber int64
|
type BlockNumber int64
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
EarliestBlockNumber = BlockNumber(-5)
|
||||||
SafeBlockNumber = BlockNumber(-4)
|
SafeBlockNumber = BlockNumber(-4)
|
||||||
FinalizedBlockNumber = BlockNumber(-3)
|
FinalizedBlockNumber = BlockNumber(-3)
|
||||||
LatestBlockNumber = BlockNumber(-2)
|
LatestBlockNumber = BlockNumber(-2)
|
||||||
PendingBlockNumber = BlockNumber(-1)
|
PendingBlockNumber = BlockNumber(-1)
|
||||||
EarliestBlockNumber = BlockNumber(0)
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// UnmarshalJSON parses the given JSON fragment into a BlockNumber. It supports:
|
// UnmarshalJSON parses the given JSON fragment into a BlockNumber. It supports:
|
||||||
|
|
|
||||||
|
|
@ -282,7 +282,7 @@ func TestTypedDataArrayValidate(t *testing.T) {
|
||||||
messageHash, tErr := td.HashStruct(td.PrimaryType, td.Message)
|
messageHash, tErr := td.HashStruct(td.PrimaryType, td.Message)
|
||||||
assert.NoError(t, tErr, "failed to hash message: %v", tErr)
|
assert.NoError(t, tErr, "failed to hash message: %v", tErr)
|
||||||
|
|
||||||
digest := crypto.Keccak256Hash([]byte(fmt.Sprintf("%s%s%s", "\x19\x01", string(domainSeparator), string(messageHash))))
|
digest := crypto.Keccak256Hash(fmt.Appendf(nil, "%s%s%s", "\x19\x01", string(domainSeparator), string(messageHash)))
|
||||||
assert.Equal(t, tc.Digest, digest.String(), "digest doesn't not match")
|
assert.Equal(t, tc.Digest, digest.String(), "digest doesn't not match")
|
||||||
|
|
||||||
assert.NoError(t, td.validate(), "validation failed", tErr)
|
assert.NoError(t, td.validate(), "validation failed", tErr)
|
||||||
|
|
|
||||||
|
|
@ -369,7 +369,7 @@ func sign(typedData apitypes.TypedData) ([]byte, []byte, error) {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, err
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
rawData := []byte(fmt.Sprintf("\x19\x01%s%s", string(domainSeparator), string(typedDataHash)))
|
rawData := fmt.Appendf(nil, "\x19\x01%s%s", string(domainSeparator), string(typedDataHash))
|
||||||
sighash := crypto.Keccak256(rawData)
|
sighash := crypto.Keccak256(rawData)
|
||||||
return typedDataHash, sighash, nil
|
return typedDataHash, sighash, nil
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1 +1 @@
|
||||||
Subproject commit faf33b471465d3c6cdc3d04fbd690895f78d33f2
|
Subproject commit 81862e4848585a438d64f911a19b3825f0f4cd95
|
||||||
|
|
@ -57,32 +57,26 @@ func (c *committer) commit(path []byte, n node, parallel bool) node {
|
||||||
// Commit children, then parent, and remove the dirty flag.
|
// Commit children, then parent, and remove the dirty flag.
|
||||||
switch cn := n.(type) {
|
switch cn := n.(type) {
|
||||||
case *shortNode:
|
case *shortNode:
|
||||||
// Commit child
|
|
||||||
collapsed := cn.copy()
|
|
||||||
|
|
||||||
// If the child is fullNode, recursively commit,
|
// If the child is fullNode, recursively commit,
|
||||||
// otherwise it can only be hashNode or valueNode.
|
// otherwise it can only be hashNode or valueNode.
|
||||||
if _, ok := cn.Val.(*fullNode); ok {
|
if _, ok := cn.Val.(*fullNode); ok {
|
||||||
collapsed.Val = c.commit(append(path, cn.Key...), cn.Val, false)
|
cn.Val = c.commit(append(path, cn.Key...), cn.Val, false)
|
||||||
}
|
}
|
||||||
// The key needs to be copied, since we're adding it to the
|
// The key needs to be copied, since we're adding it to the
|
||||||
// modified nodeset.
|
// modified nodeset.
|
||||||
collapsed.Key = hexToCompact(cn.Key)
|
cn.Key = hexToCompact(cn.Key)
|
||||||
hashedNode := c.store(path, collapsed)
|
hashedNode := c.store(path, cn)
|
||||||
if hn, ok := hashedNode.(hashNode); ok {
|
if hn, ok := hashedNode.(hashNode); ok {
|
||||||
return hn
|
return hn
|
||||||
}
|
}
|
||||||
return collapsed
|
return cn
|
||||||
case *fullNode:
|
case *fullNode:
|
||||||
hashedKids := c.commitChildren(path, cn, parallel)
|
c.commitChildren(path, cn, parallel)
|
||||||
collapsed := cn.copy()
|
hashedNode := c.store(path, cn)
|
||||||
collapsed.Children = hashedKids
|
|
||||||
|
|
||||||
hashedNode := c.store(path, collapsed)
|
|
||||||
if hn, ok := hashedNode.(hashNode); ok {
|
if hn, ok := hashedNode.(hashNode); ok {
|
||||||
return hn
|
return hn
|
||||||
}
|
}
|
||||||
return collapsed
|
return cn
|
||||||
case hashNode:
|
case hashNode:
|
||||||
return cn
|
return cn
|
||||||
default:
|
default:
|
||||||
|
|
@ -92,11 +86,10 @@ func (c *committer) commit(path []byte, n node, parallel bool) node {
|
||||||
}
|
}
|
||||||
|
|
||||||
// commitChildren commits the children of the given fullnode
|
// commitChildren commits the children of the given fullnode
|
||||||
func (c *committer) commitChildren(path []byte, n *fullNode, parallel bool) [17]node {
|
func (c *committer) commitChildren(path []byte, n *fullNode, parallel bool) {
|
||||||
var (
|
var (
|
||||||
wg sync.WaitGroup
|
wg sync.WaitGroup
|
||||||
nodesMu sync.Mutex
|
nodesMu sync.Mutex
|
||||||
children [17]node
|
|
||||||
)
|
)
|
||||||
for i := 0; i < 16; i++ {
|
for i := 0; i < 16; i++ {
|
||||||
child := n.Children[i]
|
child := n.Children[i]
|
||||||
|
|
@ -106,22 +99,21 @@ func (c *committer) commitChildren(path []byte, n *fullNode, parallel bool) [17]
|
||||||
// If it's the hashed child, save the hash value directly.
|
// If it's the hashed child, save the hash value directly.
|
||||||
// Note: it's impossible that the child in range [0, 15]
|
// Note: it's impossible that the child in range [0, 15]
|
||||||
// is a valueNode.
|
// is a valueNode.
|
||||||
if hn, ok := child.(hashNode); ok {
|
if _, ok := child.(hashNode); ok {
|
||||||
children[i] = hn
|
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
// Commit the child recursively and store the "hashed" value.
|
// Commit the child recursively and store the "hashed" value.
|
||||||
// Note the returned node can be some embedded nodes, so it's
|
// Note the returned node can be some embedded nodes, so it's
|
||||||
// possible the type is not hashNode.
|
// possible the type is not hashNode.
|
||||||
if !parallel {
|
if !parallel {
|
||||||
children[i] = c.commit(append(path, byte(i)), child, false)
|
n.Children[i] = c.commit(append(path, byte(i)), child, false)
|
||||||
} else {
|
} else {
|
||||||
wg.Add(1)
|
wg.Add(1)
|
||||||
go func(index int) {
|
go func(index int) {
|
||||||
p := append(path, byte(index))
|
p := append(path, byte(index))
|
||||||
childSet := trienode.NewNodeSet(c.nodes.Owner)
|
childSet := trienode.NewNodeSet(c.nodes.Owner)
|
||||||
childCommitter := newCommitter(childSet, c.tracer, c.collectLeaf)
|
childCommitter := newCommitter(childSet, c.tracer, c.collectLeaf)
|
||||||
children[index] = childCommitter.commit(p, child, false)
|
n.Children[index] = childCommitter.commit(p, child, false)
|
||||||
nodesMu.Lock()
|
nodesMu.Lock()
|
||||||
c.nodes.MergeSet(childSet)
|
c.nodes.MergeSet(childSet)
|
||||||
nodesMu.Unlock()
|
nodesMu.Unlock()
|
||||||
|
|
@ -132,11 +124,6 @@ func (c *committer) commitChildren(path []byte, n *fullNode, parallel bool) [17]
|
||||||
if parallel {
|
if parallel {
|
||||||
wg.Wait()
|
wg.Wait()
|
||||||
}
|
}
|
||||||
// For the 17th child, it's possible the type is valuenode.
|
|
||||||
if n.Children[16] != nil {
|
|
||||||
children[16] = n.Children[16]
|
|
||||||
}
|
|
||||||
return children
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// store hashes the node n and adds it to the modified nodeset. If leaf collection
|
// store hashes the node n and adds it to the modified nodeset. If leaf collection
|
||||||
|
|
|
||||||
|
|
@ -53,62 +53,56 @@ func returnHasherToPool(h *hasher) {
|
||||||
hasherPool.Put(h)
|
hasherPool.Put(h)
|
||||||
}
|
}
|
||||||
|
|
||||||
// hash collapses a node down into a hash node, also returning a copy of the
|
// hash collapses a node down into a hash node.
|
||||||
// original node initialized with the computed hash to replace the original one.
|
func (h *hasher) hash(n node, force bool) node {
|
||||||
func (h *hasher) hash(n node, force bool) (hashed node, cached node) {
|
|
||||||
// Return the cached hash if it's available
|
// Return the cached hash if it's available
|
||||||
if hash, _ := n.cache(); hash != nil {
|
if hash, _ := n.cache(); hash != nil {
|
||||||
return hash, n
|
return hash
|
||||||
}
|
}
|
||||||
// Trie not processed yet, walk the children
|
// Trie not processed yet, walk the children
|
||||||
switch n := n.(type) {
|
switch n := n.(type) {
|
||||||
case *shortNode:
|
case *shortNode:
|
||||||
collapsed, cached := h.hashShortNodeChildren(n)
|
collapsed := h.hashShortNodeChildren(n)
|
||||||
hashed := h.shortnodeToHash(collapsed, force)
|
hashed := h.shortnodeToHash(collapsed, force)
|
||||||
// We need to retain the possibly _not_ hashed node, in case it was too
|
|
||||||
// small to be hashed
|
|
||||||
if hn, ok := hashed.(hashNode); ok {
|
if hn, ok := hashed.(hashNode); ok {
|
||||||
cached.flags.hash = hn
|
n.flags.hash = hn
|
||||||
} else {
|
} else {
|
||||||
cached.flags.hash = nil
|
n.flags.hash = nil
|
||||||
}
|
}
|
||||||
return hashed, cached
|
return hashed
|
||||||
case *fullNode:
|
case *fullNode:
|
||||||
collapsed, cached := h.hashFullNodeChildren(n)
|
collapsed := h.hashFullNodeChildren(n)
|
||||||
hashed = h.fullnodeToHash(collapsed, force)
|
hashed := h.fullnodeToHash(collapsed, force)
|
||||||
if hn, ok := hashed.(hashNode); ok {
|
if hn, ok := hashed.(hashNode); ok {
|
||||||
cached.flags.hash = hn
|
n.flags.hash = hn
|
||||||
} else {
|
} else {
|
||||||
cached.flags.hash = nil
|
n.flags.hash = nil
|
||||||
}
|
}
|
||||||
return hashed, cached
|
return hashed
|
||||||
default:
|
default:
|
||||||
// Value and hash nodes don't have children, so they're left as were
|
// Value and hash nodes don't have children, so they're left as were
|
||||||
return n, n
|
return n
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// hashShortNodeChildren collapses the short node. The returned collapsed node
|
// hashShortNodeChildren returns a copy of the supplied shortNode, with its child
|
||||||
// holds a live reference to the Key, and must not be modified.
|
// being replaced by either the hash or an embedded node if the child is small.
|
||||||
func (h *hasher) hashShortNodeChildren(n *shortNode) (collapsed, cached *shortNode) {
|
func (h *hasher) hashShortNodeChildren(n *shortNode) *shortNode {
|
||||||
// Hash the short node's child, caching the newly hashed subtree
|
var collapsed shortNode
|
||||||
collapsed, cached = n.copy(), n.copy()
|
|
||||||
// Previously, we did copy this one. We don't seem to need to actually
|
|
||||||
// do that, since we don't overwrite/reuse keys
|
|
||||||
// cached.Key = common.CopyBytes(n.Key)
|
|
||||||
collapsed.Key = hexToCompact(n.Key)
|
collapsed.Key = hexToCompact(n.Key)
|
||||||
// Unless the child is a valuenode or hashnode, hash it
|
|
||||||
switch n.Val.(type) {
|
switch n.Val.(type) {
|
||||||
case *fullNode, *shortNode:
|
case *fullNode, *shortNode:
|
||||||
collapsed.Val, cached.Val = h.hash(n.Val, false)
|
collapsed.Val = h.hash(n.Val, false)
|
||||||
|
default:
|
||||||
|
collapsed.Val = n.Val
|
||||||
}
|
}
|
||||||
return collapsed, cached
|
return &collapsed
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *hasher) hashFullNodeChildren(n *fullNode) (collapsed *fullNode, cached *fullNode) {
|
// hashFullNodeChildren returns a copy of the supplied fullNode, with its child
|
||||||
// Hash the full node's children, caching the newly hashed subtrees
|
// being replaced by either the hash or an embedded node if the child is small.
|
||||||
cached = n.copy()
|
func (h *hasher) hashFullNodeChildren(n *fullNode) *fullNode {
|
||||||
collapsed = n.copy()
|
var children [17]node
|
||||||
if h.parallel {
|
if h.parallel {
|
||||||
var wg sync.WaitGroup
|
var wg sync.WaitGroup
|
||||||
wg.Add(16)
|
wg.Add(16)
|
||||||
|
|
@ -116,9 +110,9 @@ func (h *hasher) hashFullNodeChildren(n *fullNode) (collapsed *fullNode, cached
|
||||||
go func(i int) {
|
go func(i int) {
|
||||||
hasher := newHasher(false)
|
hasher := newHasher(false)
|
||||||
if child := n.Children[i]; child != nil {
|
if child := n.Children[i]; child != nil {
|
||||||
collapsed.Children[i], cached.Children[i] = hasher.hash(child, false)
|
children[i] = hasher.hash(child, false)
|
||||||
} else {
|
} else {
|
||||||
collapsed.Children[i] = nilValueNode
|
children[i] = nilValueNode
|
||||||
}
|
}
|
||||||
returnHasherToPool(hasher)
|
returnHasherToPool(hasher)
|
||||||
wg.Done()
|
wg.Done()
|
||||||
|
|
@ -128,19 +122,21 @@ func (h *hasher) hashFullNodeChildren(n *fullNode) (collapsed *fullNode, cached
|
||||||
} else {
|
} else {
|
||||||
for i := 0; i < 16; i++ {
|
for i := 0; i < 16; i++ {
|
||||||
if child := n.Children[i]; child != nil {
|
if child := n.Children[i]; child != nil {
|
||||||
collapsed.Children[i], cached.Children[i] = h.hash(child, false)
|
children[i] = h.hash(child, false)
|
||||||
} else {
|
} else {
|
||||||
collapsed.Children[i] = nilValueNode
|
children[i] = nilValueNode
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return collapsed, cached
|
if n.Children[16] != nil {
|
||||||
|
children[16] = n.Children[16]
|
||||||
|
}
|
||||||
|
return &fullNode{flags: nodeFlag{}, Children: children}
|
||||||
}
|
}
|
||||||
|
|
||||||
// shortnodeToHash creates a hashNode from a shortNode. The supplied shortnode
|
// shortNodeToHash computes the hash of the given shortNode. The shortNode must
|
||||||
// should have hex-type Key, which will be converted (without modification)
|
// first be collapsed, with its key converted to compact form. If the RLP-encoded
|
||||||
// into compact form for RLP encoding.
|
// node data is smaller than 32 bytes, the node itself is returned.
|
||||||
// If the rlp data is smaller than 32 bytes, `nil` is returned.
|
|
||||||
func (h *hasher) shortnodeToHash(n *shortNode, force bool) node {
|
func (h *hasher) shortnodeToHash(n *shortNode, force bool) node {
|
||||||
n.encode(h.encbuf)
|
n.encode(h.encbuf)
|
||||||
enc := h.encodedBytes()
|
enc := h.encodedBytes()
|
||||||
|
|
@ -151,8 +147,8 @@ func (h *hasher) shortnodeToHash(n *shortNode, force bool) node {
|
||||||
return h.hashData(enc)
|
return h.hashData(enc)
|
||||||
}
|
}
|
||||||
|
|
||||||
// fullnodeToHash is used to create a hashNode from a fullNode, (which
|
// fullnodeToHash computes the hash of the given fullNode. If the RLP-encoded
|
||||||
// may contain nil values)
|
// node data is smaller than 32 bytes, the node itself is returned.
|
||||||
func (h *hasher) fullnodeToHash(n *fullNode, force bool) node {
|
func (h *hasher) fullnodeToHash(n *fullNode, force bool) node {
|
||||||
n.encode(h.encbuf)
|
n.encode(h.encbuf)
|
||||||
enc := h.encodedBytes()
|
enc := h.encodedBytes()
|
||||||
|
|
@ -203,10 +199,10 @@ func (h *hasher) hashDataTo(dst, data []byte) {
|
||||||
func (h *hasher) proofHash(original node) (collapsed, hashed node) {
|
func (h *hasher) proofHash(original node) (collapsed, hashed node) {
|
||||||
switch n := original.(type) {
|
switch n := original.(type) {
|
||||||
case *shortNode:
|
case *shortNode:
|
||||||
sn, _ := h.hashShortNodeChildren(n)
|
sn := h.hashShortNodeChildren(n)
|
||||||
return sn, h.shortnodeToHash(sn, false)
|
return sn, h.shortnodeToHash(sn, false)
|
||||||
case *fullNode:
|
case *fullNode:
|
||||||
fn, _ := h.hashFullNodeChildren(n)
|
fn := h.hashFullNodeChildren(n)
|
||||||
return fn, h.fullnodeToHash(fn, false)
|
return fn, h.fullnodeToHash(fn, false)
|
||||||
default:
|
default:
|
||||||
// Value and hash nodes don't have children, so they're left as were
|
// Value and hash nodes don't have children, so they're left as were
|
||||||
|
|
|
||||||
14
trie/node.go
14
trie/node.go
|
|
@ -79,15 +79,19 @@ func (n *fullNode) EncodeRLP(w io.Writer) error {
|
||||||
return eb.Flush()
|
return eb.Flush()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (n *fullNode) copy() *fullNode { copy := *n; return © }
|
|
||||||
func (n *shortNode) copy() *shortNode { copy := *n; return © }
|
|
||||||
|
|
||||||
// nodeFlag contains caching-related metadata about a node.
|
// nodeFlag contains caching-related metadata about a node.
|
||||||
type nodeFlag struct {
|
type nodeFlag struct {
|
||||||
hash hashNode // cached hash of the node (may be nil)
|
hash hashNode // cached hash of the node (may be nil)
|
||||||
dirty bool // whether the node has changes that must be written to the database
|
dirty bool // whether the node has changes that must be written to the database
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (n nodeFlag) copy() nodeFlag {
|
||||||
|
return nodeFlag{
|
||||||
|
hash: common.CopyBytes(n.hash),
|
||||||
|
dirty: n.dirty,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func (n *fullNode) cache() (hashNode, bool) { return n.flags.hash, n.flags.dirty }
|
func (n *fullNode) cache() (hashNode, bool) { return n.flags.hash, n.flags.dirty }
|
||||||
func (n *shortNode) cache() (hashNode, bool) { return n.flags.hash, n.flags.dirty }
|
func (n *shortNode) cache() (hashNode, bool) { return n.flags.hash, n.flags.dirty }
|
||||||
func (n hashNode) cache() (hashNode, bool) { return nil, true }
|
func (n hashNode) cache() (hashNode, bool) { return nil, true }
|
||||||
|
|
@ -228,7 +232,9 @@ func decodeRef(buf []byte) (node, []byte, error) {
|
||||||
err := fmt.Errorf("oversized embedded node (size is %d bytes, want size < %d)", size, hashLen)
|
err := fmt.Errorf("oversized embedded node (size is %d bytes, want size < %d)", size, hashLen)
|
||||||
return nil, buf, err
|
return nil, buf, err
|
||||||
}
|
}
|
||||||
n, err := decodeNode(nil, buf)
|
// The buffer content has already been copied or is safe to use;
|
||||||
|
// no additional copy is required.
|
||||||
|
n, err := decodeNodeUnsafe(nil, buf)
|
||||||
return n, rest, err
|
return n, rest, err
|
||||||
case kind == rlp.String && len(val) == 0:
|
case kind == rlp.String && len(val) == 0:
|
||||||
// empty node
|
// empty node
|
||||||
|
|
|
||||||
68
trie/trie.go
68
trie/trie.go
|
|
@ -29,11 +29,11 @@ import (
|
||||||
"github.com/ethereum/go-ethereum/triedb/database"
|
"github.com/ethereum/go-ethereum/triedb/database"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Trie is a Merkle Patricia Trie. Use New to create a trie that sits on
|
// Trie represents a Merkle Patricia Trie. Use New to create a trie that operates
|
||||||
// top of a database. Whenever trie performs a commit operation, the generated
|
// on top of a node database. During a commit operation, the trie collects all
|
||||||
// nodes will be gathered and returned in a set. Once the trie is committed,
|
// modified nodes into a set for return. After committing, the trie becomes
|
||||||
// it's not usable anymore. Callers have to re-create the trie with new root
|
// unusable, and callers must recreate it with the new root based on the updated
|
||||||
// based on the updated trie database.
|
// trie database.
|
||||||
//
|
//
|
||||||
// Trie is not safe for concurrent use.
|
// Trie is not safe for concurrent use.
|
||||||
type Trie struct {
|
type Trie struct {
|
||||||
|
|
@ -67,13 +67,13 @@ func (t *Trie) newFlag() nodeFlag {
|
||||||
// Copy returns a copy of Trie.
|
// Copy returns a copy of Trie.
|
||||||
func (t *Trie) Copy() *Trie {
|
func (t *Trie) Copy() *Trie {
|
||||||
return &Trie{
|
return &Trie{
|
||||||
root: t.root,
|
root: copyNode(t.root),
|
||||||
owner: t.owner,
|
owner: t.owner,
|
||||||
committed: t.committed,
|
committed: t.committed,
|
||||||
|
unhashed: t.unhashed,
|
||||||
|
uncommitted: t.uncommitted,
|
||||||
reader: t.reader,
|
reader: t.reader,
|
||||||
tracer: t.tracer.copy(),
|
tracer: t.tracer.copy(),
|
||||||
uncommitted: t.uncommitted,
|
|
||||||
unhashed: t.unhashed,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -169,14 +169,12 @@ func (t *Trie) get(origNode node, key []byte, pos int) (value []byte, newnode no
|
||||||
}
|
}
|
||||||
value, newnode, didResolve, err = t.get(n.Val, key, pos+len(n.Key))
|
value, newnode, didResolve, err = t.get(n.Val, key, pos+len(n.Key))
|
||||||
if err == nil && didResolve {
|
if err == nil && didResolve {
|
||||||
n = n.copy()
|
|
||||||
n.Val = newnode
|
n.Val = newnode
|
||||||
}
|
}
|
||||||
return value, n, didResolve, err
|
return value, n, didResolve, err
|
||||||
case *fullNode:
|
case *fullNode:
|
||||||
value, newnode, didResolve, err = t.get(n.Children[key[pos]], key, pos+1)
|
value, newnode, didResolve, err = t.get(n.Children[key[pos]], key, pos+1)
|
||||||
if err == nil && didResolve {
|
if err == nil && didResolve {
|
||||||
n = n.copy()
|
|
||||||
n.Children[key[pos]] = newnode
|
n.Children[key[pos]] = newnode
|
||||||
}
|
}
|
||||||
return value, n, didResolve, err
|
return value, n, didResolve, err
|
||||||
|
|
@ -257,7 +255,6 @@ func (t *Trie) getNode(origNode node, path []byte, pos int) (item []byte, newnod
|
||||||
}
|
}
|
||||||
item, newnode, resolved, err = t.getNode(n.Val, path, pos+len(n.Key))
|
item, newnode, resolved, err = t.getNode(n.Val, path, pos+len(n.Key))
|
||||||
if err == nil && resolved > 0 {
|
if err == nil && resolved > 0 {
|
||||||
n = n.copy()
|
|
||||||
n.Val = newnode
|
n.Val = newnode
|
||||||
}
|
}
|
||||||
return item, n, resolved, err
|
return item, n, resolved, err
|
||||||
|
|
@ -265,7 +262,6 @@ func (t *Trie) getNode(origNode node, path []byte, pos int) (item []byte, newnod
|
||||||
case *fullNode:
|
case *fullNode:
|
||||||
item, newnode, resolved, err = t.getNode(n.Children[path[pos]], path, pos+1)
|
item, newnode, resolved, err = t.getNode(n.Children[path[pos]], path, pos+1)
|
||||||
if err == nil && resolved > 0 {
|
if err == nil && resolved > 0 {
|
||||||
n = n.copy()
|
|
||||||
n.Children[path[pos]] = newnode
|
n.Children[path[pos]] = newnode
|
||||||
}
|
}
|
||||||
return item, n, resolved, err
|
return item, n, resolved, err
|
||||||
|
|
@ -375,7 +371,6 @@ func (t *Trie) insert(n node, prefix, key []byte, value node) (bool, node, error
|
||||||
if !dirty || err != nil {
|
if !dirty || err != nil {
|
||||||
return false, n, err
|
return false, n, err
|
||||||
}
|
}
|
||||||
n = n.copy()
|
|
||||||
n.flags = t.newFlag()
|
n.flags = t.newFlag()
|
||||||
n.Children[key[0]] = nn
|
n.Children[key[0]] = nn
|
||||||
return true, n, nil
|
return true, n, nil
|
||||||
|
|
@ -483,7 +478,6 @@ func (t *Trie) delete(n node, prefix, key []byte) (bool, node, error) {
|
||||||
if !dirty || err != nil {
|
if !dirty || err != nil {
|
||||||
return false, n, err
|
return false, n, err
|
||||||
}
|
}
|
||||||
n = n.copy()
|
|
||||||
n.flags = t.newFlag()
|
n.flags = t.newFlag()
|
||||||
n.Children[key[0]] = nn
|
n.Children[key[0]] = nn
|
||||||
|
|
||||||
|
|
@ -576,6 +570,36 @@ func concat(s1 []byte, s2 ...byte) []byte {
|
||||||
return r
|
return r
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// copyNode deep-copies the supplied node along with its children recursively.
|
||||||
|
func copyNode(n node) node {
|
||||||
|
switch n := (n).(type) {
|
||||||
|
case nil:
|
||||||
|
return nil
|
||||||
|
case valueNode:
|
||||||
|
return valueNode(common.CopyBytes(n))
|
||||||
|
|
||||||
|
case *shortNode:
|
||||||
|
return &shortNode{
|
||||||
|
flags: n.flags.copy(),
|
||||||
|
Key: common.CopyBytes(n.Key),
|
||||||
|
Val: copyNode(n.Val),
|
||||||
|
}
|
||||||
|
case *fullNode:
|
||||||
|
var children [17]node
|
||||||
|
for i, cn := range n.Children {
|
||||||
|
children[i] = copyNode(cn)
|
||||||
|
}
|
||||||
|
return &fullNode{
|
||||||
|
flags: n.flags.copy(),
|
||||||
|
Children: children,
|
||||||
|
}
|
||||||
|
case hashNode:
|
||||||
|
return n
|
||||||
|
default:
|
||||||
|
panic(fmt.Sprintf("%T: unknown node type", n))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func (t *Trie) resolve(n node, prefix []byte) (node, error) {
|
func (t *Trie) resolve(n node, prefix []byte) (node, error) {
|
||||||
if n, ok := n.(hashNode); ok {
|
if n, ok := n.(hashNode); ok {
|
||||||
return t.resolveAndTrack(n, prefix)
|
return t.resolveAndTrack(n, prefix)
|
||||||
|
|
@ -593,15 +617,16 @@ func (t *Trie) resolveAndTrack(n hashNode, prefix []byte) (node, error) {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
t.tracer.onRead(prefix, blob)
|
t.tracer.onRead(prefix, blob)
|
||||||
return mustDecodeNode(n, blob), nil
|
|
||||||
|
// The returned node blob won't be changed afterward. No need to
|
||||||
|
// deep-copy the slice.
|
||||||
|
return decodeNodeUnsafe(n, blob)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Hash returns the root hash of the trie. It does not write to the
|
// Hash returns the root hash of the trie. It does not write to the
|
||||||
// database and can be used even if the trie doesn't have one.
|
// database and can be used even if the trie doesn't have one.
|
||||||
func (t *Trie) Hash() common.Hash {
|
func (t *Trie) Hash() common.Hash {
|
||||||
hash, cached := t.hashRoot()
|
return common.BytesToHash(t.hashRoot().(hashNode))
|
||||||
t.root = cached
|
|
||||||
return common.BytesToHash(hash.(hashNode))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Commit collects all dirty nodes in the trie and replaces them with the
|
// Commit collects all dirty nodes in the trie and replaces them with the
|
||||||
|
|
@ -652,9 +677,9 @@ func (t *Trie) Commit(collectLeaf bool) (common.Hash, *trienode.NodeSet) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// hashRoot calculates the root hash of the given trie
|
// hashRoot calculates the root hash of the given trie
|
||||||
func (t *Trie) hashRoot() (node, node) {
|
func (t *Trie) hashRoot() node {
|
||||||
if t.root == nil {
|
if t.root == nil {
|
||||||
return hashNode(types.EmptyRootHash.Bytes()), nil
|
return hashNode(types.EmptyRootHash.Bytes())
|
||||||
}
|
}
|
||||||
// If the number of changes is below 100, we let one thread handle it
|
// If the number of changes is below 100, we let one thread handle it
|
||||||
h := newHasher(t.unhashed >= 100)
|
h := newHasher(t.unhashed >= 100)
|
||||||
|
|
@ -662,8 +687,7 @@ func (t *Trie) hashRoot() (node, node) {
|
||||||
returnHasherToPool(h)
|
returnHasherToPool(h)
|
||||||
t.unhashed = 0
|
t.unhashed = 0
|
||||||
}()
|
}()
|
||||||
hashed, cached := h.hash(t.root, true)
|
return h.hash(t.root, true)
|
||||||
return hashed, cached
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Witness returns a set containing all trie nodes that have been accessed.
|
// Witness returns a set containing all trie nodes that have been accessed.
|
||||||
|
|
|
||||||
|
|
@ -1330,3 +1330,171 @@ func printSet(set *trienode.NodeSet) string {
|
||||||
}
|
}
|
||||||
return out.String()
|
return out.String()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestTrieCopy(t *testing.T) {
|
||||||
|
testTrieCopy(t, []kv{
|
||||||
|
{k: []byte("do"), v: []byte("verb")},
|
||||||
|
{k: []byte("ether"), v: []byte("wookiedoo")},
|
||||||
|
{k: []byte("horse"), v: []byte("stallion")},
|
||||||
|
{k: []byte("shaman"), v: []byte("horse")},
|
||||||
|
{k: []byte("doge"), v: []byte("coin")},
|
||||||
|
{k: []byte("dog"), v: []byte("puppy")},
|
||||||
|
})
|
||||||
|
|
||||||
|
var entries []kv
|
||||||
|
for i := 0; i < 256; i++ {
|
||||||
|
entries = append(entries, kv{k: testrand.Bytes(32), v: testrand.Bytes(32)})
|
||||||
|
}
|
||||||
|
testTrieCopy(t, entries)
|
||||||
|
}
|
||||||
|
|
||||||
|
func testTrieCopy(t *testing.T, entries []kv) {
|
||||||
|
tr := NewEmpty(nil)
|
||||||
|
for _, entry := range entries {
|
||||||
|
tr.Update(entry.k, entry.v)
|
||||||
|
}
|
||||||
|
trCpy := tr.Copy()
|
||||||
|
|
||||||
|
if tr.Hash() != trCpy.Hash() {
|
||||||
|
t.Errorf("Hash mismatch: old %v, copy %v", tr.Hash(), trCpy.Hash())
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check iterator
|
||||||
|
it, _ := tr.NodeIterator(nil)
|
||||||
|
itCpy, _ := trCpy.NodeIterator(nil)
|
||||||
|
|
||||||
|
for it.Next(false) {
|
||||||
|
hasNext := itCpy.Next(false)
|
||||||
|
if !hasNext {
|
||||||
|
t.Fatal("Iterator is not matched")
|
||||||
|
}
|
||||||
|
if !bytes.Equal(it.Path(), itCpy.Path()) {
|
||||||
|
t.Fatal("Iterator is not matched")
|
||||||
|
}
|
||||||
|
if it.Leaf() != itCpy.Leaf() {
|
||||||
|
t.Fatal("Iterator is not matched")
|
||||||
|
}
|
||||||
|
if it.Leaf() && !bytes.Equal(it.LeafBlob(), itCpy.LeafBlob()) {
|
||||||
|
t.Fatal("Iterator is not matched")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check commit
|
||||||
|
root, nodes := tr.Commit(false)
|
||||||
|
rootCpy, nodesCpy := trCpy.Commit(false)
|
||||||
|
if root != rootCpy {
|
||||||
|
t.Fatal("root mismatch")
|
||||||
|
}
|
||||||
|
if len(nodes.Nodes) != len(nodesCpy.Nodes) {
|
||||||
|
t.Fatal("commit node mismatch")
|
||||||
|
}
|
||||||
|
for p, n := range nodes.Nodes {
|
||||||
|
nn, exists := nodesCpy.Nodes[p]
|
||||||
|
if !exists {
|
||||||
|
t.Fatalf("node not exists: %v", p)
|
||||||
|
}
|
||||||
|
if !reflect.DeepEqual(n, nn) {
|
||||||
|
t.Fatalf("node mismatch: %v", p)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestTrieCopyOldTrie(t *testing.T) {
|
||||||
|
testTrieCopyOldTrie(t, []kv{
|
||||||
|
{k: []byte("do"), v: []byte("verb")},
|
||||||
|
{k: []byte("ether"), v: []byte("wookiedoo")},
|
||||||
|
{k: []byte("horse"), v: []byte("stallion")},
|
||||||
|
{k: []byte("shaman"), v: []byte("horse")},
|
||||||
|
{k: []byte("doge"), v: []byte("coin")},
|
||||||
|
{k: []byte("dog"), v: []byte("puppy")},
|
||||||
|
})
|
||||||
|
|
||||||
|
var entries []kv
|
||||||
|
for i := 0; i < 256; i++ {
|
||||||
|
entries = append(entries, kv{k: testrand.Bytes(32), v: testrand.Bytes(32)})
|
||||||
|
}
|
||||||
|
testTrieCopyOldTrie(t, entries)
|
||||||
|
}
|
||||||
|
|
||||||
|
func testTrieCopyOldTrie(t *testing.T, entries []kv) {
|
||||||
|
tr := NewEmpty(nil)
|
||||||
|
for _, entry := range entries {
|
||||||
|
tr.Update(entry.k, entry.v)
|
||||||
|
}
|
||||||
|
hash := tr.Hash()
|
||||||
|
|
||||||
|
trCpy := tr.Copy()
|
||||||
|
for _, val := range entries {
|
||||||
|
if rand.Intn(2) == 0 {
|
||||||
|
trCpy.Delete(val.k)
|
||||||
|
} else {
|
||||||
|
trCpy.Update(val.k, testrand.Bytes(32))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for i := 0; i < 10; i++ {
|
||||||
|
trCpy.Update(testrand.Bytes(32), testrand.Bytes(32))
|
||||||
|
}
|
||||||
|
trCpy.Hash()
|
||||||
|
trCpy.Commit(false)
|
||||||
|
|
||||||
|
// Traverse the original tree, the changes made on the copy one shouldn't
|
||||||
|
// affect the old one
|
||||||
|
for _, entry := range entries {
|
||||||
|
d, _ := tr.Get(entry.k)
|
||||||
|
if !bytes.Equal(d, entry.v) {
|
||||||
|
t.Errorf("Unexpected data, key: %v, want: %v, got: %v", entry.k, entry.v, d)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if tr.Hash() != hash {
|
||||||
|
t.Errorf("Hash mismatch: old %v, new %v", hash, tr.Hash())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestTrieCopyNewTrie(t *testing.T) {
|
||||||
|
testTrieCopyNewTrie(t, []kv{
|
||||||
|
{k: []byte("do"), v: []byte("verb")},
|
||||||
|
{k: []byte("ether"), v: []byte("wookiedoo")},
|
||||||
|
{k: []byte("horse"), v: []byte("stallion")},
|
||||||
|
{k: []byte("shaman"), v: []byte("horse")},
|
||||||
|
{k: []byte("doge"), v: []byte("coin")},
|
||||||
|
{k: []byte("dog"), v: []byte("puppy")},
|
||||||
|
})
|
||||||
|
|
||||||
|
var entries []kv
|
||||||
|
for i := 0; i < 256; i++ {
|
||||||
|
entries = append(entries, kv{k: testrand.Bytes(32), v: testrand.Bytes(32)})
|
||||||
|
}
|
||||||
|
testTrieCopyNewTrie(t, entries)
|
||||||
|
}
|
||||||
|
|
||||||
|
func testTrieCopyNewTrie(t *testing.T, entries []kv) {
|
||||||
|
tr := NewEmpty(nil)
|
||||||
|
for _, entry := range entries {
|
||||||
|
tr.Update(entry.k, entry.v)
|
||||||
|
}
|
||||||
|
trCpy := tr.Copy()
|
||||||
|
hash := trCpy.Hash()
|
||||||
|
|
||||||
|
for _, val := range entries {
|
||||||
|
if rand.Intn(2) == 0 {
|
||||||
|
tr.Delete(val.k)
|
||||||
|
} else {
|
||||||
|
tr.Update(val.k, testrand.Bytes(32))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for i := 0; i < 10; i++ {
|
||||||
|
tr.Update(testrand.Bytes(32), testrand.Bytes(32))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Traverse the original tree, the changes made on the copy one shouldn't
|
||||||
|
// affect the old one
|
||||||
|
for _, entry := range entries {
|
||||||
|
d, _ := trCpy.Get(entry.k)
|
||||||
|
if !bytes.Equal(d, entry.v) {
|
||||||
|
t.Errorf("Unexpected data, key: %v, want: %v, got: %v", entry.k, entry.v, d)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if trCpy.Hash() != hash {
|
||||||
|
t.Errorf("Hash mismatch: old %v, new %v", hash, tr.Hash())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -27,6 +27,8 @@ type NodeReader interface {
|
||||||
// node path and the corresponding node hash. No error will be returned
|
// node path and the corresponding node hash. No error will be returned
|
||||||
// if the node is not found.
|
// if the node is not found.
|
||||||
//
|
//
|
||||||
|
// The returned node content won't be changed after the call.
|
||||||
|
//
|
||||||
// Don't modify the returned byte slice since it's not deep-copied and
|
// Don't modify the returned byte slice since it's not deep-copied and
|
||||||
// still be referenced by database.
|
// still be referenced by database.
|
||||||
Node(owner common.Hash, path []byte, hash common.Hash) ([]byte, error)
|
Node(owner common.Hash, path []byte, hash common.Hash) ([]byte, error)
|
||||||
|
|
|
||||||
|
|
@ -371,27 +371,27 @@ func TestAccountIteratorTraversalValues(t *testing.T) {
|
||||||
h = make(map[common.Hash][]byte)
|
h = make(map[common.Hash][]byte)
|
||||||
)
|
)
|
||||||
for i := byte(2); i < 0xff; i++ {
|
for i := byte(2); i < 0xff; i++ {
|
||||||
a[common.Hash{i}] = []byte(fmt.Sprintf("layer-%d, key %d", 0, i))
|
a[common.Hash{i}] = fmt.Appendf(nil, "layer-%d, key %d", 0, i)
|
||||||
if i > 20 && i%2 == 0 {
|
if i > 20 && i%2 == 0 {
|
||||||
b[common.Hash{i}] = []byte(fmt.Sprintf("layer-%d, key %d", 1, i))
|
b[common.Hash{i}] = fmt.Appendf(nil, "layer-%d, key %d", 1, i)
|
||||||
}
|
}
|
||||||
if i%4 == 0 {
|
if i%4 == 0 {
|
||||||
c[common.Hash{i}] = []byte(fmt.Sprintf("layer-%d, key %d", 2, i))
|
c[common.Hash{i}] = fmt.Appendf(nil, "layer-%d, key %d", 2, i)
|
||||||
}
|
}
|
||||||
if i%7 == 0 {
|
if i%7 == 0 {
|
||||||
d[common.Hash{i}] = []byte(fmt.Sprintf("layer-%d, key %d", 3, i))
|
d[common.Hash{i}] = fmt.Appendf(nil, "layer-%d, key %d", 3, i)
|
||||||
}
|
}
|
||||||
if i%8 == 0 {
|
if i%8 == 0 {
|
||||||
e[common.Hash{i}] = []byte(fmt.Sprintf("layer-%d, key %d", 4, i))
|
e[common.Hash{i}] = fmt.Appendf(nil, "layer-%d, key %d", 4, i)
|
||||||
}
|
}
|
||||||
if i > 50 || i < 85 {
|
if i > 50 || i < 85 {
|
||||||
f[common.Hash{i}] = []byte(fmt.Sprintf("layer-%d, key %d", 5, i))
|
f[common.Hash{i}] = fmt.Appendf(nil, "layer-%d, key %d", 5, i)
|
||||||
}
|
}
|
||||||
if i%64 == 0 {
|
if i%64 == 0 {
|
||||||
g[common.Hash{i}] = []byte(fmt.Sprintf("layer-%d, key %d", 6, i))
|
g[common.Hash{i}] = fmt.Appendf(nil, "layer-%d, key %d", 6, i)
|
||||||
}
|
}
|
||||||
if i%128 == 0 {
|
if i%128 == 0 {
|
||||||
h[common.Hash{i}] = []byte(fmt.Sprintf("layer-%d, key %d", 7, i))
|
h[common.Hash{i}] = fmt.Appendf(nil, "layer-%d, key %d", 7, i)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Assemble a stack of snapshots from the account layers
|
// Assemble a stack of snapshots from the account layers
|
||||||
|
|
@ -480,27 +480,27 @@ func TestStorageIteratorTraversalValues(t *testing.T) {
|
||||||
h = make(map[common.Hash][]byte)
|
h = make(map[common.Hash][]byte)
|
||||||
)
|
)
|
||||||
for i := byte(2); i < 0xff; i++ {
|
for i := byte(2); i < 0xff; i++ {
|
||||||
a[common.Hash{i}] = []byte(fmt.Sprintf("layer-%d, key %d", 0, i))
|
a[common.Hash{i}] = fmt.Appendf(nil, "layer-%d, key %d", 0, i)
|
||||||
if i > 20 && i%2 == 0 {
|
if i > 20 && i%2 == 0 {
|
||||||
b[common.Hash{i}] = []byte(fmt.Sprintf("layer-%d, key %d", 1, i))
|
b[common.Hash{i}] = fmt.Appendf(nil, "layer-%d, key %d", 1, i)
|
||||||
}
|
}
|
||||||
if i%4 == 0 {
|
if i%4 == 0 {
|
||||||
c[common.Hash{i}] = []byte(fmt.Sprintf("layer-%d, key %d", 2, i))
|
c[common.Hash{i}] = fmt.Appendf(nil, "layer-%d, key %d", 2, i)
|
||||||
}
|
}
|
||||||
if i%7 == 0 {
|
if i%7 == 0 {
|
||||||
d[common.Hash{i}] = []byte(fmt.Sprintf("layer-%d, key %d", 3, i))
|
d[common.Hash{i}] = fmt.Appendf(nil, "layer-%d, key %d", 3, i)
|
||||||
}
|
}
|
||||||
if i%8 == 0 {
|
if i%8 == 0 {
|
||||||
e[common.Hash{i}] = []byte(fmt.Sprintf("layer-%d, key %d", 4, i))
|
e[common.Hash{i}] = fmt.Appendf(nil, "layer-%d, key %d", 4, i)
|
||||||
}
|
}
|
||||||
if i > 50 || i < 85 {
|
if i > 50 || i < 85 {
|
||||||
f[common.Hash{i}] = []byte(fmt.Sprintf("layer-%d, key %d", 5, i))
|
f[common.Hash{i}] = fmt.Appendf(nil, "layer-%d, key %d", 5, i)
|
||||||
}
|
}
|
||||||
if i%64 == 0 {
|
if i%64 == 0 {
|
||||||
g[common.Hash{i}] = []byte(fmt.Sprintf("layer-%d, key %d", 6, i))
|
g[common.Hash{i}] = fmt.Appendf(nil, "layer-%d, key %d", 6, i)
|
||||||
}
|
}
|
||||||
if i%128 == 0 {
|
if i%128 == 0 {
|
||||||
h[common.Hash{i}] = []byte(fmt.Sprintf("layer-%d, key %d", 7, i))
|
h[common.Hash{i}] = fmt.Appendf(nil, "layer-%d, key %d", 7, i)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Assemble a stack of snapshots from the account layers
|
// Assemble a stack of snapshots from the account layers
|
||||||
|
|
|
||||||
|
|
@ -37,7 +37,8 @@ import (
|
||||||
// the combined trie node set from several aggregated state transitions.
|
// the combined trie node set from several aggregated state transitions.
|
||||||
type nodeSet struct {
|
type nodeSet struct {
|
||||||
size uint64 // aggregated size of the trie node
|
size uint64 // aggregated size of the trie node
|
||||||
nodes map[common.Hash]map[string]*trienode.Node // node set, mapped by owner and path
|
accountNodes map[string]*trienode.Node // account trie nodes, mapped by path
|
||||||
|
storageNodes map[common.Hash]map[string]*trienode.Node // storage trie nodes, mapped by owner and path
|
||||||
}
|
}
|
||||||
|
|
||||||
// newNodeSet constructs the set with the provided dirty trie nodes.
|
// newNodeSet constructs the set with the provided dirty trie nodes.
|
||||||
|
|
@ -46,7 +47,17 @@ func newNodeSet(nodes map[common.Hash]map[string]*trienode.Node) *nodeSet {
|
||||||
if nodes == nil {
|
if nodes == nil {
|
||||||
nodes = make(map[common.Hash]map[string]*trienode.Node)
|
nodes = make(map[common.Hash]map[string]*trienode.Node)
|
||||||
}
|
}
|
||||||
s := &nodeSet{nodes: nodes}
|
s := &nodeSet{
|
||||||
|
accountNodes: make(map[string]*trienode.Node),
|
||||||
|
storageNodes: make(map[common.Hash]map[string]*trienode.Node),
|
||||||
|
}
|
||||||
|
for owner, subset := range nodes {
|
||||||
|
if owner == (common.Hash{}) {
|
||||||
|
s.accountNodes = subset
|
||||||
|
} else {
|
||||||
|
s.storageNodes[owner] = subset
|
||||||
|
}
|
||||||
|
}
|
||||||
s.computeSize()
|
s.computeSize()
|
||||||
return s
|
return s
|
||||||
}
|
}
|
||||||
|
|
@ -54,13 +65,12 @@ func newNodeSet(nodes map[common.Hash]map[string]*trienode.Node) *nodeSet {
|
||||||
// computeSize calculates the database size of the held trie nodes.
|
// computeSize calculates the database size of the held trie nodes.
|
||||||
func (s *nodeSet) computeSize() {
|
func (s *nodeSet) computeSize() {
|
||||||
var size uint64
|
var size uint64
|
||||||
for owner, subset := range s.nodes {
|
for path, n := range s.accountNodes {
|
||||||
var prefix int
|
size += uint64(len(n.Blob) + len(path))
|
||||||
if owner != (common.Hash{}) {
|
|
||||||
prefix = common.HashLength // owner (32 bytes) for storage trie nodes
|
|
||||||
}
|
}
|
||||||
|
for _, subset := range s.storageNodes {
|
||||||
for path, n := range subset {
|
for path, n := range subset {
|
||||||
size += uint64(prefix + len(n.Blob) + len(path))
|
size += uint64(common.HashLength + len(n.Blob) + len(path))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
s.size = size
|
s.size = size
|
||||||
|
|
@ -79,15 +89,18 @@ func (s *nodeSet) updateSize(delta int64) {
|
||||||
|
|
||||||
// node retrieves the trie node with node path and its trie identifier.
|
// node retrieves the trie node with node path and its trie identifier.
|
||||||
func (s *nodeSet) node(owner common.Hash, path []byte) (*trienode.Node, bool) {
|
func (s *nodeSet) node(owner common.Hash, path []byte) (*trienode.Node, bool) {
|
||||||
subset, ok := s.nodes[owner]
|
// Account trie node
|
||||||
|
if owner == (common.Hash{}) {
|
||||||
|
n, ok := s.accountNodes[string(path)]
|
||||||
|
return n, ok
|
||||||
|
}
|
||||||
|
// Storage trie node
|
||||||
|
subset, ok := s.storageNodes[owner]
|
||||||
if !ok {
|
if !ok {
|
||||||
return nil, false
|
return nil, false
|
||||||
}
|
}
|
||||||
n, ok := subset[string(path)]
|
n, ok := subset[string(path)]
|
||||||
if !ok {
|
return n, ok
|
||||||
return nil, false
|
|
||||||
}
|
|
||||||
return n, true
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// merge integrates the provided dirty nodes into the set. The provided nodeset
|
// merge integrates the provided dirty nodes into the set. The provided nodeset
|
||||||
|
|
@ -97,15 +110,24 @@ func (s *nodeSet) merge(set *nodeSet) {
|
||||||
delta int64 // size difference resulting from node merging
|
delta int64 // size difference resulting from node merging
|
||||||
overwrite counter // counter of nodes being overwritten
|
overwrite counter // counter of nodes being overwritten
|
||||||
)
|
)
|
||||||
for owner, subset := range set.nodes {
|
|
||||||
var prefix int
|
// Merge account nodes
|
||||||
if owner != (common.Hash{}) {
|
for path, n := range set.accountNodes {
|
||||||
prefix = common.HashLength
|
if orig, exist := s.accountNodes[path]; !exist {
|
||||||
|
delta += int64(len(n.Blob) + len(path))
|
||||||
|
} else {
|
||||||
|
delta += int64(len(n.Blob) - len(orig.Blob))
|
||||||
|
overwrite.add(len(orig.Blob) + len(path))
|
||||||
}
|
}
|
||||||
current, exist := s.nodes[owner]
|
s.accountNodes[path] = n
|
||||||
|
}
|
||||||
|
|
||||||
|
// Merge storage nodes
|
||||||
|
for owner, subset := range set.storageNodes {
|
||||||
|
current, exist := s.storageNodes[owner]
|
||||||
if !exist {
|
if !exist {
|
||||||
for path, n := range subset {
|
for path, n := range subset {
|
||||||
delta += int64(prefix + len(n.Blob) + len(path))
|
delta += int64(common.HashLength + len(n.Blob) + len(path))
|
||||||
}
|
}
|
||||||
// Perform a shallow copy of the map for the subset instead of claiming it
|
// Perform a shallow copy of the map for the subset instead of claiming it
|
||||||
// directly from the provided nodeset to avoid potential concurrent map
|
// directly from the provided nodeset to avoid potential concurrent map
|
||||||
|
|
@ -113,19 +135,19 @@ func (s *nodeSet) merge(set *nodeSet) {
|
||||||
// accessible even after merging. Therefore, ownership of the nodes map
|
// accessible even after merging. Therefore, ownership of the nodes map
|
||||||
// should still belong to the original layer, and any modifications to it
|
// should still belong to the original layer, and any modifications to it
|
||||||
// should be prevented.
|
// should be prevented.
|
||||||
s.nodes[owner] = maps.Clone(subset)
|
s.storageNodes[owner] = maps.Clone(subset)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
for path, n := range subset {
|
for path, n := range subset {
|
||||||
if orig, exist := current[path]; !exist {
|
if orig, exist := current[path]; !exist {
|
||||||
delta += int64(prefix + len(n.Blob) + len(path))
|
delta += int64(common.HashLength + len(n.Blob) + len(path))
|
||||||
} else {
|
} else {
|
||||||
delta += int64(len(n.Blob) - len(orig.Blob))
|
delta += int64(len(n.Blob) - len(orig.Blob))
|
||||||
overwrite.add(prefix + len(orig.Blob) + len(path))
|
overwrite.add(common.HashLength + len(orig.Blob) + len(path))
|
||||||
}
|
}
|
||||||
current[path] = n
|
current[path] = n
|
||||||
}
|
}
|
||||||
s.nodes[owner] = current
|
s.storageNodes[owner] = current
|
||||||
}
|
}
|
||||||
overwrite.report(gcTrieNodeMeter, gcTrieNodeBytesMeter)
|
overwrite.report(gcTrieNodeMeter, gcTrieNodeBytesMeter)
|
||||||
s.updateSize(delta)
|
s.updateSize(delta)
|
||||||
|
|
@ -136,36 +158,40 @@ func (s *nodeSet) merge(set *nodeSet) {
|
||||||
func (s *nodeSet) revertTo(db ethdb.KeyValueReader, nodes map[common.Hash]map[string]*trienode.Node) {
|
func (s *nodeSet) revertTo(db ethdb.KeyValueReader, nodes map[common.Hash]map[string]*trienode.Node) {
|
||||||
var delta int64
|
var delta int64
|
||||||
for owner, subset := range nodes {
|
for owner, subset := range nodes {
|
||||||
current, ok := s.nodes[owner]
|
if owner == (common.Hash{}) {
|
||||||
|
// 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()))
|
||||||
|
}
|
||||||
|
s.accountNodes[path] = n
|
||||||
|
delta += int64(len(n.Blob)) - int64(len(orig.Blob))
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Storage trie nodes
|
||||||
|
current, ok := s.storageNodes[owner]
|
||||||
if !ok {
|
if !ok {
|
||||||
panic(fmt.Sprintf("non-existent subset (%x)", owner))
|
panic(fmt.Sprintf("non-existent subset (%x)", owner))
|
||||||
}
|
}
|
||||||
for path, n := range subset {
|
for path, n := range subset {
|
||||||
orig, ok := current[path]
|
orig, ok := current[path]
|
||||||
if !ok {
|
if !ok {
|
||||||
// There is a special case in merkle tree that one child is removed
|
blob := rawdb.ReadStorageTrieNode(db, owner, []byte(path))
|
||||||
// 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))
|
|
||||||
}
|
|
||||||
// Ignore the clean node in the case described above.
|
|
||||||
if bytes.Equal(blob, n.Blob) {
|
if bytes.Equal(blob, n.Blob) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
panic(fmt.Sprintf("non-existent node (%x %v) blob: %v", owner, path, crypto.Keccak256Hash(n.Blob).Hex()))
|
panic(fmt.Sprintf("non-existent storage node (%x %v) blob: %v", owner, path, crypto.Keccak256Hash(n.Blob).Hex()))
|
||||||
}
|
}
|
||||||
current[path] = n
|
current[path] = n
|
||||||
delta += int64(len(n.Blob)) - int64(len(orig.Blob))
|
delta += int64(len(n.Blob)) - int64(len(orig.Blob))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
s.updateSize(delta)
|
s.updateSize(delta)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -184,8 +210,21 @@ type journalNodes struct {
|
||||||
|
|
||||||
// encode serializes the content of trie nodes into the provided writer.
|
// encode serializes the content of trie nodes into the provided writer.
|
||||||
func (s *nodeSet) encode(w io.Writer) error {
|
func (s *nodeSet) encode(w io.Writer) error {
|
||||||
nodes := make([]journalNodes, 0, len(s.nodes))
|
nodes := make([]journalNodes, 0, len(s.storageNodes)+1)
|
||||||
for owner, subset := range s.nodes {
|
|
||||||
|
// Encode account nodes
|
||||||
|
if len(s.accountNodes) > 0 {
|
||||||
|
entry := journalNodes{Owner: common.Hash{}}
|
||||||
|
for path, node := range s.accountNodes {
|
||||||
|
entry.Nodes = append(entry.Nodes, journalNode{
|
||||||
|
Path: []byte(path),
|
||||||
|
Blob: node.Blob,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
nodes = append(nodes, entry)
|
||||||
|
}
|
||||||
|
// Encode storage nodes
|
||||||
|
for owner, subset := range s.storageNodes {
|
||||||
entry := journalNodes{Owner: owner}
|
entry := journalNodes{Owner: owner}
|
||||||
for path, node := range subset {
|
for path, node := range subset {
|
||||||
entry.Nodes = append(entry.Nodes, journalNode{
|
entry.Nodes = append(entry.Nodes, journalNode{
|
||||||
|
|
@ -204,8 +243,21 @@ func (s *nodeSet) decode(r *rlp.Stream) error {
|
||||||
if err := r.Decode(&encoded); err != nil {
|
if err := r.Decode(&encoded); err != nil {
|
||||||
return fmt.Errorf("load nodes: %v", err)
|
return fmt.Errorf("load nodes: %v", err)
|
||||||
}
|
}
|
||||||
nodes := make(map[common.Hash]map[string]*trienode.Node)
|
s.accountNodes = make(map[string]*trienode.Node)
|
||||||
|
s.storageNodes = make(map[common.Hash]map[string]*trienode.Node)
|
||||||
|
|
||||||
for _, entry := range encoded {
|
for _, entry := range encoded {
|
||||||
|
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)
|
subset := make(map[string]*trienode.Node)
|
||||||
for _, n := range entry.Nodes {
|
for _, n := range entry.Nodes {
|
||||||
if len(n.Blob) > 0 {
|
if len(n.Blob) > 0 {
|
||||||
|
|
@ -214,33 +266,38 @@ func (s *nodeSet) decode(r *rlp.Stream) error {
|
||||||
subset[string(n.Path)] = trienode.NewDeleted()
|
subset[string(n.Path)] = trienode.NewDeleted()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
nodes[entry.Owner] = subset
|
s.storageNodes[entry.Owner] = subset
|
||||||
|
}
|
||||||
}
|
}
|
||||||
s.nodes = nodes
|
|
||||||
s.computeSize()
|
s.computeSize()
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// write flushes nodes into the provided database batch as a whole.
|
// write flushes nodes into the provided database batch as a whole.
|
||||||
func (s *nodeSet) write(batch ethdb.Batch, clean *fastcache.Cache) int {
|
func (s *nodeSet) write(batch ethdb.Batch, clean *fastcache.Cache) int {
|
||||||
return writeNodes(batch, s.nodes, clean)
|
nodes := make(map[common.Hash]map[string]*trienode.Node)
|
||||||
|
if len(s.accountNodes) > 0 {
|
||||||
|
nodes[common.Hash{}] = s.accountNodes
|
||||||
|
}
|
||||||
|
for owner, subset := range s.storageNodes {
|
||||||
|
nodes[owner] = subset
|
||||||
|
}
|
||||||
|
return writeNodes(batch, nodes, clean)
|
||||||
}
|
}
|
||||||
|
|
||||||
// reset clears all cached trie node data.
|
// reset clears all cached trie node data.
|
||||||
func (s *nodeSet) reset() {
|
func (s *nodeSet) reset() {
|
||||||
s.nodes = make(map[common.Hash]map[string]*trienode.Node)
|
s.accountNodes = make(map[string]*trienode.Node)
|
||||||
|
s.storageNodes = make(map[common.Hash]map[string]*trienode.Node)
|
||||||
s.size = 0
|
s.size = 0
|
||||||
}
|
}
|
||||||
|
|
||||||
// dbsize returns the approximate size of db write.
|
// dbsize returns the approximate size of db write.
|
||||||
func (s *nodeSet) dbsize() int {
|
func (s *nodeSet) dbsize() int {
|
||||||
var m int
|
var m int
|
||||||
for owner, nodes := range s.nodes {
|
m += len(s.accountNodes) * len(rawdb.TrieNodeAccountPrefix) // database key prefix
|
||||||
if owner == (common.Hash{}) {
|
for _, nodes := range s.storageNodes {
|
||||||
m += len(nodes) * len(rawdb.TrieNodeAccountPrefix) // database key prefix
|
|
||||||
} else {
|
|
||||||
m += len(nodes) * (len(rawdb.TrieNodeStoragePrefix)) // database key prefix
|
m += len(nodes) * (len(rawdb.TrieNodeStoragePrefix)) // database key prefix
|
||||||
}
|
}
|
||||||
}
|
|
||||||
return m + int(s.size)
|
return m + int(s.size)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -19,6 +19,6 @@ package version
|
||||||
const (
|
const (
|
||||||
Major = 1 // Major version component of the current release
|
Major = 1 // Major version component of the current release
|
||||||
Minor = 15 // Minor version component of the current release
|
Minor = 15 // Minor version component of the current release
|
||||||
Patch = 6 // Patch version component of the current release
|
Patch = 8 // Patch version component of the current release
|
||||||
Meta = "unstable" // Version metadata to append to the version string
|
Meta = "unstable" // Version metadata to append to the version string
|
||||||
)
|
)
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue