mirror of
https://github.com/ethereum/go-ethereum.git
synced 2026-07-27 15:16:43 +00:00
Merge tag 'v1.15.7' into release/geth-1.x-fh3.0
This commit is contained in:
commit
da61ce6491
42 changed files with 901 additions and 445 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
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
|
|
|
||||||
|
|
@ -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.
|
||||||
|
|
|
||||||
|
|
@ -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,7 +29,6 @@ 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"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -59,6 +58,7 @@ type FilterMaps struct {
|
||||||
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
|
||||||
|
|
||||||
|
|
@ -69,6 +69,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
|
||||||
|
|
||||||
|
|
@ -180,6 +181,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.
|
||||||
|
|
@ -197,6 +202,7 @@ 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{}),
|
disabledCh: make(chan struct{}),
|
||||||
exportFileName: config.ExportFileName,
|
exportFileName: config.ExportFileName,
|
||||||
Params: params,
|
Params: params,
|
||||||
|
|
@ -208,6 +214,8 @@ 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,
|
historyCutoff: historyCutoff,
|
||||||
finalBlock: finalBlock,
|
finalBlock: finalBlock,
|
||||||
matcherSyncCh: make(chan *FilterMapsMatcherBackend),
|
matcherSyncCh: make(chan *FilterMapsMatcherBackend),
|
||||||
|
|
@ -301,14 +309,24 @@ func (f *FilterMaps) reset() {
|
||||||
// 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)
|
||||||
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
|
// ensure that there is no remaining data in the filter maps key range
|
||||||
if !f.safeDeleteRange(rawdb.DeleteFilterMapsDb, "Resetting log index database") {
|
if err := f.safeDeleteWithLogs(rawdb.DeleteFilterMapsDb, "Resetting log index database", f.isShuttingDown); err != nil {
|
||||||
return errors.New("could not reset log index database")
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
f.indexLock.Lock()
|
f.indexLock.Lock()
|
||||||
|
|
@ -358,38 +376,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
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -658,54 +675,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"
|
||||||
|
|
||||||
|
|
@ -49,18 +50,15 @@ func (f *FilterMaps) indexerLoop() {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if err := f.init(); err != nil {
|
if err := f.init(); err != nil {
|
||||||
log.Error("Error initializing log index; reverting to unindexed mode", "error", err)
|
f.disableForError("initialization", err)
|
||||||
f.reset()
|
f.reset() // remove broken index from DB
|
||||||
f.disabled = true
|
|
||||||
close(f.disabledCh)
|
|
||||||
return
|
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 {
|
||||||
|
|
@ -69,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 {
|
||||||
|
|
@ -116,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
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -196,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()
|
||||||
|
|
@ -230,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",
|
||||||
|
|
@ -240,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
|
||||||
|
|
@ -248,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) {
|
||||||
|
|
@ -256,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
|
||||||
|
|
@ -268,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 {
|
||||||
|
|
@ -302,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() {
|
||||||
|
|
@ -318,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() {
|
||||||
|
|
@ -354,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
|
||||||
|
|
|
||||||
|
|
@ -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, bloomBitsMetaPrefix)
|
return deletePrefixRange(db, bloomBitsMetaPrefix, hashScheme, stopCallback)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -28,12 +28,15 @@ import (
|
||||||
"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
|
||||||
|
|
@ -388,10 +391,6 @@ func InspectDatabase(db ethdb.Database, keyPrefix, keyStart []byte) error {
|
||||||
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
|
||||||
|
|
@ -465,16 +464,6 @@ func InspectDatabase(db ethdb.Database, keyPrefix, keyStart []byte) error {
|
||||||
case bytes.HasPrefix(key, bloomBitsMetaPrefix) && len(key) < len(bloomBitsMetaPrefix)+8:
|
case bytes.HasPrefix(key, bloomBitsMetaPrefix) && len(key) < len(bloomBitsMetaPrefix)+8:
|
||||||
bloomBits.Add(size)
|
bloomBits.Add(size)
|
||||||
|
|
||||||
// LES indexes (deprecated)
|
|
||||||
case bytes.HasPrefix(key, chtTablePrefix) ||
|
|
||||||
bytes.HasPrefix(key, chtIndexTablePrefix) ||
|
|
||||||
bytes.HasPrefix(key, chtPrefix): // Canonical hash trie
|
|
||||||
chtTrieNodes.Add(size)
|
|
||||||
case bytes.HasPrefix(key, bloomTrieTablePrefix) ||
|
|
||||||
bytes.HasPrefix(key, bloomTrieIndexPrefix) ||
|
|
||||||
bytes.HasPrefix(key, bloomTriePrefix): // Bloomtrie sub
|
|
||||||
bloomTrieNodes.Add(size)
|
|
||||||
|
|
||||||
// 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):
|
||||||
remain := key[len(VerklePrefix):]
|
remain := key[len(VerklePrefix):]
|
||||||
|
|
@ -538,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)
|
||||||
|
|
@ -623,3 +610,73 @@ func ReadChainMetadata(db ethdb.KeyValueStore) [][]string {
|
||||||
}
|
}
|
||||||
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()
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -144,14 +144,6 @@ var (
|
||||||
// old log index
|
// old log index
|
||||||
bloomBitsMetaPrefix = []byte("iB")
|
bloomBitsMetaPrefix = []byte("iB")
|
||||||
|
|
||||||
// LES indexes
|
|
||||||
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-")
|
|
||||||
|
|
||||||
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
|
||||||
|
|
|
||||||
|
|
@ -603,7 +603,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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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]
|
||||||
|
|
@ -1849,6 +1849,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)
|
||||||
|
|
|
||||||
|
|
@ -108,6 +108,19 @@ func (env *testEnv) makeTx(nonce uint64, gasPrice *big.Int) *types.Transaction {
|
||||||
return tx
|
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() {
|
func (env *testEnv) commit() {
|
||||||
head := env.chain.CurrentBlock()
|
head := env.chain.CurrentBlock()
|
||||||
block := env.chain.GetBlock(head.Hash(), head.Number.Uint64())
|
block := env.chain.GetBlock(head.Hash(), head.Number.Uint64())
|
||||||
|
|
@ -177,3 +190,29 @@ func TestRejectInvalids(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -466,9 +466,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.
|
||||||
|
|
@ -481,6 +481,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) {
|
||||||
|
|
@ -556,6 +565,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()
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -327,7 +327,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,7 +239,12 @@ 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())
|
||||||
historyCutoff := eth.blockchain.HistoryPruningCutoff()
|
historyCutoff := eth.blockchain.HistoryPruningCutoff()
|
||||||
var finalBlock uint64
|
var finalBlock uint64
|
||||||
|
|
|
||||||
|
|
@ -403,7 +403,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
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -1117,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
|
||||||
}
|
}
|
||||||
|
|
@ -1136,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
|
||||||
|
|
@ -1166,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 {
|
||||||
|
|
@ -1186,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)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -3529,3 +3529,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)
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -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 {
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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 = 7 // Patch version component of the current release
|
||||||
Meta = "stable" // Version metadata to append to the version string
|
Meta = "stable" // Version metadata to append to the version string
|
||||||
)
|
)
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue